Skip to content
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

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions compiler/rustc_trait_selection/src/traits/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,12 +65,12 @@ pub use self::specialize::{
pub use self::structural_match::search_for_structural_match_violation;
pub use self::structural_normalize::StructurallyNormalizeExt;
pub use self::util::elaborate;
pub use self::util::{expand_trait_aliases, TraitAliasExpander};
pub use self::util::{get_vtable_index_of_object_method, impl_item_is_final, upcast_choices};
pub use self::util::{
supertrait_def_ids, supertraits, transitive_bounds, transitive_bounds_that_define_assoc_item,
SupertraitDefIds,
check_substs_compatible, supertrait_def_ids, supertraits, transitive_bounds,
transitive_bounds_that_define_assoc_item, SupertraitDefIds,
};
pub use self::util::{expand_trait_aliases, TraitAliasExpander};
pub use self::util::{get_vtable_index_of_object_method, impl_item_is_final, upcast_choices};

pub use self::chalk_fulfill::FulfillmentContext as ChalkFulfillmentContext;

Expand Down
42 changes: 1 addition & 41 deletions compiler/rustc_trait_selection/src/traits/project.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//! Code for projecting associated types out of trait references.

use super::check_substs_compatible;
use super::specialization_graph;
use super::translate_substs;
use super::util;
Expand Down Expand Up @@ -2378,47 +2379,6 @@ fn confirm_impl_candidate<'cx, 'tcx>(
}
}

// Verify that the trait item and its implementation have compatible substs lists
fn check_substs_compatible<'tcx>(
tcx: TyCtxt<'tcx>,
assoc_item: ty::AssocItem,
substs: ty::SubstsRef<'tcx>,
) -> bool {
Comment on lines -2381 to -2386
Copy link
Contributor

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

fn check_substs_compatible_inner<'tcx>(
tcx: TyCtxt<'tcx>,
generics: &'tcx ty::Generics,
args: &'tcx [ty::GenericArg<'tcx>],
) -> bool {
if generics.count() != args.len() {
return false;
}

let (parent_args, own_args) = args.split_at(generics.parent_count);

if let Some(parent) = generics.parent
&& let parent_generics = tcx.generics_of(parent)
&& !check_substs_compatible_inner(tcx, parent_generics, parent_args) {
return false;
}

for (param, arg) in std::iter::zip(&generics.params, own_args) {
match (&param.kind, arg.unpack()) {
(ty::GenericParamDefKind::Type { .. }, ty::GenericArgKind::Type(_))
| (ty::GenericParamDefKind::Lifetime, ty::GenericArgKind::Lifetime(_))
| (ty::GenericParamDefKind::Const { .. }, ty::GenericArgKind::Const(_)) => {}
_ => return false,
}
}

true
}

let generics = tcx.generics_of(assoc_item.def_id);
// Chop off any additional substs (RPITIT) substs
let substs = &substs[0..generics.count().min(substs.len())];
check_substs_compatible_inner(tcx, generics, substs)
}

fn confirm_impl_trait_in_trait_candidate<'tcx>(
selcx: &mut SelectionContext<'_, 'tcx>,
obligation: &ProjectionTyObligation<'tcx>,
Expand Down
41 changes: 41 additions & 0 deletions compiler/rustc_trait_selection/src/traits/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -302,3 +302,44 @@ pub enum TupleArgumentsFlag {
Yes,
No,
}

// Verify that the trait item and its implementation have compatible substs lists
pub fn check_substs_compatible<'tcx>(
tcx: TyCtxt<'tcx>,
assoc_item: ty::AssocItem,
substs: ty::SubstsRef<'tcx>,
) -> bool {
fn check_substs_compatible_inner<'tcx>(
tcx: TyCtxt<'tcx>,
generics: &'tcx ty::Generics,
args: &'tcx [ty::GenericArg<'tcx>],
) -> bool {
if generics.count() != args.len() {
return false;
}

let (parent_args, own_args) = args.split_at(generics.parent_count);

if let Some(parent) = generics.parent
&& let parent_generics = tcx.generics_of(parent)
&& !check_substs_compatible_inner(tcx, parent_generics, parent_args) {
return false;
}

for (param, arg) in std::iter::zip(&generics.params, own_args) {
match (&param.kind, arg.unpack()) {
(ty::GenericParamDefKind::Type { .. }, ty::GenericArgKind::Type(_))
| (ty::GenericParamDefKind::Lifetime, ty::GenericArgKind::Lifetime(_))
| (ty::GenericParamDefKind::Const { .. }, ty::GenericArgKind::Const(_)) => {}
_ => return false,
}
}

true
}

let generics = tcx.generics_of(assoc_item.def_id);
// Chop off any additional substs (RPITIT) substs
let substs = &substs[0..generics.count().min(substs.len())];
check_substs_compatible_inner(tcx, generics, substs)
}
80 changes: 50 additions & 30 deletions compiler/rustc_ty_utils/src/opaque_types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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};
Expand Down Expand Up @@ -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,
Expand All @@ -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(());
}
Expand Down Expand Up @@ -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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

how does that happen?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can have something like -> <Self as Trait<NotIdentitySubsts...>>::Assoc, though I'm not sure if it ever results in anything but a cycle.

Copy link
Member Author

@compiler-errors compiler-errors Jun 21, 2023

Choose a reason for hiding this comment

The 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.

Copy link
Member Author

Choose a reason for hiding this comment

The 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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 fold_ty

Copy link
Member Author

Choose a reason for hiding this comment

The 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?

Copy link
Contributor

Choose a reason for hiding this comment

The 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);
}
}
}
Expand Down
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`.