From 6f043a03778a996dbda4e61b38b2bb8721bc3051 Mon Sep 17 00:00:00 2001 From: Luke Nelson Date: Wed, 3 May 2023 10:51:23 -0700 Subject: [PATCH] Change the encoding of supertrait clauses to support associated types When `auto_impl` is used to implement trait that has a supertrait, it needs to enforce that the inner type already implements the supertrait. However, the current encoding does not work correctly when the supertrait has an associated type that the subtrait refers to in one of its methods. To make this concrete, take the following example. ```rust use auto_impl::auto_impl; pub trait Foo { type MyType; } pub trait Bar: Foo { fn bar(&self, value: Self::MyType); } ``` `auto_impl` will expand this code into the following, which fails to compile since the compiler doesn't know that `Arc::Foo` is the same type as `T::Foo`. ```rust pub trait Foo { type MyType; } pub trait Bar: Foo { fn bar(&self, value: Self::MyType); } const _: () = { extern crate alloc; impl Bar for alloc::sync::Arc where alloc::sync::Arc: Foo, { fn bar(&self, value: Self::MyType) { T::bar(self, value) } } }; ``` This commit changes how `auto_impl` encodes supertrait `where` constraints like `alloc::sync::Arc: Foo` to refer to the inner type parameter instead, e.g., `T: Foo`. With this change, if we also annotate trait `Foo` with `auto_impl`, then the associated type example will compile, because the compiler has access to the implementation of `Foo` for `Arc` when `T: Foo`: ```rust use auto_impl::auto_impl; pub trait Foo { type MyType; } pub trait Bar: Foo { fn bar(&self, value: Self::MyType); } ``` This change is _not_ backwards compatible in certain edge scenarios. For example, the new encoding does not permit using `auto_impl` to implement `Bar` for `Arc` but having a user-defined / custom implementation of `Arc` for its supertrait `Foo`. It also causes `auto_impl` on `Bar` to fail to compile _eagerly_ when there is no corresponding implementation of `Foo`, whereas the previous behavior would allow this to compile (but would produce a constraint that fails to be satisfied if the implementation of `Bar` is ever attempted to be used). These seem like niche scenarios, but this does constitute a functional (though minor) change. However, this this trade-off may be worth since it's otherwise not possible to use associated types from supertraits with `auto_impl`. This commit also updates the unit tests accordingly; for example, adding `auto_impl` on supertraits where it was missing before in order to fix the new build errors. --- src/gen.rs | 2 +- .../super_trait_not_implemented.stderr | 49 ++++++++++--------- .../super_trait_associated_type.rs | 14 ++++++ .../super_trait_complex_for_refs.rs | 1 + .../super_trait_simple_for_refs.rs | 1 + 5 files changed, 44 insertions(+), 23 deletions(-) create mode 100644 tests/compile-pass/super_trait_associated_type.rs diff --git a/src/gen.rs b/src/gen.rs index e382f64..9310074 100644 --- a/src/gen.rs +++ b/src/gen.rs @@ -270,7 +270,7 @@ fn gen_header( if !trait_def.supertraits.is_empty() { let supertraits = &trait_def.supertraits; - out.extend(quote! { #self_ty: #supertraits, }); + out.extend(quote! { #proxy_ty_param: #supertraits, }); } if let Some(predicates) = where_clause.map(|c| &c.predicates) { out.extend(predicates.into_token_stream()); diff --git a/tests/compile-fail/super_trait_not_implemented.stderr b/tests/compile-fail/super_trait_not_implemented.stderr index 52513b6..1b65dd5 100644 --- a/tests/compile-fail/super_trait_not_implemented.stderr +++ b/tests/compile-fail/super_trait_not_implemented.stderr @@ -1,22 +1,27 @@ -error[E0277]: the trait bound `Box: Supi` is not satisfied - --> tests/compile-fail/super_trait_not_implemented.rs:18:18 - | -18 | requires_foo(Box::new(Dog)); // shouldn't, because `Box: Supi` is not satisfied - | ------------ ^^^^^^^^^^^^^ the trait `Supi` is not implemented for `Box` - | | - | required by a bound introduced by this call - | - = help: the trait `Supi` is implemented for `Dog` -note: required for `Box` to implement `Foo` - --> tests/compile-fail/super_trait_not_implemented.rs:5:1 - | -5 | #[auto_impl(Box, &)] - | ^^^^^^^^^^^^^^^^^^^^ -6 | trait Foo: Supi {} - | ^^^ ---- unsatisfied trait bound introduced here -note: required by a bound in `requires_foo` - --> tests/compile-fail/super_trait_not_implemented.rs:14:20 - | -14 | fn requires_foo(_: T) {} - | ^^^ required by this bound in `requires_foo` - = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) +error[E0277]: the trait bound `Box: Supi` is not satisfied + --> tests/compile-fail/super_trait_not_implemented.rs:5:1 + | +5 | #[auto_impl(Box, &)] + | ^^^^^^^^^^^^^^^^^^^^ the trait `Supi` is not implemented for `Box` + | + = help: the trait `Supi` is implemented for `Dog` +note: required by a bound in `Foo` + --> tests/compile-fail/super_trait_not_implemented.rs:6:12 + | +6 | trait Foo: Supi {} + | ^^^^ required by this bound in `Foo` + = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0277]: the trait bound `&'a T: Supi` is not satisfied + --> tests/compile-fail/super_trait_not_implemented.rs:5:1 + | +5 | #[auto_impl(Box, &)] + | ^^^^^^^^^^^^^^^^^^^^ the trait `Supi` is not implemented for `&'a T` + | + = help: the trait `Supi` is implemented for `Dog` +note: required by a bound in `Foo` + --> tests/compile-fail/super_trait_not_implemented.rs:6:12 + | +6 | trait Foo: Supi {} + | ^^^^ required by this bound in `Foo` + = note: this error originates in the attribute macro `auto_impl` (in Nightly builds, run with -Z macro-backtrace for more info) diff --git a/tests/compile-pass/super_trait_associated_type.rs b/tests/compile-pass/super_trait_associated_type.rs new file mode 100644 index 0000000..a2f47d5 --- /dev/null +++ b/tests/compile-pass/super_trait_associated_type.rs @@ -0,0 +1,14 @@ +use auto_impl::auto_impl; + +#[auto_impl(Box, &)] +trait Supi { + type Assoc; +} + +#[auto_impl(Box, &)] +trait Foo: Supi { + fn foo(&self, x: Self::Assoc) -> String; +} + + +fn main() {} diff --git a/tests/compile-pass/super_trait_complex_for_refs.rs b/tests/compile-pass/super_trait_complex_for_refs.rs index 1be1ef6..5d3def7 100644 --- a/tests/compile-pass/super_trait_complex_for_refs.rs +++ b/tests/compile-pass/super_trait_complex_for_refs.rs @@ -1,5 +1,6 @@ use auto_impl::auto_impl; +#[auto_impl(Box, &)] trait Supi<'a, T> { fn supi(&self); } diff --git a/tests/compile-pass/super_trait_simple_for_refs.rs b/tests/compile-pass/super_trait_simple_for_refs.rs index a1a8018..18a56f9 100644 --- a/tests/compile-pass/super_trait_simple_for_refs.rs +++ b/tests/compile-pass/super_trait_simple_for_refs.rs @@ -1,5 +1,6 @@ use auto_impl::auto_impl; +#[auto_impl(Box, &)] trait Supi {} #[auto_impl(Box, &)]