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}