Skip to main content

syn_path/
lib.rs

1#![allow(clippy::tabs_in_doc_comments)]
2#![warn(rust_2018_idioms, unreachable_pub)]
3#![deny(elided_lifetimes_in_paths)]
4#![forbid(unsafe_code)]
5
6//! This crate contains macros to construct [`syn`]-types that contain paths inside a
7//! procedural macro.
8//!
9//!  - The [`path!`] macro constructs a [`syn::Path`].
10//!
11//!    **Example:**
12//!
13//!    ```
14//!    use syn_path::path;
15//!    let path = path!(::std::option::Option<::std::string::String>);
16//!    # assert_eq!(path, syn::parse_str("::std::option::Option<::std::string::String>").unwrap());
17//!    ```
18//!
19//!  - The [`type_path!`] macro constructs a [`syn::TypePath`].
20//!
21//!    **Example:**
22//!
23//!    ```
24//!    use syn_path::type_path;
25//!    let type_path = type_path!(<i64 as ::std::str::FromStr>::Err);
26//!    # assert_eq!(type_path, syn::parse_str("<i64 as ::std::str::FromStr>::Err").unwrap());
27//!    ```
28//!
29//!  - The [`ty!`] macro constructs a [`syn::Type`].
30//!
31//!    **Example:**
32//!
33//!    ```
34//!    use syn_path::ty;
35//!    let ty = ty!(<i64 as ::std::str::FromStr>::Err);
36//!    # assert_eq!(ty, syn::parse_str("<i64 as ::std::str::FromStr>::Err").unwrap());
37//!    ```
38//!
39//! While we can just type whatever we need into [`quote!`] most of the time when writing
40//! procedural macros, sometimes we need a certain syn type. The macros from this crate
41//! help you out in these situations.
42//!
43//! ## Example: Making a type optional
44//!
45//! Some derive macros might need to transform a type to an optional type. For example,
46//! this function takes a [`Field`](syn::Field) and returns its type or an option of its
47//! type based on the `nullable` parameter:
48//!
49//! ```
50//! # use syn::*; use syn::punctuated::Punctuated; use quote::quote;
51//! use syn_path::type_path;
52//!
53//! fn field_ty(field: &Field, nullable: bool) -> Type {
54//! 	let mut ty = field.ty.clone();
55//! 	if nullable {
56//! 		let mut args = Punctuated::new();
57//! 		args.push(GenericArgument::Type(ty));
58//! 		let mut type_path = type_path!(::core::option::Option);
59//! 		type_path.path.segments.last_mut().unwrap().arguments = PathArguments::AngleBracketed(
60//! 			AngleBracketedGenericArguments {
61//! 				colon2_token: None,
62//! 				lt_token: Default::default(),
63//! 				args,
64//! 				gt_token: Default::default()
65//! 			}
66//! 		);
67//! 		ty = Type::Path(type_path);
68//! 	}
69//! 	ty
70//! }
71//!
72//! # let strukt: ItemStruct = parse_str("struct Foo { x: String }").unwrap();
73//! # let Fields::Named(fields) = strukt.fields else { unreachable!() };
74//! let field = // x: String
75//! # fields.named.first().unwrap();
76//! let ty = field_ty(field, true);
77//! assert_eq!(ty, syn::parse2(quote!(::core::option::Option<String>)).unwrap());
78//! ```
79//!
80//! This example is adopted from the
81//! [`openapi_type_derive`](https://crates.io/crates/openapi_type_derive)
82//! crate. The full example can be found
83//! [here](https://github.com/msrd0/openapi_type/blob/6ba01686a0a8b782dd2bfba711bbe5f50ddfdb08/derive/src/parser.rs#L98-L110).
84//!
85//! ## Example: Adding a where clause
86//!
87//! This example shows how to add `T: Send` clauses for each field of a struct. We cannot
88//! just write `where T: Send` into [`quote!`] since there might or might
89//! not be a where clause for the struct our derive macro received as an input.
90//!
91//! ```
92//! # use syn::*; use quote::quote;
93//! use syn_path::path;
94//!
95//! fn where_predicate_t_send(t: Type) -> WherePredicate {
96//! 	WherePredicate::Type(PredicateType {
97//! 		attrs: Vec::new(),
98//! 		lifetimes: None,
99//! 		bounded_ty: t,
100//! 		colon_token: Default::default(),
101//! 		bounds: [TypeParamBound::Trait(TraitBound {
102//! 			paren_token: None,
103//! 			modifiers: TraitBoundModifiers::default(),
104//! 			lifetimes: None,
105//! 			maybe: None,
106//! 			path: path!(::std::marker::Send)
107//! 		})].into_iter().collect()
108//! 	})
109//! }
110//!
111//! let strukt =
112//! # parse_str::<ItemStruct>("
113//! 	struct Foo<T> where T: Hash + Eq { foo: HashSet<T> }
114//! # ").unwrap();
115//! let ident = strukt.ident;
116//! let (impl_generics, ty_generics, where_clause) = strukt.generics.split_for_impl();
117//! let mut where_clause = where_clause.cloned().unwrap_or(WhereClause {
118//! 	where_token: Default::default(),
119//! 	predicates: Default::default()
120//! });
121//! for field in strukt.fields {
122//! 	where_clause.predicates.push(where_predicate_t_send(field.ty));
123//! }
124//! assert_eq!(
125//! 	quote!(impl #impl_generics MyTrait for #ident #ty_generics #where_clause {}).to_string(),
126//! 	quote!(impl<T> MyTrait for Foo<T> where T: Hash + Eq, HashSet<T>: ::std::marker::Send {}).to_string()
127//! )
128//! ```
129//!
130//!  [`quote!`]: https://docs.rs/quote/1/quote/macro.quote.html
131
132#[doc(hidden)]
133pub mod private {
134	pub use proc_macro2::{Ident, Span};
135	pub use std::{
136		boxed::Box,
137		option::Option::{None, Some},
138		stringify
139	};
140	pub use syn::{
141		punctuated::Punctuated, AngleBracketedGenericArguments, GenericArgument, Path,
142		PathArguments, PathSegment, QSelf, Type, TypePath
143	};
144
145	#[inline]
146	pub fn default<T: Default>() -> T {
147		T::default()
148	}
149
150	#[inline]
151	pub const fn len<T, const LEN: usize>(_: &[T; LEN]) -> usize {
152		LEN
153	}
154}
155
156/// This macro takes type paths of the form `my_crate::my_mod::FooBar` and
157/// `<my_crate::my_mod::FooBar as my_crate::my_mod::MyTrait>::MyType` and
158/// turns them into a [`syn::Type`].
159#[macro_export]
160macro_rules! ty {
161	($($body:tt)*) => {
162		$crate::private::Type::Path($crate::type_path!($($body)*))
163	};
164}
165
166/// This macro takes type paths of the form `my_crate::my_mod::FooBar` and
167/// `<my_crate::my_mod::FooBar as my_crate::my_mod::MyTrait>::MyType` and
168/// turns them into a [`syn::TypePath`].
169#[macro_export]
170macro_rules! type_path {
171	(:: $($body:tt)*) => {
172		$crate::type_path_impl!($crate::private::None, $crate::path!(:: $($body)*))
173	};
174	($segment:ident $($body:tt)*) => {
175		$crate::type_path_impl!($crate::private::None, $crate::path!($segment $($body)*))
176	};
177
178	(< $($ty_segment:ident)::* > :: $($body:tt)*) => {
179		$crate::type_path_impl!(
180			$crate::private::Some($crate::private::QSelf {
181				lt_token: $crate::private::default(),
182				ty: Box::new($crate::ty!($($ty_segment)::*)),
183				position: 0,
184				as_token: $crate::private::None,
185				gt_token: $crate::private::default()
186			}),
187			$crate::path!(:: $($body)*)
188		)
189	};
190	(< :: $($ty_segment:ident)::* > :: $($body:tt)*) => {
191		$crate::type_path_impl!(
192			$crate::private::Some($crate::private::QSelf {
193				lt_token: $crate::private::default(),
194				ty: Box::new($crate::ty!(:: $($ty_segment)::*)),
195				position: 0,
196				as_token: $crate::private::None,
197				gt_token: $crate::private::default()
198			}),
199			$crate::path!(:: $($body)*)
200		)
201	};
202
203	(< $($ty_segment:ident)::* as $($as_segment:ident)::* > :: $($body:tt)*) => {
204		$crate::type_path_impl!(
205			$crate::private::Some($crate::private::QSelf {
206				lt_token: $crate::private::default(),
207				ty: Box::new($crate::ty!($($ty_segment)::*)),
208				position: $crate::private::len(&[$($crate::private::stringify!($as_segment)),*]),
209				as_token: $crate::private::Some($crate::private::default()),
210				gt_token: $crate::private::default()
211			}),
212			$crate::path!($($as_segment)::* :: $($body)*)
213		)
214	};
215	(< :: $($ty_segment:ident)::* as $($as_segment:ident)::* > :: $($body:tt)*) => {
216		$crate::type_path_impl!(
217			$crate::private::Some($crate::private::QSelf {
218				lt_token: $crate::private::default(),
219				ty: Box::new($crate::ty!(:: $($ty_segment)::*)),
220				position: $crate::private::len(&[$($crate::private::stringify!($as_segment)),*]),
221				as_token: $crate::private::Some($crate::private::default()),
222				gt_token: $crate::private::default()
223			}),
224			$crate::path!($($as_segment)::* :: $($body)*)
225		)
226	};
227	(< $($ty_segment:ident)::* as :: $($as_segment:ident)::* > :: $($body:tt)*) => {
228		$crate::type_path_impl!(
229			$crate::private::Some($crate::private::QSelf {
230				lt_token: $crate::private::default(),
231				ty: Box::new($crate::ty!($($ty_segment)::*)),
232				position: $crate::private::len(&[$($crate::private::stringify!($as_segment)),*]),
233				as_token: $crate::private::Some($crate::private::default()),
234				gt_token: $crate::private::default()
235			}),
236			$crate::path!(:: $($as_segment)::* :: $($body)*)
237		)
238	};
239	(< :: $($ty_segment:ident)::* as :: $($as_segment:ident)::* > :: $($body:tt)*) => {
240		$crate::type_path_impl!(
241			$crate::private::Some($crate::private::QSelf {
242				lt_token: $crate::private::default(),
243				ty: Box::new($crate::ty!(:: $($ty_segment)::*)),
244				position: $crate::private::len(&[$($crate::private::stringify!($as_segment)),*]),
245				as_token: $crate::private::Some($crate::private::default()),
246				gt_token: $crate::private::default()
247			}),
248			$crate::path!(:: $($as_segment)::* :: $($body)*)
249		)
250	};
251}
252
253#[macro_export]
254#[doc(hidden)]
255macro_rules! type_path_impl {
256	($qself:expr, $path:expr) => {
257		$crate::private::TypePath {
258			attrs: $crate::private::default(),
259			qself: $qself,
260			path: $path
261		}
262	};
263}
264
265/// This macro takes paths of the form `my_crate::my_mod::FooBar` and
266/// `::my_crate::my_mod::FooBar` and turns them into a [`syn::Path`].
267#[macro_export]
268macro_rules! path {
269	(:: $($segment:ident)::*) => {
270		$crate::path_impl!(
271			$crate::private::Some($crate::private::default()),
272			$crate::private::PathArguments::None,
273			$($segment),*
274		)
275	};
276	($($segment:ident)::*) => {
277		$crate::path_impl!(
278			$crate::private::None,
279			$crate::private::PathArguments::None,
280			$($segment),*
281		)
282	};
283	// note: the last $tt will capture the '>'
284	(:: $($segment:ident)::* < $($args:tt)*) => {
285		$crate::path_impl!(
286			$crate::private::Some($crate::private::default()),
287			$crate::path_args_impl!($crate::private::default(), $crate::private::None, $($args)*),
288			$($segment),*
289		)
290	};
291	($($segment:ident)::* < $($args:tt)*) => {
292		$crate::path_impl!(
293			$crate::private::None,
294			$crate::path_args_impl!($crate::private::default(), $crate::private::None, $($args)*),
295			$($segment),*
296		)
297	};
298}
299
300#[macro_export]
301#[doc(hidden)]
302macro_rules! path_impl {
303	($leading_colon:expr, $args:expr, $($segment:ident),*) => {
304		{
305			#[allow(unused_mut)]
306			let mut segments: $crate::private::Punctuated<$crate::private::PathSegment, _> = $crate::private::default();
307			$(
308				segments.push($crate::private::PathSegment {
309					ident: $crate::private::Ident::new(
310						$crate::private::stringify!($segment),
311						$crate::private::Span::call_site()
312					),
313					arguments: $crate::private::PathArguments::None
314				});
315			)*
316			if let Some(last_segment) = segments.last_mut() {
317				last_segment.arguments = $args;
318			}
319			$crate::private::Path {
320				leading_colon: $leading_colon,
321				segments
322			}
323		}
324	};
325}
326
327#[macro_export]
328#[doc(hidden)]
329macro_rules! path_args_impl {
330	// end of recursion: no tokens at all. the '>' was captured by the path! macro.
331	($args:expr, $leading_colon:expr, $(>)?) => {
332		$crate::private::PathArguments::AngleBracketed(
333			$crate::private::AngleBracketedGenericArguments {
334				colon2_token: $leading_colon,
335				lt_token: $crate::private::default(),
336				args: $args,
337				gt_token: $crate::private::default()
338			}
339		)
340	};
341	// match first argument: no following tokens, but the '>' was captured by the path! macro.
342	($args:expr, $leading_colon:expr, :: $($segment:ident)::* $(>)?) => {
343		$crate::path_args_impl!(
344			{
345				let mut args: $crate::private::Punctuated<$crate::private::GenericArgument, _> = $args;
346				args.push($crate::private::GenericArgument::Type($crate::ty!(:: $($segment)::*)));
347				args
348			},
349			$leading_colon,
350		)
351	};
352	($args:expr, $leading_colon:expr, $($segment:ident)::* $(>)?) => {
353		$crate::path_args_impl!(
354			{
355				let mut args: $crate::private::Punctuated<$crate::private::GenericArgument, _> = $args;
356				args.push($crate::private::GenericArgument::Type($crate::ty!($($segment)::*)));
357				args
358			},
359			$leading_colon,
360		)
361	};
362	// match first argument: following tokens, last will capture the '>'
363	($args:expr, $leading_colon:expr, :: $($segment:ident)::* $(, $($body:tt)*)?) => {
364		$crate::path_args_impl!(
365			{
366				let mut args: $crate::private::Punctuated<$crate::private::GenericArgument, _> = $args;
367				args.push($crate::private::GenericArgument::Type($crate::ty!(:: $($segment)::*)));
368				args
369			},
370			$leading_colon,
371			$($($body)*)?
372		)
373	};
374	($args:expr, $leading_colon:expr, $($segment:ident)::* $(, $($body:tt)*)?) => {
375		$crate::path_args_impl!(
376			{
377				let mut args: $crate::private::Punctuated<$crate::private::GenericArgument, _> = $args;
378				args.push($crate::private::GenericArgument::Type($crate::ty!($($segment)::*)));
379				args
380			},
381			$leading_colon,
382			$($($body)*)?
383		)
384	};
385}
386
387#[cfg(test)]
388mod tests {
389	use std::fmt::Debug;
390	use syn::parse::Parse;
391
392	#[track_caller]
393	fn assert_eq<T>(t: T, s: &str)
394	where
395		T: Debug + Eq + Parse
396	{
397		let expected: T = syn::parse_str(s).unwrap();
398		// assert_eq!(expected, t);
399		if expected != t {
400			panic!(
401				r#"assertion `left == right` failed
402  left: {expected:#?}
403 right: {t:#?}"#
404			)
405		}
406	}
407
408	// ### ty! macro tests
409
410	#[test]
411	fn type_with_leading_colon() {
412		assert_eq(
413			ty!(::my_crate::my_mod::FooBar),
414			"::my_crate::my_mod::FooBar"
415		);
416	}
417
418	#[test]
419	fn type_without_leading_colon() {
420		assert_eq(ty!(my_crate::my_mod::FooBar), "my_crate::my_mod::FooBar");
421	}
422
423	#[test]
424	fn type_with_qself_with_leading_colon() {
425		assert_eq(
426			ty!(<::my_crate::my_mod::FooBar>::MyType),
427			"<::my_crate::my_mod::FooBar>::MyType"
428		);
429	}
430
431	#[test]
432	fn type_with_qself_without_leading_colon() {
433		assert_eq(
434			ty!(<my_crate::my_mod::FooBar>::MyType),
435			"<my_crate::my_mod::FooBar>::MyType"
436		);
437	}
438
439	#[test]
440	fn type_with_qself_with_leading_colon_with_as_with_leading_colon() {
441		assert_eq(
442			ty!(<::my_crate::my_mod::FooBar as ::my_crate::my_mod::MyTrait>::MyType),
443			"<::my_crate::my_mod::FooBar as ::my_crate::my_mod::MyTrait>::MyType"
444		);
445	}
446
447	#[test]
448	fn type_with_qself_with_leading_colon_with_as_without_leading_colon() {
449		assert_eq(
450			ty!(<::my_crate::my_mod::FooBar as my_crate::my_mod::MyTrait>::MyType),
451			"<::my_crate::my_mod::FooBar as my_crate::my_mod::MyTrait>::MyType"
452		);
453	}
454
455	#[test]
456	fn type_with_qself_without_leading_colon_with_as_with_leading_colon() {
457		assert_eq(
458			ty!(<my_crate::my_mod::FooBar as ::my_crate::my_mod::MyTrait>::MyType),
459			"<my_crate::my_mod::FooBar as ::my_crate::my_mod::MyTrait>::MyType"
460		);
461	}
462
463	#[test]
464	fn type_with_qself_without_leading_colon_with_as_without_leading_colon() {
465		assert_eq(
466			ty!(<my_crate::my_mod::FooBar as my_crate::my_mod::MyTrait>::MyType),
467			"<my_crate::my_mod::FooBar as my_crate::my_mod::MyTrait>::MyType"
468		);
469	}
470
471	// ### type_path! macro tests
472
473	#[test]
474	fn type_path_with_leading_colon() {
475		assert_eq(
476			type_path!(::my_crate::my_mod::FooBar),
477			"::my_crate::my_mod::FooBar"
478		);
479	}
480
481	#[test]
482	fn type_path_without_leading_colon() {
483		assert_eq(
484			type_path!(my_crate::my_mod::FooBar),
485			"my_crate::my_mod::FooBar"
486		);
487	}
488
489	#[test]
490	fn type_path_with_qself_with_leading_colon() {
491		assert_eq(
492			type_path!(<::my_crate::my_mod::FooBar>::MyType),
493			"<::my_crate::my_mod::FooBar>::MyType"
494		);
495	}
496
497	#[test]
498	fn type_path_with_qself_without_leading_colon() {
499		assert_eq(
500			type_path!(<my_crate::my_mod::FooBar>::MyType),
501			"<my_crate::my_mod::FooBar>::MyType"
502		);
503	}
504
505	#[test]
506	fn type_path_with_qself_with_leading_colon_with_as_with_leading_colon() {
507		assert_eq(
508			type_path!(
509				<::my_crate::my_mod::FooBar as ::my_crate::my_mod::MyTrait>::MyType
510			),
511			"<::my_crate::my_mod::FooBar as ::my_crate::my_mod::MyTrait>::MyType"
512		);
513	}
514
515	#[test]
516	fn type_path_with_qself_with_leading_colon_with_as_without_leading_colon() {
517		assert_eq(
518			type_path!(<::my_crate::my_mod::FooBar as my_crate::my_mod::MyTrait>::MyType),
519			"<::my_crate::my_mod::FooBar as my_crate::my_mod::MyTrait>::MyType"
520		);
521	}
522
523	#[test]
524	fn type_path_with_qself_without_leading_colon_with_as_with_leading_colon() {
525		assert_eq(
526			type_path!(<my_crate::my_mod::FooBar as ::my_crate::my_mod::MyTrait>::MyType),
527			"<my_crate::my_mod::FooBar as ::my_crate::my_mod::MyTrait>::MyType"
528		);
529	}
530
531	#[test]
532	fn type_path_with_qself_without_leading_colon_with_as_without_leading_colon() {
533		assert_eq(
534			type_path!(<my_crate::my_mod::FooBar as my_crate::my_mod::MyTrait>::MyType),
535			"<my_crate::my_mod::FooBar as my_crate::my_mod::MyTrait>::MyType"
536		);
537	}
538
539	// ### path! macro tests
540
541	#[test]
542	fn path_with_leading_colon() {
543		assert_eq(
544			path!(::my_crate::my_mod::FooBar),
545			"::my_crate::my_mod::FooBar"
546		);
547	}
548
549	#[test]
550	fn path_without_leading_colon() {
551		assert_eq(path!(my_crate::my_mod::FooBar), "my_crate::my_mod::FooBar");
552	}
553
554	#[test]
555	fn path_with_leading_colon_with_args() {
556		assert_eq(
557			path!(::my_crate::my_mod::FooBar<::some::Argument, AnotherArgument>),
558			"::my_crate::my_mod::FooBar<::some::Argument, AnotherArgument>"
559		)
560	}
561
562	#[test]
563	fn path_without_leading_colon_with_args() {
564		assert_eq(
565			path!(my_crate::my_mod::FooBar<::some::Argument, AnotherArgument>),
566			"my_crate::my_mod::FooBar<::some::Argument, AnotherArgument>"
567		)
568	}
569}