-
Notifications
You must be signed in to change notification settings - Fork 116
/
generics.rs
172 lines (156 loc) · 6.22 KB
/
generics.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use syn::{
GenericArgument, GenericParam, Generics, ItemStruct, PathArguments, Type, TypeGroup,
TypeReference, TypeSlice, TypeTuple,
};
use crate::{attr::StructAttr, deps::Dependencies};
/// formats the generic arguments (like A, B in struct X<A, B>{..}) as "<X>" where x is a comma
/// seperated list of generic arguments, or an empty string if there are no type generics (lifetime/const generics are ignored).
/// this expands to an expression which evaluates to a `String`.
///
/// If a default type arg is encountered, it will be added to the dependencies.
pub fn format_generics(deps: &mut Dependencies, generics: &Generics) -> TokenStream {
let mut expanded_params = generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Type(type_param) => Some({
let ty = type_param.ident.to_string();
if let Some(default) = &type_param.default {
let default = format_type(default, deps, generics);
quote!(format!("{} = {}", #ty, #default))
} else {
quote!(#ty.to_owned())
}
}),
_ => None,
})
.peekable();
if expanded_params.peek().is_none() {
return quote!("");
}
let comma_separated = quote!([#(#expanded_params),*].join(", "));
quote!(format!("<{}>", #comma_separated))
}
pub fn format_type(ty: &Type, dependencies: &mut Dependencies, generics: &Generics) -> TokenStream {
// If the type matches one of the generic parameters, just pass the identifier:
if let Some(generic) = generics
.params
.iter()
.filter_map(|param| match param {
GenericParam::Type(type_param) => Some(type_param),
_ => None,
})
.find(|type_param| {
matches!(
ty,
Type::Path(type_path)
if type_path.qself.is_none()
&& type_path.path.is_ident(&type_param.ident)
)
})
{
let generic_ident = generic.ident.clone();
let generic_ident_str = generic_ident.to_string();
if !generic.bounds.is_empty() {
return quote!(#generic_ident_str.to_owned());
}
return quote!(
match <#generic_ident>::inline().as_str() {
// When exporting a generic, the default type used is `()`,
// which gives "null" when calling `.name()`. In this case, we
// want to preserve the type param's identifier as the name used
"null" => #generic_ident_str.to_owned(),
// If name is not "null", a type has been provided, so we use its
// name instead
x => x.to_owned()
}
);
}
// special treatment for arrays and tuples
match ty {
// Arrays have their own implementation that needs to be handle separetly
// be cause the T in `[T; N]` is technically not a generic
Type::Array(type_array) => {
let formatted = format_type(&type_array.elem, dependencies, generics);
return quote!(<#type_array>::name_with_type_args(vec![#formatted]));
}
// The field is a slice (`[T]`) so it technically doesn't have a
// generic argument. Therefore, we handle it explicitly here like a `Vec<T>`
Type::Slice(TypeSlice { ref elem, .. }) => {
let inner_ty = elem;
let vec_ty = syn::parse2::<Type>(quote!(Vec::<#inner_ty>)).unwrap();
return format_type(&vec_ty, dependencies, generics);
}
// same goes for a tuple (`(A, B, C)`) - it doesn't have a type arg, so we handle it
// explicitly here.
Type::Tuple(tuple) => {
if tuple.elems.is_empty() {
// empty tuples `()` should be treated as `null`
return super::unit::null(&StructAttr::default(), "")
.unwrap()
.inline;
}
// we convert the tuple field to a struct: `(A, B, C)` => `struct A(A, B, C)`
let tuple_struct = super::type_def(
&StructAttr::default(),
&format_ident!("_"),
&tuple_type_to_tuple_struct(tuple).fields,
generics,
)
.unwrap();
// now, we return the inline definition
dependencies.append(tuple_struct.dependencies);
return tuple_struct.inline;
}
Type::Reference(syn::TypeReference { ref elem, .. }) => {
return format_type(elem, dependencies, generics)
}
_ => (),
};
dependencies.push_or_append_from(ty);
match extract_type_args(ty) {
None => quote!(<#ty as ts_rs::TS>::name()),
Some(type_args) => {
let args = type_args
.iter()
.map(|ty| format_type(ty, dependencies, generics))
.collect::<Vec<_>>();
let args = quote!(vec![#(#args),*]);
quote!(<#ty as ts_rs::TS>::name_with_type_args(#args))
}
}
}
fn extract_type_args(ty: &Type) -> Option<Vec<&Type>> {
let last_segment = match ty {
Type::Group(TypeGroup { elem, .. }) | Type::Reference(TypeReference { elem, .. }) => {
return extract_type_args(elem)
}
Type::Path(type_path) => type_path.path.segments.last(),
_ => None,
}?;
let segment_arguments = match &last_segment.arguments {
PathArguments::AngleBracketed(generic_arguments) => Some(generic_arguments),
_ => None,
}?;
let type_args: Vec<_> = segment_arguments
.args
.iter()
.filter_map(|arg| match arg {
GenericArgument::Type(ty) => Some(ty),
_ => None,
})
.collect();
if type_args.is_empty() {
return None;
}
Some(type_args)
}
// convert a [`TypeTuple`], e.g `(A, B, C)`
// to a [`ItemStruct`], e.g `struct A(A, B, C)`
fn tuple_type_to_tuple_struct(tuple: &TypeTuple) -> ItemStruct {
let elements = tuple.elems.iter();
syn::parse2(quote!(struct A( #(#elements),* );))
.expect("could not convert tuple to tuple struct")
}