cargo_dist_schema/macros.rs
1//! Internal macros for cargo-dist
2
3/// ## Motivation
4///
5/// cargo-dist deals with a lot of "string-like" types. A target triple,
6/// like `x86_64-unknown-linux-gnu`, for example, is string-like. So is
7/// a github runner name, like `macos-14`.
8///
9/// Declaring `target` fields to be of type `String` might sound fine,
10/// but when you're looking at:
11///
12/// ```rust,ignore
13/// let mystery_var: BTreeMap<String, BTreeMap<String, Vec<String>>>;
14/// ```
15///
16/// how do you know what each of those `String` refer to?
17///
18/// Rust lets you declare type aliases, so you might do:
19///
20/// ```rust,ignore
21/// type TargetTriple = String;
22/// ```
23///
24/// And then the type of our mystery_var becomes a little clearer:
25///
26/// ```rust,ignore
27/// let mystery_var: BTreeMap<TargetTriple, BTReeMap<String, Vec<String>>>;
28/// ```
29///
30/// However, this is a small cosmetic difference: we didn't gain any actual
31/// type safety.
32///
33/// We can still very much assign it things that are completely unrelated:
34///
35/// ```rust,ignore
36/// type TargetTriple = String;
37/// type GitHubRunner = String;
38///
39/// let mut target: TargetTriple = "x86_64-unknown-linux-gnu".to_owned(); // so far so good
40/// let runner: GithubRunner = "macos-14".to_owned(); // that's okay too
41/// target = runner; // 💥 uh oh! this compiles, but it shouldn't!
42/// target = "complete nonsense".into(); // 😠no, code, what are you doing! compiler help us!
43/// ```
44///
45/// If we want those two types to be actually distinct, we have to make "new types" for them.
46/// We could make a struct with a field:
47///
48/// ```rust,ignore
49/// pub struct TargetTriple {
50/// pub value: String,
51/// }
52///
53/// let t: TargetTriple = get_target();
54/// eprintln!("our target is {}", t.value);
55/// ```
56///
57/// But that's a bit wordy — we're always ever going to have one field. The Rust pattern
58/// most commonly used here is to use a "tuple struct": think of it as a struct with numbered
59/// fields: in this case, it has a single field, named `0`
60///
61/// ```rust,ignore
62/// pub struct TargetTriple(String);
63///
64/// let t: TargetTriple = get_target();
65/// eprintln!("our target is {}", t.0);
66/// ```
67///
68/// With this technique, it's impossible to _accidentally_ assign a `GithubRunner`
69/// to a `TargetTriple`, for example:
70///
71/// ```rust,ignore
72/// pub struct TargetTriple(String);
73/// pub struct GithubRunner(String);
74///
75/// let mut target: TargetTriple = get_target();
76/// let runner: GithubRunner = get_runner();
77/// target = runner; // ✨ this is now a compile error!
78/// ```
79///
80/// We now have the compiler's back. We can now use rust-analyzer's "Find all references"
81/// functionality on `TargetTriple` and find all the places in the codebase
82/// we care about targets!
83///
84/// But we usually want to do _more_ with these types. Just like we're
85/// able to compare `String`s for equality, and order them, and hash
86/// them, and clone them, we also want to be able to do that for types
87/// like `TargetTriple` and `GithubRunner`.
88///
89/// We also want to be able to build references to them, perhaps some from
90/// some static string. `String` has the corresponding unsized type `str`,
91/// and `String::as_ref()` returns a `&str` — we need some sort of similar
92/// mapping here.
93///
94/// Doing all this by hand, exactly correct, every time, for every one of
95/// those types, is tricky. `String` and `&str` are linked together with
96/// multiple `From`, `AsRef`, and `Deref` implementations: it's really easy
97/// to forget one.
98///
99/// So, this macro does all that for you!
100///
101/// ## Usage
102///
103/// Let's review what you need to know to use a type declared by this macro.
104///
105/// ### Declaring a new type
106///
107/// You can invoke this macro to declare one or more "strongly-typed string"
108/// types, like so:
109///
110/// ```rust,ignore
111/// declare_strongly_typed_string! {
112/// /// TargetTriple docs go here
113/// pub const TargetTriple => &TripleNameRef;
114///
115/// /// GithubRunner docs go here
116/// pub const GithubRunner => &GithubRunner;
117/// }
118/// ```
119///
120/// ### Taking values of that type
121///
122/// Let's assume we're talking about `TargetTriple`: if you'd normally use
123/// the `String` type, (ie. you need ownership of that type, maybe you're
124/// storing it in a struct), then you'll want `TargetTriple` itself:
125///
126/// ```rust,ignore
127/// struct Blah {
128/// targets: Vec<String>;
129/// }
130/// // 👇 becomes
131/// struct Blah {
132/// targets: Vec<TargetTriple>;
133/// }
134/// ```
135///
136/// If you're only reading from it, then maybe you can take a `&TripleNameRef` instead:
137///
138/// ```rust,ignore
139/// fn is_target_triple_funny(target: &str) -> bool {
140/// target.contains("loong") // let's be honest: it's kinda funny
141/// }
142/// // 👇 becomes
143/// fn is_target_triple_funny(target: &TripleNameRef) -> bool {
144/// target.as_str().contains("loong")
145/// }
146/// ```
147///
148/// You don't _have_ to, but it lets you take values built from 'static strings,
149/// which... guess what the next section is about?
150///
151/// ### Creating values of that type
152///
153/// You can create owned values with `::new()`:
154///
155/// ```rust,ignore
156/// let target = String::from("x86_64-unknown-linux-gnu");
157/// // 👇 becomes
158/// let target = TargetTriple::new("x86_64-unknown-linux-gnu");
159/// ```
160///
161/// And now you can "Find all reference" for `TargetTriple::new` to find
162/// all the places in the codebase where you're turning "user input" into
163/// such a value.
164///
165/// You can still mess up, but it takes effort, and it's easier to find
166/// places to review.
167///
168/// You can also create references with `::from_str()`:
169///
170/// ```rust,ignore
171/// let target = TargetTriple::from_str("x86_64-unknown-linux-gnu");
172/// // 👇 becomes
173/// let target = TargetTriple::new("x86_64-unknown-linux-gnu");
174/// ```
175///
176/// What you're getting here is a `&'static TripleNameRef` — no allocations
177/// involved, and if your functions take `&TripleNameRef`, then you're already
178/// all set.
179///
180/// ### Treating it as a string anyway
181///
182/// You can access the underlying value with `::as_str()`:
183///
184/// ```rust,ignore
185/// let target = String::from("x86_64-unknown-linux-gnu");
186/// let first_token = target.split('-').next().unwrap();
187/// // 👇 becomes
188/// let target = TargetTriple::new("x86_64-unknown-linux-gnu");
189/// let first_token = target.as_str().split('-').next().unwrap();
190/// ```
191///
192/// Of course, the `String/&str` version is shorter — the whole thing
193/// is to _discourage_ you from treating that value as a string: to have
194/// it live as a string as short as possible, to avoid mistakes.
195///
196/// ### Adding methods
197///
198/// Because `TargetTriple` is a type we declare ourselves, as opposed to
199/// `String`, which is declared in the standard library, we can define
200/// our own methods on it, like so:
201///
202/// ```rust,ignore
203/// impl TargetTriple {
204/// pub fn tokens(&self) -> impl Iterator<Item = &str> {
205/// self.as_str().split('-')
206/// }
207/// }
208/// ```
209///
210/// And then the transformation above would look more like:
211///
212/// ```rust,ignore
213/// let target = String::from("x86_64-unknown-linux-gnu");
214/// let first_token = target.split('-').next().unwrap();
215/// // 👇 becomes
216/// let target = TargetTriple::new("x86_64-unknown-linux-gnu");
217/// let first_token = target.tokens().next().unwrap();
218/// ```
219///
220/// Now we're not duplicating the logic of "splitting on '-'" in a bunch
221/// of places. Of course, it's unlikely that target triples will suddenly
222/// switched to em-dash as a separator, but you get the gist.
223///
224/// Note that even the code above `target.tokens()` is a bit too
225/// stringly-typed: we could have a `.as_parsed()` method that returns
226/// a struct like `ParsedTriple`, which has separate fields for
227/// "os", "arch", "abigunk", etc. — again, there would be only one
228/// path from `TargetTriple` to `ParsedTriple`, which would be easy to
229/// search to, the logic for transforming one into the other would be
230/// in a single place, etc. You get it.
231///
232/// ### Annoying corner cases: slices
233///
234/// This will not work:
235///
236/// ```rust,ignore
237/// fn i_take_a_slice(targets: &[TripleNameRef]) { todo!(targets) }
238///
239/// let targets = vec![TargetTriple::new("x86_64-unknown-linux-gnu")];
240/// i_take_a_slice(&targets);
241/// ```
242///
243/// Because you have a `&Vec<TargetTriple>`, and `Deref` only takes you
244/// as far as `&[TargetTriple]`, but not `&[TripleNameRef]`. If you
245/// encounter that case, it's probably fine to just take a `&[TargetTriple]`.
246///
247/// Note that you would have the same problem with `Vec<String>`: it would give
248/// you a `&[String]`, not a `&[&str]`. You could take an `impl Iterator<Item = &str>`
249/// if you really wanted to.
250///
251/// ### Annoying corner case: match
252///
253/// This will not work:
254///
255/// ```rust,ignore
256/// fn match_on_target(target: &TripleNameRef) => &str {
257/// match target {
258/// TARGET_X64_WINDOWS => "what's up gamers",
259/// _ => "good morning",
260/// }
261/// }
262/// ```
263///
264/// Even if `TARGET_X64_WINDOWS` is a `&'static TripleNameRef` and
265/// a `const`. Doesn't matter. rustc says no. Maybe in the future.
266///
267/// For now, just stick it in a `HashMap`, or use an if-else chain or something. Sorry!
268#[macro_export]
269macro_rules! declare_strongly_typed_string {
270 ($(
271 $(#[$attr:meta])*
272 $vis:vis struct $name:ident => &$ref_name:ident;
273 )+) => {
274 $(
275 #[derive(Clone, Hash, PartialEq, Eq, Ord, PartialOrd)]
276 #[derive(serde::Serialize, serde::Deserialize)]
277 #[derive(schemars::JsonSchema)]
278 #[serde(transparent)]
279 #[repr(transparent)]
280 $(#[$attr])*
281 pub struct $name(String);
282
283 #[automatically_derived]
284 impl $name {
285 /// Constructs a new strongly-typed value
286 #[inline]
287 pub const fn new(raw: String) -> Self {
288 Self(raw)
289 }
290
291 #[doc = "Turn $name into $ref_name explicitly"]
292 #[inline]
293 pub fn as_explicit_ref(&self) -> &$ref_name {
294 &self
295 }
296 }
297
298 #[automatically_derived]
299 impl ::std::borrow::Borrow<$ref_name> for $name {
300 #[inline]
301 fn borrow(&self) -> &$ref_name {
302 ::std::ops::Deref::deref(self)
303 }
304 }
305
306 #[automatically_derived]
307 impl ::std::convert::AsRef<$ref_name> for $name {
308 #[inline]
309 fn as_ref(&self) -> &$ref_name {
310 ::std::ops::Deref::deref(self)
311 }
312 }
313
314 #[automatically_derived]
315 impl ::std::convert::AsRef<str> for $name {
316 #[inline]
317 fn as_ref(&self) -> &str {
318 self.as_str()
319 }
320 }
321
322 #[automatically_derived]
323 impl ::std::str::FromStr for $name {
324 type Err = ::std::convert::Infallible;
325 #[inline]
326 fn from_str(s: &str) -> ::std::result::Result<Self, Self::Err> {
327 ::std::result::Result::Ok($name::new(s.to_owned()))
328 }
329 }
330
331 #[automatically_derived]
332 impl ::std::borrow::Borrow<str> for $name {
333 #[inline]
334 fn borrow(&self) -> &str {
335 self.as_str()
336 }
337 }
338
339 #[automatically_derived]
340 impl ::std::ops::Deref for $name {
341 type Target = $ref_name;
342 #[inline]
343 fn deref(&self) -> &Self::Target {
344 $ref_name::from_str(::std::convert::AsRef::as_ref(&self.0))
345 }
346 }
347
348 #[automatically_derived]
349 impl ::std::fmt::Debug for $name {
350 #[inline]
351 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
352 <$ref_name as ::std::fmt::Debug>::fmt(::std::ops::Deref::deref(self), f)
353 }
354 }
355
356 #[automatically_derived]
357 impl ::std::fmt::Display for $name {
358 #[inline]
359 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
360 <$ref_name as ::std::fmt::Display>::fmt(::std::ops::Deref::deref(self), f)
361 }
362 }
363
364 #[repr(transparent)]
365 #[derive(Hash, PartialEq, Eq, PartialOrd, Ord)]
366 $(#[$attr])*
367 pub struct $ref_name(str);
368
369 #[automatically_derived]
370 impl $ref_name {
371 #[allow(unsafe_code)]
372 #[inline]
373 #[doc = "Transparently reinterprets the string slice as a strongly-typed $ref_name"]
374 pub const fn from_str(raw: &str) -> &Self {
375 let ptr: *const str = raw;
376
377 // SAFETY: `$ref_name` is `#[repr(transparent)]` around a single `str` field, so a `*const str` can be safely reinterpreted as a `*const $ref_name`
378 unsafe { &*(ptr as *const Self) }
379 }
380
381 #[doc = r" Provides access to the underlying value as a string slice."]
382 #[inline]
383 pub const fn as_str(&self) -> &str {
384 &self.0
385 }
386 }
387
388 #[automatically_derived]
389 impl ::std::borrow::ToOwned for $ref_name {
390 type Owned = $name;
391 #[inline]
392 fn to_owned(&self) -> Self::Owned {
393 $name(self.0.into())
394 }
395 }
396
397 #[automatically_derived]
398 impl ::std::cmp::PartialEq<$ref_name> for $name {
399 #[inline]
400 fn eq(&self, other: &$ref_name) -> bool {
401 self.as_str() == other.as_str()
402 }
403 }
404
405 #[automatically_derived]
406 impl ::std::cmp::PartialEq<$name> for $ref_name {
407 #[inline]
408 fn eq(&self, other: &$name) -> bool {
409 self.as_str() == other.as_str()
410 }
411 }
412
413 #[automatically_derived]
414 impl ::std::cmp::PartialEq<&'_ $ref_name> for $name {
415 #[inline]
416 fn eq(&self, other: &&$ref_name) -> bool {
417 self.as_str() == other.as_str()
418 }
419 }
420
421 #[automatically_derived]
422 impl ::std::cmp::PartialEq<$name> for &'_ $ref_name {
423 #[inline]
424 fn eq(&self, other: &$name) -> bool {
425 self.as_str() == other.as_str()
426 }
427 }
428
429 #[automatically_derived]
430 impl<'a> ::std::convert::From<&'a str> for &'a $ref_name {
431 #[inline]
432 fn from(s: &'a str) -> &'a $ref_name {
433 $ref_name::from_str(s)
434 }
435 }
436
437 #[automatically_derived]
438 impl ::std::borrow::Borrow<str> for $ref_name {
439 #[inline]
440 fn borrow(&self) -> &str {
441 &self.0
442 }
443 }
444
445 #[automatically_derived]
446 impl ::std::convert::AsRef<str> for $ref_name {
447 #[inline]
448 fn as_ref(&self) -> &str {
449 &self.0
450 }
451 }
452
453 #[automatically_derived]
454 impl ::std::convert::From<&'_ $ref_name> for ::std::rc::Rc<$ref_name> {
455 #[allow(unsafe_code)]
456 #[inline]
457 fn from(r: &'_ $ref_name) -> Self {
458 // SAFETY: `$ref_name` is `#[repr(transparent)]` around a single `str` field, so a `*const str` can be safely reinterpreted as a `*const $ref_name`
459 let rc = ::std::rc::Rc::<str>::from(r.as_str());
460 unsafe { ::std::rc::Rc::from_raw(::std::rc::Rc::into_raw(rc) as *const $ref_name) }
461 }
462 }
463 #[automatically_derived]
464 impl ::std::convert::From<&'_ $ref_name> for ::std::sync::Arc<$ref_name> {
465 #[allow(unsafe_code)]
466 #[inline]
467 fn from(r: &'_ $ref_name) -> Self {
468 // SAFETY: `$ref_name` is `#[repr(transparent)]` around a single `str` field, so a `*const str` can be safely reinterpreted as a `*const $ref_name`
469 let arc = ::std::sync::Arc::<str>::from(r.as_str());
470 unsafe {
471 ::std::sync::Arc::from_raw(::std::sync::Arc::into_raw(arc) as *const $ref_name)
472 }
473 }
474 }
475
476 #[automatically_derived]
477 impl ::std::fmt::Debug for $ref_name {
478 #[inline]
479 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
480 <str as ::std::fmt::Debug>::fmt(&self.0, f)
481 }
482 }
483
484 #[automatically_derived]
485 impl ::std::fmt::Display for $ref_name {
486 #[inline]
487 fn fmt(&self, f: &mut ::std::fmt::Formatter) -> ::std::fmt::Result {
488 <str as ::std::fmt::Display>::fmt(&self.0, f)
489 }
490 }
491 )+
492 };
493}