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

generalize: handle occurs check failure in aliases #117088

Merged
merged 4 commits into from
Dec 5, 2023
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
64 changes: 49 additions & 15 deletions compiler/rustc_infer/src/infer/combine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -326,7 +326,9 @@ impl<'tcx> InferCtxt<'tcx> {
) -> RelateResult<'tcx, ty::Const<'tcx>> {
let span =
self.inner.borrow_mut().const_unification_table().probe_value(target_vid).origin.span;
let Generalization { value, needs_wf: _ } = generalize::generalize(
// FIXME(generic_const_exprs): Occurs check failures for unevaluated
// constants and generic expressions are not yet handled correctly.
let Generalization { value_may_be_infer: value, needs_wf: _ } = generalize::generalize(
self,
&mut CombineDelegate { infcx: self, span, param_env },
ct,
Expand Down Expand Up @@ -445,7 +447,7 @@ impl<'infcx, 'tcx> CombineFields<'infcx, 'tcx> {
// `'?2` and `?3` are fresh region/type inference
// variables. (Down below, we will relate `a_ty <: b_ty`,
// adding constraints like `'x: '?2` and `?1 <: ?3`.)
let Generalization { value: b_ty, needs_wf } = generalize::generalize(
let Generalization { value_may_be_infer: b_ty, needs_wf } = generalize::generalize(
self.infcx,
&mut CombineDelegate {
infcx: self.infcx,
Expand All @@ -457,7 +459,6 @@ impl<'infcx, 'tcx> CombineFields<'infcx, 'tcx> {
ambient_variance,
)?;

debug!(?b_ty);
self.infcx.inner.borrow_mut().type_variables().instantiate(b_vid, b_ty);

if needs_wf {
Expand All @@ -477,19 +478,52 @@ impl<'infcx, 'tcx> CombineFields<'infcx, 'tcx> {
// relations wind up attributed to the same spans. We need
// to associate causes/spans with each of the relations in
// the stack to get this right.
match ambient_variance {
ty::Variance::Invariant => self.equate(a_is_expected).relate(a_ty, b_ty),
ty::Variance::Covariant => self.sub(a_is_expected).relate(a_ty, b_ty),
ty::Variance::Contravariant => self.sub(a_is_expected).relate_with_variance(
ty::Contravariant,
ty::VarianceDiagInfo::default(),
a_ty,
b_ty,
),
ty::Variance::Bivariant => {
unreachable!("no code should be generalizing bivariantly (currently)")
if b_ty.is_ty_var() {
// This happens for cases like `<?0 as Trait>::Assoc == ?0`.
// We can't instantiate `?0` here as that would result in a
// cyclic type. We instead delay the unification in case
// the alias can be normalized to something which does not
// mention `?0`.

// FIXME(-Ztrait-solver=next): replace this with `AliasRelate`
let &ty::Alias(kind, data) = a_ty.kind() else {
bug!("generalization should only result in infer vars for aliases");
};
if !self.infcx.next_trait_solver() {
// The old solver only accepts projection predicates for associated types.
match kind {
ty::AliasKind::Projection => {}
ty::AliasKind::Inherent | ty::AliasKind::Weak | ty::AliasKind::Opaque => {
return Err(TypeError::CyclicTy(a_ty));
}
}
}
}?;

// FIXME: This does not handle subtyping correctly, we should switch to
// alias-relate in the new solver and could instead create a new inference
// variable for `a_ty`, emitting `Projection(a_ty, a_infer)` and
// `a_infer <: b_ty`.
self.obligations.push(Obligation::new(
lcnr marked this conversation as resolved.
Show resolved Hide resolved
self.tcx(),
self.trace.cause.clone(),
self.param_env,
ty::ProjectionPredicate { projection_ty: data, term: b_ty.into() },
))
} else {
match ambient_variance {
ty::Variance::Invariant => self.equate(a_is_expected).relate(a_ty, b_ty),
ty::Variance::Covariant => self.sub(a_is_expected).relate(a_ty, b_ty),
ty::Variance::Contravariant => self.sub(a_is_expected).relate_with_variance(
ty::Contravariant,
ty::VarianceDiagInfo::default(),
a_ty,
b_ty,
),
ty::Variance::Bivariant => {
unreachable!("no code should be generalizing bivariantly (currently)")
}
}?;
}

Ok(())
}
Expand Down
87 changes: 77 additions & 10 deletions compiler/rustc_infer/src/infer/generalize.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
use std::mem;

use rustc_data_structures::sso::SsoHashMap;
use rustc_hir::def_id::DefId;
use rustc_middle::infer::unify_key::{ConstVarValue, ConstVariableValue};
use rustc_middle::ty::error::TypeError;
use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation};
use rustc_middle::ty::{self, InferConst, Term, Ty, TyCtxt, TypeVisitableExt};
use rustc_middle::ty::visit::MaxUniverse;
use rustc_middle::ty::{self, InferConst, Term, Ty, TyCtxt, TypeVisitable, TypeVisitableExt};
use rustc_span::Span;

use crate::infer::nll_relate::TypeRelatingDelegate;
use crate::infer::type_variable::TypeVariableValue;
use crate::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind, TypeVariableValue};
use crate::infer::{InferCtxt, RegionVariableOrigin};

/// Attempts to generalize `term` for the type variable `for_vid`.
Expand Down Expand Up @@ -38,27 +41,30 @@ pub(super) fn generalize<'tcx, D: GeneralizerDelegate<'tcx>, T: Into<Term<'tcx>>
root_vid,
for_universe,
root_term: term.into(),
in_alias: false,
needs_wf: false,
cache: Default::default(),
};

assert!(!term.has_escaping_bound_vars());
let value = generalizer.relate(term, term)?;
let value_may_be_infer = generalizer.relate(term, term)?;
let needs_wf = generalizer.needs_wf;
Ok(Generalization { value, needs_wf })
Ok(Generalization { value_may_be_infer, needs_wf })
}

/// Abstracts the handling of region vars between HIR and MIR/NLL typechecking
/// in the generalizer code.
pub trait GeneralizerDelegate<'tcx> {
pub(super) trait GeneralizerDelegate<'tcx> {
fn param_env(&self) -> ty::ParamEnv<'tcx>;

fn forbid_inference_vars() -> bool;

fn span(&self) -> Span;

fn generalize_region(&mut self, universe: ty::UniverseIndex) -> ty::Region<'tcx>;
}

pub struct CombineDelegate<'cx, 'tcx> {
pub(super) struct CombineDelegate<'cx, 'tcx> {
pub infcx: &'cx InferCtxt<'tcx>,
pub param_env: ty::ParamEnv<'tcx>,
pub span: Span,
Expand All @@ -73,6 +79,10 @@ impl<'tcx> GeneralizerDelegate<'tcx> for CombineDelegate<'_, 'tcx> {
false
}

fn span(&self) -> Span {
self.span
}

fn generalize_region(&mut self, universe: ty::UniverseIndex) -> ty::Region<'tcx> {
// FIXME: This is non-ideal because we don't give a
// very descriptive origin for this region variable.
Expand All @@ -93,6 +103,10 @@ where
<Self as TypeRelatingDelegate<'tcx>>::forbid_inference_vars()
}

fn span(&self) -> Span {
<Self as TypeRelatingDelegate<'tcx>>::span(&self)
}

fn generalize_region(&mut self, universe: ty::UniverseIndex) -> ty::Region<'tcx> {
<Self as TypeRelatingDelegate<'tcx>>::generalize_existential(self, universe)
}
Expand Down Expand Up @@ -139,6 +153,13 @@ struct Generalizer<'me, 'tcx, D> {

cache: SsoHashMap<Ty<'tcx>, Ty<'tcx>>,

/// This is set once we're generalizing the arguments of an alias.
///
/// This is necessary to correctly handle
/// `<T as Bar<<?0 as Foo>::Assoc>::Assoc == ?0`. This equality can
/// hold by either normalizing the outer or the inner associated type.
in_alias: bool,

/// See the field `needs_wf` in `Generalization`.
needs_wf: bool,
}
Expand Down Expand Up @@ -193,7 +214,7 @@ where
opt_variances,
a_subst,
b_subst,
true,
false,
)
}
}
Expand Down Expand Up @@ -309,6 +330,44 @@ where
}
}

ty::Alias(kind, data) => {
// An occurs check failure inside of an alias does not mean
// that the types definitely don't unify. We may be able
// to normalize the alias after all.
//
// We handle this by lazily equating the alias and generalizing
// it to an inference variable.
let is_nested_alias = mem::replace(&mut self.in_alias, true);
let result = match self.relate(data, data) {
Ok(data) => Ok(Ty::new_alias(self.tcx(), kind, data)),
Err(e) => {
if is_nested_alias {
return Err(e);
} else {
let mut visitor = MaxUniverse::new();
t.visit_with(&mut visitor);
let infer_replacement_is_complete =
self.for_universe.can_name(visitor.max_universe())
&& !t.has_escaping_bound_vars();
if !infer_replacement_is_complete {
warn!("may incompletely handle alias type: {t:?}");
}

debug!("generalization failure in alias");
Ok(self.infcx.next_ty_var_in_universe(
TypeVariableOrigin {
kind: TypeVariableOriginKind::MiscVariable,
span: self.delegate.span(),
},
self.for_universe,
))
}
}
};
self.in_alias = is_nested_alias;
result
}

_ => relate::structurally_relate_tys(self, t, t),
}?;

Expand Down Expand Up @@ -456,8 +515,16 @@ where
/// not only the generalized type, but also a bool flag
/// indicating whether further WF checks are needed.
#[derive(Debug)]
pub struct Generalization<T> {
pub value: T,
pub(super) struct Generalization<T> {
/// When generalizing `<?0 as Trait>::Assoc` or
/// `<T as Bar<<?0 as Foo>::Assoc>>::Assoc`
/// for `?0` generalization returns an inference
/// variable.
///
/// This has to be handled wotj care as it can
/// otherwise very easily result in infinite
/// recursion.
pub(super) value_may_be_infer: T,

/// If true, then the generalized type may not be well-formed,
/// even if the source type is well-formed, so we should add an
Expand All @@ -484,5 +551,5 @@ pub struct Generalization<T> {
/// will force the calling code to check that `WF(Foo<?C, ?D>)`
/// holds, which in turn implies that `?C::Item == ?D`. So once
/// `?C` is constrained, that should suffice to restrict `?D`.
pub needs_wf: bool,
pub(super) needs_wf: bool,
}
6 changes: 5 additions & 1 deletion compiler/rustc_infer/src/infer/nll_relate/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -214,13 +214,17 @@ where
}

fn generalize(&mut self, ty: Ty<'tcx>, for_vid: ty::TyVid) -> RelateResult<'tcx, Ty<'tcx>> {
let Generalization { value: ty, needs_wf: _ } = generalize::generalize(
let Generalization { value_may_be_infer: ty, needs_wf: _ } = generalize::generalize(
self.infcx,
&mut self.delegate,
ty,
for_vid,
self.ambient_variance,
)?;

if ty.is_ty_var() {
span_bug!(self.delegate.span(), "occurs check failure in MIR typeck");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

potentially reachable with user type annotations? Probably not as the actual type inferred by HIR typeck also needs to contain an inference variable? 🤔

}
Ok(ty)
}

Expand Down
25 changes: 25 additions & 0 deletions tests/ui/coherence/occurs-check/associated-type.next.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, ReBound(DebruijnIndex(0), BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, RePlaceholder(!2_BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, ReBound(DebruijnIndex(0), BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, RePlaceholder(!2_BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, ReBound(DebruijnIndex(0), BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, RePlaceholder(!2_BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, ReBound(DebruijnIndex(0), BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, RePlaceholder(!2_BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
error[E0119]: conflicting implementations of trait `Overlap<for<'a> fn(&'a (), ())>` for type `for<'a> fn(&'a (), ())`
--> $DIR/associated-type.rs:31:1
|
LL | impl<T> Overlap<T> for T {
| ------------------------ first implementation here
...
LL | / impl<T> Overlap<for<'a> fn(&'a (), Assoc<'a, T>)> for T
LL | |
LL | | where
LL | | for<'a> *const T: ToUnit<'a>,
| |_________________________________^ conflicting implementation for `for<'a> fn(&'a (), ())`
|
= note: this behavior recently changed as a result of a bug fix; see rust-lang/rust#56105 for details

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0119`.
25 changes: 25 additions & 0 deletions tests/ui/coherence/occurs-check/associated-type.old.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, ReBound(DebruijnIndex(0), BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, RePlaceholder(!3_BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, ReBound(DebruijnIndex(0), BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, RePlaceholder(!3_BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, ReBound(DebruijnIndex(0), BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, RePlaceholder(!3_BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, ReBound(DebruijnIndex(0), BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
WARN rustc_infer::infer::generalize may incompletely handle alias type: Alias(Projection, AliasTy { args: [*const ?1t, RePlaceholder(!3_BoundRegion { var: 0, kind: BrNamed(DefId(0:27 ~ associated_type[f554]::{impl#3}::'a#1), 'a) })], def_id: DefId(0:5 ~ associated_type[f554]::ToUnit::Unit) })
error[E0119]: conflicting implementations of trait `Overlap<for<'a> fn(&'a (), _)>` for type `for<'a> fn(&'a (), _)`
--> $DIR/associated-type.rs:31:1
|
LL | impl<T> Overlap<T> for T {
| ------------------------ first implementation here
...
LL | / impl<T> Overlap<for<'a> fn(&'a (), Assoc<'a, T>)> for T
LL | |
LL | | where
LL | | for<'a> *const T: ToUnit<'a>,
| |_________________________________^ conflicting implementation for `for<'a> fn(&'a (), _)`
|
= note: this behavior recently changed as a result of a bug fix; see rust-lang/rust#56105 for details

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0119`.
45 changes: 45 additions & 0 deletions tests/ui/coherence/occurs-check/associated-type.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// revisions: old next
//[next] compile-flags: -Ztrait-solver=next

// A regression test for #105787

// Using the higher ranked projection hack to prevent us from replacing the projection
// with an inference variable.
trait ToUnit<'a> {
type Unit;
}

struct LocalTy;
impl<'a> ToUnit<'a> for *const LocalTy {
type Unit = ();
}

impl<'a, T: Copy + ?Sized> ToUnit<'a> for *const T {
type Unit = ();
}

trait Overlap<T> {
type Assoc;
}

type Assoc<'a, T> = <*const T as ToUnit<'a>>::Unit;

impl<T> Overlap<T> for T {
type Assoc = usize;
}

impl<T> Overlap<for<'a> fn(&'a (), Assoc<'a, T>)> for T
//~^ ERROR conflicting implementations of trait
where
for<'a> *const T: ToUnit<'a>,
{
type Assoc = Box<usize>;
}

fn foo<T: Overlap<U>, U>(x: T::Assoc) -> T::Assoc {
x
}

fn main() {
foo::<for<'a> fn(&'a (), ()), for<'a> fn(&'a (), ())>(3usize);
}
12 changes: 12 additions & 0 deletions tests/ui/coherence/occurs-check/opaques.next.stderr
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
error[E0119]: conflicting implementations of trait `Trait<Alias<_>>` for type `Alias<_>`
--> $DIR/opaques.rs:29:1
|
LL | impl<T> Trait<T> for T {
| ---------------------- first implementation here
...
LL | impl<T> Trait<T> for defining_scope::Alias<T> {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ conflicting implementation for `Alias<_>`

error: aborting due to 1 previous error

For more information about this error, try `rustc --explain E0119`.
Loading
Loading