Skip to content

Commit b4702a1

Browse files
committed
Migrate out from proc macro error
Implement `Result<T, E>` based proc macro error handling instead of out of fashion `proc_macro_error` that still is in _syn_ `1.0`. This allows us to remove the need for `proc_macro_error` crate and makes our dependency tree leaner. The error handling is implemented with custom `ToTokensDiagnostics` trait that is used instead of the `ToTokens` when there is a possibility for error. Error is passed up in the call stack via `Diagnostics` struct that converts to compile error token stream at root of the macro call. The result based approach is the recommended way of handling compile errors in proc macros. Resolves #854
1 parent 9f8ebf3 commit b4702a1

20 files changed

+1722
-1118
lines changed

utoipa-gen/Cargo.toml

-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,6 @@ proc-macro = true
1616
proc-macro2 = "1.0"
1717
syn = { version = "2.0", features = ["full", "extra-traits"] }
1818
quote = "1.0"
19-
proc-macro-error = "1.0"
2019
regex = { version = "1.7", optional = true }
2120
uuid = { version = "1", features = ["serde"], optional = true }
2221
ulid = { version = "1", optional = true, default-features = false }

utoipa-gen/src/component.rs

+151-106
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,14 @@
11
use std::borrow::Cow;
22

33
use proc_macro2::{Ident, Span, TokenStream};
4-
use proc_macro_error::{abort, abort_call_site};
54
use quote::{quote, quote_spanned, ToTokens};
65
use syn::spanned::Spanned;
76
use syn::{Attribute, GenericArgument, Path, PathArguments, PathSegment, Type, TypePath};
87

98
use crate::doc_comment::CommentAttributes;
109
use crate::schema_type::SchemaFormat;
1110
use crate::{schema_type::SchemaType, Deprecated};
11+
use crate::{Diagnostics, OptionExt};
1212

1313
use self::features::{
1414
pop_feature, Feature, FeaturesExt, IsInline, Minimum, Nullable, ToTokensExt, Validatable,
@@ -104,27 +104,31 @@ pub struct TypeTree<'t> {
104104
}
105105

106106
impl<'t> TypeTree<'t> {
107-
pub fn from_type(ty: &'t Type) -> TypeTree<'t> {
108-
Self::convert_types(Self::get_type_tree_values(ty))
109-
.next()
110-
.expect("TypeTree from type should have one TypeTree parent")
107+
pub fn from_type(ty: &'t Type) -> Result<TypeTree<'t>, Diagnostics> {
108+
Self::convert_types(Self::get_type_tree_values(ty)?).map(|mut type_tree| {
109+
type_tree
110+
.next()
111+
.expect("TypeTree from type should have one TypeTree parent")
112+
})
111113
}
112114

113-
fn get_type_tree_values(ty: &'t Type) -> Vec<TypeTreeValue> {
114-
match ty {
115+
fn get_type_tree_values(ty: &'t Type) -> Result<Vec<TypeTreeValue>, Diagnostics> {
116+
let type_tree_values = match ty {
115117
Type::Path(path) => {
116118
vec![TypeTreeValue::TypePath(path)]
117119
},
118-
Type::Reference(reference) => Self::get_type_tree_values(reference.elem.as_ref()),
120+
Type::Reference(reference) => Self::get_type_tree_values(reference.elem.as_ref())?,
119121
Type::Tuple(tuple) => {
120122
// Detect unit type ()
121-
if tuple.elems.is_empty() { return vec![TypeTreeValue::UnitType] }
122-
123-
vec![TypeTreeValue::Tuple(tuple.elems.iter().flat_map(Self::get_type_tree_values).collect(), tuple.span())]
123+
if tuple.elems.is_empty() { return Ok(vec![TypeTreeValue::UnitType]) }
124+
vec![TypeTreeValue::Tuple(
125+
tuple.elems.iter().map(Self::get_type_tree_values).collect::<Result<Vec<_>, Diagnostics>>()?.into_iter().flatten().collect(),
126+
tuple.span()
127+
)]
124128
},
125-
Type::Group(group) => Self::get_type_tree_values(group.elem.as_ref()),
126-
Type::Slice(slice) => vec![TypeTreeValue::Array(Self::get_type_tree_values(&slice.elem), slice.bracket_token.span.join())],
127-
Type::Array(array) => vec![TypeTreeValue::Array(Self::get_type_tree_values(&array.elem), array.bracket_token.span.join())],
129+
Type::Group(group) => Self::get_type_tree_values(group.elem.as_ref())?,
130+
Type::Slice(slice) => vec![TypeTreeValue::Array(Self::get_type_tree_values(&slice.elem)?, slice.bracket_token.span.join())],
131+
Type::Array(array) => vec![TypeTreeValue::Array(Self::get_type_tree_values(&array.elem)?, array.bracket_token.span.join())],
128132
Type::TraitObject(trait_object) => {
129133
trait_object
130134
.bounds
@@ -139,68 +143,83 @@ impl<'t> TypeTree<'t> {
139143
})
140144
.map(|path| vec![TypeTreeValue::Path(path)]).unwrap_or_else(Vec::new)
141145
}
142-
_ => abort_call_site!(
143-
"unexpected type in component part get type path, expected one of: Path, Tuple, Reference, Group, Array, Slice, TraitObject"
144-
),
145-
}
146+
unexpected => return Err(Diagnostics::with_span(unexpected.span(), "unexpected type in component part get type path, expected one of: Path, Tuple, Reference, Group, Array, Slice, TraitObject")),
147+
};
148+
149+
Ok(type_tree_values)
146150
}
147151

148-
fn convert_types(paths: Vec<TypeTreeValue<'t>>) -> impl Iterator<Item = TypeTree<'t>> {
149-
paths.into_iter().map(|value| {
150-
let path = match value {
151-
TypeTreeValue::TypePath(type_path) => &type_path.path,
152-
TypeTreeValue::Path(path) => path,
153-
TypeTreeValue::Array(value, span) => {
154-
let array: Path = Ident::new("Array", span).into();
155-
return TypeTree {
156-
path: Some(Cow::Owned(array)),
157-
span: Some(span),
158-
value_type: ValueType::Object,
159-
generic_type: Some(GenericType::Vec),
160-
children: Some(Self::convert_types(value).collect()),
161-
};
162-
}
163-
TypeTreeValue::Tuple(tuple, span) => {
164-
return TypeTree {
165-
path: None,
166-
span: Some(span),
167-
children: Some(Self::convert_types(tuple).collect()),
168-
generic_type: None,
169-
value_type: ValueType::Tuple,
152+
fn convert_types(
153+
paths: Vec<TypeTreeValue<'t>>,
154+
) -> Result<impl Iterator<Item = TypeTree<'t>>, Diagnostics> {
155+
paths
156+
.into_iter()
157+
.map(|value| {
158+
let path = match value {
159+
TypeTreeValue::TypePath(type_path) => &type_path.path,
160+
TypeTreeValue::Path(path) => path,
161+
TypeTreeValue::Array(value, span) => {
162+
let array: Path = Ident::new("Array", span).into();
163+
return Ok(TypeTree {
164+
path: Some(Cow::Owned(array)),
165+
span: Some(span),
166+
value_type: ValueType::Object,
167+
generic_type: Some(GenericType::Vec),
168+
children: Some(match Self::convert_types(value) {
169+
Ok(converted_values) => converted_values.collect(),
170+
Err(diagnostics) => return Err(diagnostics),
171+
}),
172+
});
170173
}
171-
}
172-
TypeTreeValue::UnitType => {
173-
return TypeTree {
174-
path: None,
175-
span: None,
176-
value_type: ValueType::Tuple,
177-
generic_type: None,
178-
children: None,
174+
TypeTreeValue::Tuple(tuple, span) => {
175+
return Ok(TypeTree {
176+
path: None,
177+
span: Some(span),
178+
children: Some(match Self::convert_types(tuple) {
179+
Ok(converted_values) => converted_values.collect(),
180+
Err(diagnostics) => return Err(diagnostics),
181+
}),
182+
generic_type: None,
183+
value_type: ValueType::Tuple,
184+
})
179185
}
180-
}
181-
};
186+
TypeTreeValue::UnitType => {
187+
return Ok(TypeTree {
188+
path: None,
189+
span: None,
190+
value_type: ValueType::Tuple,
191+
generic_type: None,
192+
children: None,
193+
})
194+
}
195+
};
182196

183-
// there will always be one segment at least
184-
let last_segment = path
185-
.segments
186-
.last()
187-
.expect("at least one segment within path in TypeTree::convert_types");
197+
// there will always be one segment at least
198+
let last_segment = path
199+
.segments
200+
.last()
201+
.expect("at least one segment within path in TypeTree::convert_types");
188202

189-
if last_segment.arguments.is_empty() {
190-
Self::convert(path, last_segment)
191-
} else {
192-
Self::resolve_schema_type(path, last_segment)
193-
}
194-
})
203+
if last_segment.arguments.is_empty() {
204+
Ok(Self::convert(path, last_segment))
205+
} else {
206+
Self::resolve_schema_type(path, last_segment)
207+
}
208+
})
209+
.collect::<Result<Vec<TypeTree<'t>>, Diagnostics>>()
210+
.map(IntoIterator::into_iter)
195211
}
196212

197213
// Only when type is a generic type we get to this function.
198-
fn resolve_schema_type(path: &'t Path, last_segment: &'t PathSegment) -> TypeTree<'t> {
214+
fn resolve_schema_type(
215+
path: &'t Path,
216+
last_segment: &'t PathSegment,
217+
) -> Result<TypeTree<'t>, Diagnostics> {
199218
if last_segment.arguments.is_empty() {
200-
abort!(
201-
last_segment.ident,
202-
"expected at least one angle bracket argument but was 0"
203-
);
219+
return Err(Diagnostics::with_span(
220+
last_segment.ident.span(),
221+
"expected at least one angle bracket argument but was 0",
222+
));
204223
};
205224

206225
let mut generic_schema_type = Self::convert(path, last_segment);
@@ -227,26 +246,32 @@ impl<'t> TypeTree<'t> {
227246
)
228247
})
229248
.map(|arg| match arg {
230-
GenericArgument::Type(arg) => arg,
231-
_ => abort!(
232-
arg,
233-
"expected generic argument type or generic argument lifetime"
234-
),
235-
}),
249+
GenericArgument::Type(arg) => Ok(arg),
250+
unexpected => Err(Diagnostics::with_span(
251+
unexpected.span(),
252+
"expected generic argument type or generic argument lifetime",
253+
)),
254+
})
255+
.collect::<Result<Vec<_>, Diagnostics>>()?
256+
.into_iter(),
236257
)
237258
}
238259
}
239-
_ => abort!(
240-
last_segment.ident,
241-
"unexpected path argument, expected angle bracketed path argument"
242-
),
260+
_ => {
261+
return Err(Diagnostics::with_span(
262+
last_segment.ident.span(),
263+
"unexpected path argument, expected angle bracketed path argument",
264+
))
265+
}
243266
};
244267

245-
generic_schema_type.children = generic_types
246-
.as_mut()
247-
.map(|generic_type| generic_type.map(Self::from_type).collect());
268+
generic_schema_type.children = generic_types.as_mut().map_try(|generic_type| {
269+
generic_type
270+
.map(Self::from_type)
271+
.collect::<Result<Vec<_>, Diagnostics>>()
272+
})?;
248273

249-
generic_schema_type
274+
Ok(generic_schema_type)
250275
}
251276

252277
fn convert(path: &'t Path, last_segment: &'t PathSegment) -> TypeTree<'t> {
@@ -489,6 +514,12 @@ impl<'c> ComponentSchema {
489514
let deprecated_stream = ComponentSchema::get_deprecated(deprecated);
490515
let description_stream = ComponentSchema::get_description(description);
491516

517+
let match_diagnostics =
518+
|result: Result<(), Diagnostics>, tokens: &mut TokenStream| match result {
519+
Err(diagnostics) => diagnostics.to_tokens(tokens),
520+
_ => (),
521+
};
522+
492523
match type_tree.generic_type {
493524
Some(GenericType::Map) => ComponentSchema::map_to_tokens(
494525
&mut tokens,
@@ -498,38 +529,50 @@ impl<'c> ComponentSchema {
498529
description_stream,
499530
deprecated_stream,
500531
),
501-
Some(GenericType::Vec) => ComponentSchema::vec_to_tokens(
532+
Some(GenericType::Vec) => match_diagnostics(
533+
ComponentSchema::vec_to_tokens(
534+
&mut tokens,
535+
features,
536+
type_tree,
537+
object_name,
538+
description_stream,
539+
deprecated_stream,
540+
),
502541
&mut tokens,
503-
features,
504-
type_tree,
505-
object_name,
506-
description_stream,
507-
deprecated_stream,
508542
),
509-
Some(GenericType::LinkedList) => ComponentSchema::vec_to_tokens(
543+
Some(GenericType::LinkedList) => match_diagnostics(
544+
ComponentSchema::vec_to_tokens(
545+
&mut tokens,
546+
features,
547+
type_tree,
548+
object_name,
549+
description_stream,
550+
deprecated_stream,
551+
),
510552
&mut tokens,
511-
features,
512-
type_tree,
513-
object_name,
514-
description_stream,
515-
deprecated_stream,
516553
),
517-
Some(GenericType::Set) => ComponentSchema::vec_to_tokens(
554+
Some(GenericType::Set) => match_diagnostics(
555+
ComponentSchema::vec_to_tokens(
556+
&mut tokens,
557+
features,
558+
type_tree,
559+
object_name,
560+
description_stream,
561+
deprecated_stream,
562+
),
518563
&mut tokens,
519-
features,
520-
type_tree,
521-
object_name,
522-
description_stream,
523-
deprecated_stream,
524564
),
525565
#[cfg(feature = "smallvec")]
526-
Some(GenericType::SmallVec) => ComponentSchema::vec_to_tokens(
566+
Some(GenericType::SmallVec) => match_diagnostics(
567+
ComponentSchema::vec_to_tokens(
568+
&mut tokens,
569+
features,
570+
type_tree,
571+
object_name,
572+
description_stream,
573+
deprecated_stream,
574+
),
527575
&mut tokens,
528-
features,
529-
type_tree,
530-
object_name,
531-
description_stream,
532-
deprecated_stream,
533576
),
534577
Some(GenericType::Option) => {
535578
// Add nullable feature if not already exists. Option is always nullable
@@ -658,9 +701,9 @@ impl<'c> ComponentSchema {
658701
object_name: &str,
659702
description_stream: Option<TokenStream>,
660703
deprecated_stream: Option<TokenStream>,
661-
) {
704+
) -> Result<(), Diagnostics> {
662705
let example = pop_feature!(features => Feature::Example(_));
663-
let xml = features.extract_vec_xml_feature(type_tree);
706+
let xml = features.extract_vec_xml_feature(type_tree)?;
664707
let max_items = pop_feature!(features => Feature::MaxItems(_));
665708
let min_items = pop_feature!(features => Feature::MinItems(_));
666709
let nullable = pop_feature!(features => Feature::Nullable(_));
@@ -753,6 +796,8 @@ impl<'c> ComponentSchema {
753796
example.to_tokens(tokens);
754797
xml.to_tokens(tokens);
755798
nullable.to_tokens(tokens);
799+
800+
Ok(())
756801
}
757802

758803
fn non_generic_to_tokens(

0 commit comments

Comments
 (0)