xso-proc: improve error messages for codec = .. parsing hack
Previously, if you put `codec = FixedHex<20>.filtered(..)`, it would cause a confusing "expected `,`" message at the place of the `.`. This code adds a helpful "try adding a `::` before the `<`" message pointing at the `<` in the type path.
This commit is contained in:
parent
2fe3c0cef2
commit
a20caf839f
1 changed files with 67 additions and 21 deletions
|
|
@ -345,7 +345,11 @@ impl XmlCompoundMeta {
|
||||||
/// prefix.
|
/// prefix.
|
||||||
///
|
///
|
||||||
/// This does not advance the parse stream.
|
/// This does not advance the parse stream.
|
||||||
fn maybe_type_path(p: parse::ParseStream<'_>) -> bool {
|
///
|
||||||
|
/// If the tokens *do* look like a type path, a Span which points at the first
|
||||||
|
/// `<` encountered is returned. This can be used for a helpful error message
|
||||||
|
/// in case parsing the type path does then fail.
|
||||||
|
fn maybe_type_path(p: parse::ParseStream<'_>) -> (bool, Option<Span>) {
|
||||||
// ParseStream cursors do not advance the stream, but they are also rather
|
// ParseStream cursors do not advance the stream, but they are also rather
|
||||||
// unwieldly to use. Prepare for a lot of `let .. = ..`.
|
// unwieldly to use. Prepare for a lot of `let .. = ..`.
|
||||||
|
|
||||||
|
|
@ -368,21 +372,21 @@ fn maybe_type_path(p: parse::ParseStream<'_>) -> bool {
|
||||||
// Here we look for the identifier, but we do not care for its
|
// Here we look for the identifier, but we do not care for its
|
||||||
// contents.
|
// contents.
|
||||||
let Some((_, new_cursor)) = cursor.ident() else {
|
let Some((_, new_cursor)) = cursor.ident() else {
|
||||||
return false;
|
return (false, None);
|
||||||
};
|
};
|
||||||
cursor = new_cursor;
|
cursor = new_cursor;
|
||||||
|
|
||||||
// Now we see what actually follows the ident (it must be punctuation
|
// Now we see what actually follows the ident (it must be punctuation
|
||||||
// for it to be a type path...)
|
// for it to be a type path...)
|
||||||
let Some((punct, new_cursor)) = cursor.punct() else {
|
let Some((punct, new_cursor)) = cursor.punct() else {
|
||||||
return false;
|
return (false, None);
|
||||||
};
|
};
|
||||||
cursor = new_cursor;
|
cursor = new_cursor;
|
||||||
|
|
||||||
match punct.as_char() {
|
match punct.as_char() {
|
||||||
// Looks like a `foo<..`, we treat that as a type path for the
|
// Looks like a `foo<..`, we treat that as a type path for the
|
||||||
// reasons stated in [`parse_codec_expr`]'s doc.
|
// reasons stated in [`parse_codec_expr`]'s doc.
|
||||||
'<' => return true,
|
'<' => return (true, Some(punct.span())),
|
||||||
|
|
||||||
// Continue looking ahead: looks like a path separator.
|
// Continue looking ahead: looks like a path separator.
|
||||||
':' => (),
|
':' => (),
|
||||||
|
|
@ -390,18 +394,18 @@ fn maybe_type_path(p: parse::ParseStream<'_>) -> bool {
|
||||||
// Anything else (such as `,` (separating another argument most
|
// Anything else (such as `,` (separating another argument most
|
||||||
// likely), or `.` (a method call?)) we treat as "not a type
|
// likely), or `.` (a method call?)) we treat as "not a type
|
||||||
// path".
|
// path".
|
||||||
_ => return false,
|
_ => return (false, None),
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we are here, we saw a `:`. Look for the second one.
|
// If we are here, we saw a `:`. Look for the second one.
|
||||||
let Some((punct, new_cursor)) = cursor.punct() else {
|
let Some((punct, new_cursor)) = cursor.punct() else {
|
||||||
return false;
|
return (false, None);
|
||||||
};
|
};
|
||||||
cursor = new_cursor;
|
cursor = new_cursor;
|
||||||
|
|
||||||
if punct.as_char() != ':' {
|
if punct.as_char() != ':' {
|
||||||
// If it is not another `:`, it cannot be a type path.
|
// If it is not another `:`, it cannot be a type path.
|
||||||
return false;
|
return (false, None);
|
||||||
}
|
}
|
||||||
|
|
||||||
// And round and round and round it goes.
|
// And round and round and round it goes.
|
||||||
|
|
@ -426,9 +430,21 @@ fn maybe_type_path(p: parse::ParseStream<'_>) -> bool {
|
||||||
/// We however know that `Foo < Bar` is never a valid expression for a type.
|
/// We however know that `Foo < Bar` is never a valid expression for a type.
|
||||||
/// Thus, we can be smart about this and inject the `::` at the right place
|
/// Thus, we can be smart about this and inject the `::` at the right place
|
||||||
/// automatically.
|
/// automatically.
|
||||||
fn parse_codec_expr(p: parse::ParseStream<'_>) -> Result<Expr> {
|
fn parse_codec_expr(p: parse::ParseStream<'_>) -> Result<(Expr, Option<Error>)> {
|
||||||
if maybe_type_path(p) {
|
let (maybe_type_path, punct_span) = maybe_type_path(p);
|
||||||
let mut type_path: TypePath = p.parse()?;
|
if maybe_type_path {
|
||||||
|
let helpful_error =
|
||||||
|
punct_span.map(|span| Error::new(span, "help: try inserting a `::` before this `<`"));
|
||||||
|
let mut type_path: TypePath = match p.parse() {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(mut e) => match helpful_error {
|
||||||
|
Some(help) => {
|
||||||
|
e.combine(help);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
None => return Err(e),
|
||||||
|
},
|
||||||
|
};
|
||||||
// We got a type path -- so we now inject the `::` before any `<` as
|
// We got a type path -- so we now inject the `::` before any `<` as
|
||||||
// needed.
|
// needed.
|
||||||
for segment in type_path.path.segments.iter_mut() {
|
for segment in type_path.path.segments.iter_mut() {
|
||||||
|
|
@ -444,13 +460,16 @@ fn parse_codec_expr(p: parse::ParseStream<'_>) -> Result<Expr> {
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(Expr::Path(ExprPath {
|
Ok((
|
||||||
|
Expr::Path(ExprPath {
|
||||||
attrs: Vec::new(),
|
attrs: Vec::new(),
|
||||||
qself: type_path.qself,
|
qself: type_path.qself,
|
||||||
path: type_path.path,
|
path: type_path.path,
|
||||||
}))
|
}),
|
||||||
|
helpful_error,
|
||||||
|
))
|
||||||
} else {
|
} else {
|
||||||
p.parse()
|
p.parse().map(|x| (x, None))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -610,18 +629,45 @@ impl XmlFieldMeta {
|
||||||
|
|
||||||
/// Parse a `#[xml(text)]` meta.
|
/// Parse a `#[xml(text)]` meta.
|
||||||
fn text_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
fn text_from_meta(meta: ParseNestedMeta<'_>) -> Result<Self> {
|
||||||
let mut codec: Option<Expr> = None;
|
|
||||||
if meta.input.peek(Token![=]) {
|
if meta.input.peek(Token![=]) {
|
||||||
Ok(Self::Text {
|
let (codec, helpful_error) = parse_codec_expr(meta.value()?)?;
|
||||||
codec: Some(parse_codec_expr(meta.value()?)?),
|
// A meta value can only be followed by either a `,`, or the end
|
||||||
})
|
// of the parse stream (because of the delimited group ending).
|
||||||
|
// Hence we check we are there. And if we are *not* there, we emit
|
||||||
|
// an error straight away, with the helpful addition from the
|
||||||
|
// `parse_codec_expr` if we have it.
|
||||||
|
//
|
||||||
|
// If we do not do this, the user gets a rather confusing
|
||||||
|
// "expected `,`" message if the `maybe_type_path` guess was
|
||||||
|
// wrong.
|
||||||
|
let lookahead = meta.input.lookahead1();
|
||||||
|
if !lookahead.peek(Token![,]) && !meta.input.is_empty() {
|
||||||
|
if let Some(helpful_error) = helpful_error {
|
||||||
|
let mut e = lookahead.error();
|
||||||
|
e.combine(helpful_error);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Self::Text { codec: Some(codec) })
|
||||||
} else if meta.input.peek(syn::token::Paren) {
|
} else if meta.input.peek(syn::token::Paren) {
|
||||||
|
let mut codec: Option<Expr> = None;
|
||||||
meta.parse_nested_meta(|meta| {
|
meta.parse_nested_meta(|meta| {
|
||||||
if meta.path.is_ident("codec") {
|
if meta.path.is_ident("codec") {
|
||||||
if codec.is_some() {
|
if codec.is_some() {
|
||||||
return Err(Error::new_spanned(meta.path, "duplicate `codec` key"));
|
return Err(Error::new_spanned(meta.path, "duplicate `codec` key"));
|
||||||
}
|
}
|
||||||
codec = Some(parse_codec_expr(meta.value()?)?);
|
let (new_codec, helpful_error) = parse_codec_expr(meta.value()?)?;
|
||||||
|
// See above (at the top-ish of this function) for why we
|
||||||
|
// do this.
|
||||||
|
let lookahead = meta.input.lookahead1();
|
||||||
|
if !lookahead.peek(Token![,]) && !meta.input.is_empty() {
|
||||||
|
if let Some(helpful_error) = helpful_error {
|
||||||
|
let mut e = lookahead.error();
|
||||||
|
e.combine(helpful_error);
|
||||||
|
return Err(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
codec = Some(new_codec);
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(Error::new_spanned(meta.path, "unsupported key"))
|
Err(Error::new_spanned(meta.path, "unsupported key"))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue