Change span of as_dyn_error() to point compile error at attribute.

This commit is contained in:
Maarten de Vries 2023-10-19 15:04:40 +02:00
parent f4eac7ef7b
commit a49f7c603d
14 changed files with 209 additions and 8 deletions

View file

@ -23,14 +23,14 @@ fn impl_struct(input: Struct) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl(); let (impl_generics, ty_generics, where_clause) = input.generics.split_for_impl();
let mut error_inferred_bounds = InferredBounds::new(); let mut error_inferred_bounds = InferredBounds::new();
let source_body = if input.attrs.transparent.is_some() { let source_body = if let Some(transparent_attr) = &input.attrs.transparent {
let only_field = &input.fields[0]; let only_field = &input.fields[0];
if only_field.contains_generic { if only_field.contains_generic {
error_inferred_bounds.insert(only_field.ty, quote!(std::error::Error)); error_inferred_bounds.insert(only_field.ty, quote!(std::error::Error));
} }
let member = &only_field.member; let member = &only_field.member;
Some(quote! { Some(quote_spanned! {
std::error::Error::source(self.#member.as_dyn_error()) transparent_attr.span => std::error::Error::source(self.#member.as_dyn_error())
}) })
} else if let Some(source_field) = input.source_field() { } else if let Some(source_field) = input.source_field() {
let source = &source_field.member; let source = &source_field.member;
@ -43,7 +43,8 @@ fn impl_struct(input: Struct) -> TokenStream {
} else { } else {
None None
}; };
let dyn_error = quote_spanned!(source.span()=> self.#source #asref.as_dyn_error()); let dyn_error =
quote_spanned!(source_field.source_span() => self.#source #asref.as_dyn_error());
Some(quote! { Some(quote! {
::core::option::Option::Some(#dyn_error) ::core::option::Option::Some(#dyn_error)
}) })
@ -193,13 +194,13 @@ fn impl_enum(input: Enum) -> TokenStream {
let source_method = if input.has_source() { let source_method = if input.has_source() {
let arms = input.variants.iter().map(|variant| { let arms = input.variants.iter().map(|variant| {
let ident = &variant.ident; let ident = &variant.ident;
if variant.attrs.transparent.is_some() { if let Some(transparent_attr) = &variant.attrs.transparent {
let only_field = &variant.fields[0]; let only_field = &variant.fields[0];
if only_field.contains_generic { if only_field.contains_generic {
error_inferred_bounds.insert(only_field.ty, quote!(std::error::Error)); error_inferred_bounds.insert(only_field.ty, quote!(std::error::Error));
} }
let member = &only_field.member; let member = &only_field.member;
let source = quote!(std::error::Error::source(transparent.as_dyn_error())); let source = quote_spanned!(transparent_attr.span => std::error::Error::source(transparent.as_dyn_error()));
quote! { quote! {
#ty::#ident {#member: transparent} => #source, #ty::#ident {#member: transparent} => #source,
} }
@ -215,7 +216,7 @@ fn impl_enum(input: Enum) -> TokenStream {
None None
}; };
let varsource = quote!(source); let varsource = quote!(source);
let dyn_error = quote_spanned!(source.span()=> #varsource #asref.as_dyn_error()); let dyn_error = quote_spanned!(source_field.source_span()=> #varsource #asref.as_dyn_error());
quote! { quote! {
#ty::#ident {#source: #varsource, ..} => ::core::option::Option::Some(#dyn_error), #ty::#ident {#source: #varsource, ..} => ::core::option::Option::Some(#dyn_error),
} }

View file

@ -1,5 +1,6 @@
use crate::ast::{Enum, Field, Struct, Variant}; use crate::ast::{Enum, Field, Struct, Variant};
use syn::{Member, Type}; use proc_macro2::Span;
use syn::{spanned::Spanned, Member, Type};
impl Struct<'_> { impl Struct<'_> {
pub(crate) fn from_field(&self) -> Option<&Field> { pub(crate) fn from_field(&self) -> Option<&Field> {
@ -70,6 +71,16 @@ impl Field<'_> {
pub(crate) fn is_backtrace(&self) -> bool { pub(crate) fn is_backtrace(&self) -> bool {
type_is_backtrace(self.ty) type_is_backtrace(self.ty)
} }
pub(crate) fn source_span(&self) -> Span {
if let Some(source_attr) = &self.attrs.source {
source_attr.path().span()
} else if let Some(from_attr) = &self.attrs.from {
from_attr.path().span()
} else {
self.member.span()
}
}
} }
fn from_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> { fn from_field<'a, 'b>(fields: &'a [Field<'b>]) -> Option<&'a Field<'b>> {

View file

@ -0,0 +1,12 @@
use thiserror::Error;
#[derive(Debug)]
pub struct NotError;
#[derive(Error, Debug)]
#[error("...")]
pub enum ErrorEnum {
Broken(#[source] NotError),
}
fn main() {}

View file

@ -0,0 +1,22 @@
error[E0599]: the method `as_dyn_error` exists for reference `&NotError`, but its trait bounds were not satisfied
--> tests/ui/source-enum-unnamed-field-not-error.rs:9:14
|
4 | pub struct NotError;
| -------------------
| |
| doesn't satisfy `NotError: AsDynError<'_>`
| doesn't satisfy `NotError: std::error::Error`
...
9 | Broken(#[source] NotError),
| ^^^^^^ method cannot be called on `&NotError` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`NotError: std::error::Error`
which is required by `NotError: AsDynError<'_>`
`&NotError: std::error::Error`
which is required by `&NotError: AsDynError<'_>`
note: the trait `std::error::Error` must be implemented
--> $RUST/core/src/error.rs
|
| pub trait Error: Debug + Display {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -0,0 +1,10 @@
use thiserror::Error;
#[derive(Debug)]
struct NotError;
#[derive(Error, Debug)]
#[error("...")]
pub struct ErrorStruct(#[source] NotError);
fn main() {}

View file

@ -0,0 +1,21 @@
error[E0599]: the method `as_dyn_error` exists for struct `NotError`, but its trait bounds were not satisfied
--> tests/ui/source-struct-unnamed-field-not-error.rs:8:26
|
4 | struct NotError;
| ---------------
| |
| method `as_dyn_error` not found for this struct
| doesn't satisfy `NotError: AsDynError<'_>`
| doesn't satisfy `NotError: std::error::Error`
...
8 | pub struct ErrorStruct(#[source] NotError);
| ^^^^^^ method cannot be called on `NotError` due to unsatisfied trait bounds
|
= note: the following trait bounds were not satisfied:
`NotError: std::error::Error`
which is required by `NotError: AsDynError<'_>`
note: the trait `std::error::Error` must be implemented
--> $RUST/core/src/error.rs
|
| pub trait Error: Debug + Display {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

View file

@ -0,0 +1,11 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
Other {
message: String,
}
}
fn main() {}

View file

@ -0,0 +1,23 @@
error[E0599]: the method `as_dyn_error` exists for reference `&String`, but its trait bounds were not satisfied
--> tests/ui/transparent-enum-not-error.rs:5:13
|
5 | #[error(transparent)]
| ^^^^^^^^^^^ method cannot be called on `&String` due to unsatisfied trait bounds
|
::: $RUST/alloc/src/string.rs
|
| pub struct String {
| -----------------
| |
| doesn't satisfy `String: AsDynError<'_>`
| doesn't satisfy `String: std::error::Error`
|
= note: the following trait bounds were not satisfied:
`String: std::error::Error`
which is required by `String: AsDynError<'_>`
`&String: std::error::Error`
which is required by `&String: AsDynError<'_>`
`str: Sized`
which is required by `str: AsDynError<'_>`
`str: std::error::Error`
which is required by `str: AsDynError<'_>`

View file

@ -0,0 +1,9 @@
use thiserror::Error;
#[derive(Error, Debug)]
pub enum Error {
#[error(transparent)]
Other(String),
}
fn main() {}

View file

@ -0,0 +1,23 @@
error[E0599]: the method `as_dyn_error` exists for reference `&String`, but its trait bounds were not satisfied
--> tests/ui/transparent-enum-unnamed-field-not-error.rs:5:13
|
5 | #[error(transparent)]
| ^^^^^^^^^^^ method cannot be called on `&String` due to unsatisfied trait bounds
|
::: $RUST/alloc/src/string.rs
|
| pub struct String {
| -----------------
| |
| doesn't satisfy `String: AsDynError<'_>`
| doesn't satisfy `String: std::error::Error`
|
= note: the following trait bounds were not satisfied:
`String: std::error::Error`
which is required by `String: AsDynError<'_>`
`&String: std::error::Error`
which is required by `&String: AsDynError<'_>`
`str: Sized`
which is required by `str: AsDynError<'_>`
`str: std::error::Error`
which is required by `str: AsDynError<'_>`

View file

@ -0,0 +1,9 @@
use thiserror::Error;
#[derive(Error, Debug)]
#[error(transparent)]
pub struct Error {
message: String,
}
fn main() {}

View file

@ -0,0 +1,21 @@
error[E0599]: the method `as_dyn_error` exists for struct `String`, but its trait bounds were not satisfied
--> tests/ui/transparent-struct-not-error.rs:4:9
|
4 | #[error(transparent)]
| ^^^^^^^^^^^ method cannot be called on `String` due to unsatisfied trait bounds
|
::: $RUST/alloc/src/string.rs
|
| pub struct String {
| -----------------
| |
| doesn't satisfy `String: AsDynError<'_>`
| doesn't satisfy `String: std::error::Error`
|
= note: the following trait bounds were not satisfied:
`String: std::error::Error`
which is required by `String: AsDynError<'_>`
`str: Sized`
which is required by `str: AsDynError<'_>`
`str: std::error::Error`
which is required by `str: AsDynError<'_>`

View file

@ -0,0 +1,7 @@
use thiserror::Error;
#[derive(Error, Debug)]
#[error(transparent)]
pub struct Error(String);
fn main() {}

View file

@ -0,0 +1,21 @@
error[E0599]: the method `as_dyn_error` exists for struct `String`, but its trait bounds were not satisfied
--> tests/ui/transparent-struct-unnamed-field-not-error.rs:4:9
|
4 | #[error(transparent)]
| ^^^^^^^^^^^ method cannot be called on `String` due to unsatisfied trait bounds
|
::: $RUST/alloc/src/string.rs
|
| pub struct String {
| -----------------
| |
| doesn't satisfy `String: AsDynError<'_>`
| doesn't satisfy `String: std::error::Error`
|
= note: the following trait bounds were not satisfied:
`String: std::error::Error`
which is required by `String: AsDynError<'_>`
`str: Sized`
which is required by `str: AsDynError<'_>`
`str: std::error::Error`
which is required by `str: AsDynError<'_>`