-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Don't substitute a GAT that has mismatched generics in OpaqueTypeCollector
#112876
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -6,7 +6,7 @@ use rustc_middle::ty::util::{CheckRegions, NotUniqueParam}; | |
use rustc_middle::ty::{self, Ty, TyCtxt}; | ||
use rustc_middle::ty::{TypeSuperVisitable, TypeVisitable, TypeVisitor}; | ||
use rustc_span::Span; | ||
use rustc_type_ir::AliasKind; | ||
use rustc_trait_selection::traits::check_substs_compatible; | ||
use std::ops::ControlFlow; | ||
|
||
use crate::errors::{DuplicateArg, NotParam}; | ||
|
@@ -36,6 +36,15 @@ impl<'tcx> OpaqueTypeCollector<'tcx> { | |
self.tcx.def_span(self.item) | ||
} | ||
|
||
fn parent_trait_ref(&self) -> Option<ty::TraitRef<'tcx>> { | ||
let parent = self.parent()?; | ||
if matches!(self.tcx.def_kind(parent), DefKind::Impl { .. }) { | ||
Some(self.tcx.impl_trait_ref(parent)?.subst_identity()) | ||
} else { | ||
None | ||
} | ||
} | ||
|
||
fn parent(&self) -> Option<LocalDefId> { | ||
match self.tcx.def_kind(self.item) { | ||
DefKind::Fn => None, | ||
|
@@ -56,7 +65,7 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for OpaqueTypeCollector<'tcx> { | |
#[instrument(skip(self), ret, level = "trace")] | ||
fn visit_ty(&mut self, t: Ty<'tcx>) -> ControlFlow<ErrorGuaranteed> { | ||
match t.kind() { | ||
ty::Alias(AliasKind::Opaque, alias_ty) if alias_ty.def_id.is_local() => { | ||
ty::Alias(ty::Opaque, alias_ty) if alias_ty.def_id.is_local() => { | ||
if !self.seen.insert(alias_ty.def_id.expect_local()) { | ||
return ControlFlow::Continue(()); | ||
} | ||
|
@@ -98,37 +107,48 @@ impl<'tcx> TypeVisitor<TyCtxt<'tcx>> for OpaqueTypeCollector<'tcx> { | |
} | ||
} | ||
} | ||
ty::Alias(AliasKind::Projection, alias_ty) => { | ||
if let Some(parent) = self.parent() { | ||
trace!(?alias_ty); | ||
let (trait_ref, own_substs) = alias_ty.trait_ref_and_own_substs(self.tcx); | ||
|
||
trace!(?trait_ref, ?own_substs); | ||
// This avoids having to do normalization of `Self::AssocTy` by only | ||
// supporting the case of a method defining opaque types from assoc types | ||
// in the same impl block. | ||
if trait_ref.self_ty() == self.tcx.type_of(parent).subst_identity() { | ||
for assoc in self.tcx.associated_items(parent).in_definition_order() { | ||
ty::Alias(ty::Projection, alias_ty) => { | ||
// This avoids having to do normalization of `Self::AssocTy` by only | ||
// supporting the case of a method defining opaque types from assoc types | ||
// in the same impl block. | ||
if let Some(parent_trait_ref) = self.parent_trait_ref() { | ||
// If the trait ref of the associated item and the impl differs, | ||
// then we can't use the impl's identity substitutions below, so | ||
// just skip. | ||
if alias_ty.trait_ref(self.tcx) == parent_trait_ref { | ||
Comment on lines
+115
to
+118
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. how does that happen? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. you can have something like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually I think I can come up with an example where the old implementation results in surprising behavior. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, here's an example that should error but does not: #![feature(impl_trait_in_assoc_type)]
trait Foo<T> {
type Assoc;
fn test() -> u32;
}
struct DefinesOpaque;
impl Foo<DefinesOpaque> for () {
type Assoc = impl Sized;
fn test() -> <() as Foo<NoOpaques>>::Assoc {
let _: <Self as Foo<DefinesOpaque>>::Assoc = "";
1
}
}
struct NoOpaques;
impl Foo<NoOpaques> for () {
type Assoc = u32;
fn test() -> u32 { 1 }
} |
||
let parent = self.parent().expect("we should have a parent here"); | ||
|
||
for &assoc in self.tcx.associated_items(parent).in_definition_order() { | ||
trace!(?assoc); | ||
if assoc.trait_item_def_id == Some(alias_ty.def_id) { | ||
// We reconstruct the generic args of the associated type within the impl | ||
// from the impl's generics and the generic args passed to the type via the | ||
// projection. | ||
let substs = ty::InternalSubsts::identity_for_item( | ||
self.tcx, | ||
parent.to_def_id(), | ||
if assoc.trait_item_def_id != Some(alias_ty.def_id) { | ||
continue; | ||
} | ||
|
||
// If the type is further specializable, then the type_of | ||
// is not actually correct below. | ||
if !assoc.defaultness(self.tcx).is_final() { | ||
continue; | ||
} | ||
Comment on lines
+127
to
+131
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. At this point of complexity, should we just figure out how to normalize? I tried before, but it requires duplicating some of the normalization logic 🙃 Basically I'd want exactly what the normalizer does, but be able to hook into There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm, I don't think there is a good way of doing it right now... Do you have any examples where this current approach of manually substituting is not enough? Like code that isn't currently passing but we want it to? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nope. I was just hoping to always go the normalization route like I need to with TAITs |
||
|
||
let impl_substs = alias_ty.substs.rebase_onto( | ||
self.tcx, | ||
parent_trait_ref.def_id, | ||
ty::InternalSubsts::identity_for_item(self.tcx, parent), | ||
); | ||
|
||
if !check_substs_compatible(self.tcx, assoc, impl_substs) { | ||
self.tcx.sess.delay_span_bug( | ||
self.tcx.def_span(assoc.def_id), | ||
"item had incorrect substs", | ||
); | ||
trace!(?substs); | ||
let substs: Vec<_> = | ||
substs.iter().chain(own_substs.iter().copied()).collect(); | ||
trace!(?substs); | ||
// Find opaque types in this associated type. | ||
return self | ||
.tcx | ||
.type_of(assoc.def_id) | ||
.subst(self.tcx, &substs) | ||
.visit_with(self); | ||
return ControlFlow::Continue(()); | ||
} | ||
|
||
return self | ||
.tcx | ||
.type_of(assoc.def_id) | ||
.subst(self.tcx, impl_substs) | ||
.visit_with(self); | ||
} | ||
} | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
#![feature(impl_trait_in_assoc_type)] | ||
|
||
// We weren't checking that the trait and impl generics line up in the | ||
// normalization-shortcut code in `OpaqueTypeCollector`. | ||
|
||
use std::ops::Deref; | ||
|
||
trait Foo { | ||
type Bar<'a>; | ||
|
||
type Baz<'a>; | ||
|
||
fn test<'a>() -> Self::Bar<'a>; | ||
} | ||
|
||
impl Foo for () { | ||
type Bar<'a> = impl Deref<Target = Self::Baz<'a>>; | ||
|
||
type Baz<T> = impl Sized; | ||
//~^ ERROR type `Baz` has 1 type parameter but its trait declaration has 0 type parameters | ||
//~| ERROR unconstrained opaque type | ||
|
||
fn test<'a>() -> Self::Bar<'a> { | ||
&() | ||
} | ||
} | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
error[E0049]: type `Baz` has 1 type parameter but its trait declaration has 0 type parameters | ||
--> $DIR/impl-trait-in-type-alias-with-bad-substs.rs:19:14 | ||
| | ||
LL | type Baz<'a>; | ||
| -- expected 0 type parameters | ||
... | ||
LL | type Baz<T> = impl Sized; | ||
| ^ found 1 type parameter | ||
|
||
error: unconstrained opaque type | ||
--> $DIR/impl-trait-in-type-alias-with-bad-substs.rs:19:19 | ||
| | ||
LL | type Baz<T> = impl Sized; | ||
| ^^^^^^^^^^ | ||
| | ||
= note: `Baz` must be used in combination with a concrete type within the same impl | ||
|
||
error: aborting due to 2 previous errors | ||
|
||
For more information about this error, try `rustc --explain E0049`. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
#![feature(impl_trait_in_assoc_type)] | ||
|
||
trait Foo<T> { | ||
type Assoc; | ||
|
||
fn test() -> u32; | ||
} | ||
|
||
struct DefinesOpaque; | ||
impl Foo<DefinesOpaque> for () { | ||
type Assoc = impl Sized; | ||
|
||
// This test's return type is `u32`, *not* the opaque that is defined above. | ||
// Previously we were only checking that the self type of the assoc matched, | ||
// but this doesn't account for other impls with different trait substs. | ||
fn test() -> <() as Foo<NoOpaques>>::Assoc { | ||
let _: <Self as Foo<DefinesOpaque>>::Assoc = ""; | ||
//~^ ERROR mismatched types | ||
|
||
1 | ||
} | ||
} | ||
|
||
struct NoOpaques; | ||
impl Foo<NoOpaques> for () { | ||
type Assoc = u32; | ||
|
||
fn test() -> u32 { | ||
1 | ||
} | ||
} | ||
|
||
fn main() {} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
error[E0308]: mismatched types | ||
--> $DIR/not-matching-trait-refs-isnt-defining.rs:17:54 | ||
| | ||
LL | type Assoc = impl Sized; | ||
| ---------- the expected opaque type | ||
... | ||
LL | let _: <Self as Foo<DefinesOpaque>>::Assoc = ""; | ||
| ----------------------------------- ^^ expected opaque type, found `&str` | ||
| | | ||
| expected due to this | ||
| | ||
= note: expected opaque type `<() as Foo<DefinesOpaque>>::Assoc` | ||
found reference `&'static str` | ||
note: this item must have the opaque type in its signature in order to be able to register hidden types | ||
--> $DIR/not-matching-trait-refs-isnt-defining.rs:16:5 | ||
| | ||
LL | fn test() -> <() as Foo<NoOpaques>>::Assoc { | ||
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | ||
|
||
error: aborting due to previous error | ||
|
||
For more information about this error, try `rustc --explain E0308`. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Future nit: please do such moves in separate commits