From 27b386ad17720523ad7bc49e9cf481dd7f7d8da4 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Mon, 19 Jun 2023 17:58:14 +0000 Subject: [PATCH] Only walk the identity substituted version of struct fields --- compiler/rustc_ty_utils/src/opaque_types.rs | 23 +++++++++++++- ...n_behind_projection_behind_struct_field.rs | 28 +++++++++++++++++ ...hind_projection_behind_struct_field.stderr | 20 +++++++++++++ .../hidden_behind_struct_field.rs | 30 +++++++++++++++++++ .../hidden_behind_struct_field2.rs | 26 ++++++++++++++++ 5 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 tests/ui/type-alias-impl-trait/hidden_behind_projection_behind_struct_field.rs create mode 100644 tests/ui/type-alias-impl-trait/hidden_behind_projection_behind_struct_field.stderr create mode 100644 tests/ui/type-alias-impl-trait/hidden_behind_struct_field.rs create mode 100644 tests/ui/type-alias-impl-trait/hidden_behind_struct_field2.rs diff --git a/compiler/rustc_ty_utils/src/opaque_types.rs b/compiler/rustc_ty_utils/src/opaque_types.rs index 351c703cba128..29de8bf0e53f5 100644 --- a/compiler/rustc_ty_utils/src/opaque_types.rs +++ b/compiler/rustc_ty_utils/src/opaque_types.rs @@ -82,10 +82,12 @@ impl<'tcx> TypeVisitor> for OpaqueTypeCollector<'tcx> { // start seeing the error below. // Collect opaque types nested within the associated type bounds of this opaque type. + // We use identity substs here, because we already know that the opaque type uses + // only generic parameters, and thus substituting would not give us more information. for (pred, span) in self .tcx .explicit_item_bounds(alias_ty.def_id) - .subst_iter_copied(self.tcx, alias_ty.substs) + .subst_identity_iter_copied() { trace!(?pred); self.visit_spanned(span, pred); @@ -158,6 +160,25 @@ impl<'tcx> TypeVisitor> for OpaqueTypeCollector<'tcx> { } } } + ty::Adt(def, _) if def.did().is_local() => { + if !self.seen.insert(def.did().expect_local()) { + return ControlFlow::Continue(()); + } + for variant in def.variants().iter() { + for field in variant.fields.iter() { + // Don't use the `ty::Adt` substs, we either + // * found the opaque in the substs + // * will find the opaque in the unsubstituted fields + // The only other situation that can occur is that after substituting, + // some projection resolves to an opaque that we would have otherwise + // not found. While we could substitute and walk those, that would mean we + // would have to walk all substitutions of an Adt, which can quickly + // degenerate into looking at an exponential number of types. + let ty = self.tcx.type_of(field.did).subst_identity(); + self.visit_spanned(self.tcx.def_span(field.did), ty); + } + } + } _ => trace!(kind=?t.kind()), } ControlFlow::Continue(()) diff --git a/tests/ui/type-alias-impl-trait/hidden_behind_projection_behind_struct_field.rs b/tests/ui/type-alias-impl-trait/hidden_behind_projection_behind_struct_field.rs new file mode 100644 index 0000000000000..eb19b49c7e213 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/hidden_behind_projection_behind_struct_field.rs @@ -0,0 +1,28 @@ +//! This test shows that a field type that is a projection that resolves to an opaque, +//! is not a defining use. While we could substitute the struct generics, that would +//! mean we would have to walk all substitutions of an `Foo`, which can quickly +//! degenerate into looking at an exponential number of types depending on the complexity +//! of a program. + +#![feature(impl_trait_in_assoc_type)] + +struct Bar; + +trait Trait: Sized { + type Assoc; + fn foo() -> Foo; +} + +impl Trait for Bar { + type Assoc = impl std::fmt::Debug; + fn foo() -> Foo { + Foo { field: () } + //~^ ERROR: mismatched types + } +} + +struct Foo { + field: ::Assoc, +} + +fn main() {} diff --git a/tests/ui/type-alias-impl-trait/hidden_behind_projection_behind_struct_field.stderr b/tests/ui/type-alias-impl-trait/hidden_behind_projection_behind_struct_field.stderr new file mode 100644 index 0000000000000..648efd1cbfe7c --- /dev/null +++ b/tests/ui/type-alias-impl-trait/hidden_behind_projection_behind_struct_field.stderr @@ -0,0 +1,20 @@ +error[E0308]: mismatched types + --> $DIR/hidden_behind_projection_behind_struct_field.rs:19:22 + | +LL | type Assoc = impl std::fmt::Debug; + | -------------------- the expected opaque type +LL | fn foo() -> Foo { +LL | Foo { field: () } + | ^^ expected opaque type, found `()` + | + = note: expected opaque type `::Assoc` + found unit type `()` +note: this item must have the opaque type in its signature in order to be able to register hidden types + --> $DIR/hidden_behind_projection_behind_struct_field.rs:18:8 + | +LL | fn foo() -> Foo { + | ^^^ + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/type-alias-impl-trait/hidden_behind_struct_field.rs b/tests/ui/type-alias-impl-trait/hidden_behind_struct_field.rs new file mode 100644 index 0000000000000..58778702de666 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/hidden_behind_struct_field.rs @@ -0,0 +1,30 @@ +//! This test shows that the appearance of an opaque type +//! in the substs of a struct are enough to make it count +//! for making the function a defining use. It doesn't matter +//! if the opaque type is actually used in the field. + +#![feature(impl_trait_in_assoc_type)] +// check-pass + +use std::marker::PhantomData; + +struct Bar; + +trait Trait: Sized { + type Assoc; + fn foo() -> Foo; +} + +impl Trait for Bar { + type Assoc = impl std::fmt::Debug; + fn foo() -> Foo { + let foo: Foo<()> = Foo { field: PhantomData }; + foo + } +} + +struct Foo { + field: PhantomData, +} + +fn main() {} diff --git a/tests/ui/type-alias-impl-trait/hidden_behind_struct_field2.rs b/tests/ui/type-alias-impl-trait/hidden_behind_struct_field2.rs new file mode 100644 index 0000000000000..e440dce5e514f --- /dev/null +++ b/tests/ui/type-alias-impl-trait/hidden_behind_struct_field2.rs @@ -0,0 +1,26 @@ +//! This test shows that we can even follow projections +//! into associated types of the same impl if they are +//! indirectly mentioned in a struct field. + +#![feature(impl_trait_in_assoc_type)] +// check-pass + +struct Bar; + +trait Trait: Sized { + type Assoc; + fn foo() -> Foo; +} + +impl Trait for Bar { + type Assoc = impl std::fmt::Debug; + fn foo() -> Foo { + Foo { field: () } + } +} + +struct Foo { + field: ::Assoc, +} + +fn main() {}