diff --git a/compiler/rustc_infer/src/infer/combine.rs b/compiler/rustc_infer/src/infer/combine.rs index 5bd6c667fd7..33d26317c71 100644 --- a/compiler/rustc_infer/src/infer/combine.rs +++ b/compiler/rustc_infer/src/infer/combine.rs @@ -45,7 +45,7 @@ use rustc_middle::ty::relate::{self, Relate, RelateResult, TypeRelation}; use rustc_middle::ty::subst::SubstsRef; use rustc_middle::ty::{self, InferConst, ToPredicate, Ty, TyCtxt, TypeFoldable}; use rustc_middle::ty::{IntType, UintType}; -use rustc_span::DUMMY_SP; +use rustc_span::{Span, DUMMY_SP}; /// Small-storage-optimized implementation of a map /// made specifically for caching results. @@ -219,11 +219,11 @@ impl<'infcx, 'tcx> InferCtxt<'infcx, 'tcx> { } (ty::ConstKind::Infer(InferConst::Var(vid)), _) => { - return self.unify_const_variable(a_is_expected, vid, b); + return self.unify_const_variable(relation.param_env(), vid, b, a_is_expected); } (_, ty::ConstKind::Infer(InferConst::Var(vid))) => { - return self.unify_const_variable(!a_is_expected, vid, a); + return self.unify_const_variable(relation.param_env(), vid, a, !a_is_expected); } (ty::ConstKind::Unevaluated(..), _) if self.tcx.lazy_normalization() => { // FIXME(#59490): Need to remove the leak check to accommodate @@ -247,17 +247,66 @@ impl<'infcx, 'tcx> InferCtxt<'infcx, 'tcx> { ty::relate::super_relate_consts(relation, a, b) } - pub fn unify_const_variable( + /// Unifies the const variable `target_vid` with the given constant. + /// + /// This also tests if the given const `ct` contains an inference variable which was previously + /// unioned with `target_vid`. If this is the case, inferring `target_vid` to `ct` + /// would result in an infinite type as we continously replace an inference variable + /// in `ct` with `ct` itself. + /// + /// This is especially important as unevaluated consts use their parents generics. + /// They therefore often contain unused substs, making these errors far more likely. + /// + /// A good example of this is the following: + /// + /// ```rust + /// #![feature(const_generics)] + /// + /// fn bind(value: [u8; N]) -> [u8; 3 + 4] { + /// todo!() + /// } + /// + /// fn main() { + /// let mut arr = Default::default(); + /// arr = bind(arr); + /// } + /// ``` + /// + /// Here `3 + 4` ends up as `ConstKind::Unevaluated` which uses the generics + /// of `fn bind` (meaning that its substs contain `N`). + /// + /// `bind(arr)` now infers that the type of `arr` must be `[u8; N]`. + /// The assignment `arr = bind(arr)` now tries to equate `N` with `3 + 4`. + /// + /// As `3 + 4` contains `N` in its substs, this must not succeed. + /// + /// See `src/test/ui/const-generics/occurs-check/` for more examples where this is relevant. + fn unify_const_variable( &self, + param_env: ty::ParamEnv<'tcx>, + target_vid: ty::ConstVid<'tcx>, + ct: &'tcx ty::Const<'tcx>, vid_is_expected: bool, - vid: ty::ConstVid<'tcx>, - value: &'tcx ty::Const<'tcx>, ) -> RelateResult<'tcx, &'tcx ty::Const<'tcx>> { + let (for_universe, span) = { + let mut inner = self.inner.borrow_mut(); + let variable_table = &mut inner.const_unification_table(); + let var_value = variable_table.probe_value(target_vid); + match var_value.val { + ConstVariableValue::Known { value } => { + bug!("instantiating {:?} which has a known value {:?}", target_vid, value) + } + ConstVariableValue::Unknown { universe } => (universe, var_value.origin.span), + } + }; + let value = ConstInferUnifier { infcx: self, span, param_env, for_universe, target_vid } + .relate(ct, ct)?; + self.inner .borrow_mut() .const_unification_table() .unify_var_value( - vid, + target_vid, ConstVarValue { origin: ConstVariableOrigin { kind: ConstVariableOriginKind::ConstInference, @@ -266,8 +315,8 @@ impl<'infcx, 'tcx> InferCtxt<'infcx, 'tcx> { val: ConstVariableValue::Known { value }, }, ) - .map_err(|e| const_unification_error(vid_is_expected, e))?; - Ok(value) + .map(|()| value) + .map_err(|e| const_unification_error(vid_is_expected, e)) } fn unify_integral_variable( @@ -422,7 +471,7 @@ impl<'infcx, 'tcx> CombineFields<'infcx, 'tcx> { let for_universe = match self.infcx.inner.borrow_mut().type_variables().probe(for_vid) { v @ TypeVariableValue::Known { .. } => { - panic!("instantiating {:?} which has a known value {:?}", for_vid, v,) + bug!("instantiating {:?} which has a known value {:?}", for_vid, v,) } TypeVariableValue::Unknown { universe } => universe, }; @@ -740,7 +789,6 @@ impl TypeRelation<'tcx> for Generalizer<'_, 'tcx> { } } } - ty::ConstKind::Unevaluated(..) if self.tcx().lazy_normalization() => Ok(c), _ => relate::super_relate_consts(self, c, c), } } @@ -790,3 +838,175 @@ fn float_unification_error<'tcx>( let (ty::FloatVarValue(a), ty::FloatVarValue(b)) = v; TypeError::FloatMismatch(ty::relate::expected_found_bool(a_is_expected, a, b)) } + +struct ConstInferUnifier<'cx, 'tcx> { + infcx: &'cx InferCtxt<'cx, 'tcx>, + + span: Span, + + param_env: ty::ParamEnv<'tcx>, + + for_universe: ty::UniverseIndex, + + /// The vid of the const variable that is in the process of being + /// instantiated; if we find this within the const we are folding, + /// that means we would have created a cyclic const. + target_vid: ty::ConstVid<'tcx>, +} + +// We use `TypeRelation` here to propagate `RelateResult` upwards. +// +// Both inputs are expected to be the same. +impl TypeRelation<'tcx> for ConstInferUnifier<'_, 'tcx> { + fn tcx(&self) -> TyCtxt<'tcx> { + self.infcx.tcx + } + + fn param_env(&self) -> ty::ParamEnv<'tcx> { + self.param_env + } + + fn tag(&self) -> &'static str { + "ConstInferUnifier" + } + + fn a_is_expected(&self) -> bool { + true + } + + fn relate_with_variance>( + &mut self, + _variance: ty::Variance, + a: T, + b: T, + ) -> RelateResult<'tcx, T> { + // We don't care about variance here. + self.relate(a, b) + } + + fn binders( + &mut self, + a: ty::Binder, + b: ty::Binder, + ) -> RelateResult<'tcx, ty::Binder> + where + T: Relate<'tcx>, + { + Ok(ty::Binder::bind(self.relate(a.skip_binder(), b.skip_binder())?)) + } + + fn tys(&mut self, t: Ty<'tcx>, _t: Ty<'tcx>) -> RelateResult<'tcx, Ty<'tcx>> { + debug_assert_eq!(t, _t); + debug!("ConstInferUnifier: t={:?}", t); + + match t.kind() { + &ty::Infer(ty::TyVar(vid)) => { + let vid = self.infcx.inner.borrow_mut().type_variables().root_var(vid); + let probe = self.infcx.inner.borrow_mut().type_variables().probe(vid); + match probe { + TypeVariableValue::Known { value: u } => { + debug!("ConstOccursChecker: known value {:?}", u); + self.tys(u, u) + } + TypeVariableValue::Unknown { universe } => { + if self.for_universe.can_name(universe) { + return Ok(t); + } + + let origin = + *self.infcx.inner.borrow_mut().type_variables().var_origin(vid); + let new_var_id = self.infcx.inner.borrow_mut().type_variables().new_var( + self.for_universe, + false, + origin, + ); + let u = self.tcx().mk_ty_var(new_var_id); + debug!( + "ConstInferUnifier: replacing original vid={:?} with new={:?}", + vid, u + ); + Ok(u) + } + } + } + _ => relate::super_relate_tys(self, t, t), + } + } + + fn regions( + &mut self, + r: ty::Region<'tcx>, + _r: ty::Region<'tcx>, + ) -> RelateResult<'tcx, ty::Region<'tcx>> { + debug_assert_eq!(r, _r); + debug!("ConstInferUnifier: r={:?}", r); + + match r { + // Never make variables for regions bound within the type itself, + // nor for erased regions. + ty::ReLateBound(..) | ty::ReErased => { + return Ok(r); + } + + ty::RePlaceholder(..) + | ty::ReVar(..) + | ty::ReEmpty(_) + | ty::ReStatic + | ty::ReEarlyBound(..) + | ty::ReFree(..) => { + // see common code below + } + } + + let r_universe = self.infcx.universe_of_region(r); + if self.for_universe.can_name(r_universe) { + return Ok(r); + } else { + // FIXME: This is non-ideal because we don't give a + // very descriptive origin for this region variable. + Ok(self.infcx.next_region_var_in_universe(MiscVariable(self.span), self.for_universe)) + } + } + + fn consts( + &mut self, + c: &'tcx ty::Const<'tcx>, + _c: &'tcx ty::Const<'tcx>, + ) -> RelateResult<'tcx, &'tcx ty::Const<'tcx>> { + debug_assert_eq!(c, _c); + debug!("ConstInferUnifier: c={:?}", c); + + match c.val { + ty::ConstKind::Infer(InferConst::Var(vid)) => { + let mut inner = self.infcx.inner.borrow_mut(); + let variable_table = &mut inner.const_unification_table(); + + // Check if the current unification would end up + // unifying `target_vid` with a const which contains + // an inference variable which is unioned with `target_vid`. + // + // Not doing so can easily result in stack overflows. + if variable_table.unioned(self.target_vid, vid) { + return Err(TypeError::CyclicConst(c)); + } + + let var_value = variable_table.probe_value(vid); + match var_value.val { + ConstVariableValue::Known { value: u } => self.consts(u, u), + ConstVariableValue::Unknown { universe } => { + if self.for_universe.can_name(universe) { + Ok(c) + } else { + let new_var_id = variable_table.new_key(ConstVarValue { + origin: var_value.origin, + val: ConstVariableValue::Unknown { universe: self.for_universe }, + }); + Ok(self.tcx().mk_const_var(new_var_id, c.ty)) + } + } + } + } + _ => relate::super_relate_consts(self, c, c), + } + } +} diff --git a/compiler/rustc_middle/src/ty/error.rs b/compiler/rustc_middle/src/ty/error.rs index 475c3101c1e..ff98a8b9bf0 100644 --- a/compiler/rustc_middle/src/ty/error.rs +++ b/compiler/rustc_middle/src/ty/error.rs @@ -56,6 +56,7 @@ pub enum TypeError<'tcx> { /// created a cycle (because it appears somewhere within that /// type). CyclicTy(Ty<'tcx>), + CyclicConst(&'tcx ty::Const<'tcx>), ProjectionMismatched(ExpectedFound), ExistentialMismatch(ExpectedFound<&'tcx ty::List>>), ObjectUnsafeCoercion(DefId), @@ -100,6 +101,7 @@ impl<'tcx> fmt::Display for TypeError<'tcx> { match *self { CyclicTy(_) => write!(f, "cyclic type of infinite size"), + CyclicConst(_) => write!(f, "encountered a self-referencing constant"), Mismatch => write!(f, "types differ"), UnsafetyMismatch(values) => { write!(f, "expected {} fn, found {} fn", values.expected, values.found) @@ -195,9 +197,9 @@ impl<'tcx> TypeError<'tcx> { pub fn must_include_note(&self) -> bool { use self::TypeError::*; match self { - CyclicTy(_) | UnsafetyMismatch(_) | Mismatch | AbiMismatch(_) | FixedArraySize(_) - | Sorts(_) | IntMismatch(_) | FloatMismatch(_) | VariadicMismatch(_) - | TargetFeatureCast(_) => false, + CyclicTy(_) | CyclicConst(_) | UnsafetyMismatch(_) | Mismatch | AbiMismatch(_) + | FixedArraySize(_) | Sorts(_) | IntMismatch(_) | FloatMismatch(_) + | VariadicMismatch(_) | TargetFeatureCast(_) => false, Mutability | TupleSize(_) diff --git a/compiler/rustc_middle/src/ty/structural_impls.rs b/compiler/rustc_middle/src/ty/structural_impls.rs index 6d9d23836fc..597ceac9386 100644 --- a/compiler/rustc_middle/src/ty/structural_impls.rs +++ b/compiler/rustc_middle/src/ty/structural_impls.rs @@ -689,6 +689,7 @@ impl<'a, 'tcx> Lift<'tcx> for ty::error::TypeError<'a> { Traits(x) => Traits(x), VariadicMismatch(x) => VariadicMismatch(x), CyclicTy(t) => return tcx.lift(&t).map(|t| CyclicTy(t)), + CyclicConst(ct) => return tcx.lift(&ct).map(|ct| CyclicConst(ct)), ProjectionMismatched(x) => ProjectionMismatched(x), Sorts(ref x) => return tcx.lift(x).map(Sorts), ExistentialMismatch(ref x) => return tcx.lift(x).map(ExistentialMismatch), diff --git a/compiler/rustc_mir/src/interpret/eval_context.rs b/compiler/rustc_mir/src/interpret/eval_context.rs index 00d6ffb14ea..f97096984fa 100644 --- a/compiler/rustc_mir/src/interpret/eval_context.rs +++ b/compiler/rustc_mir/src/interpret/eval_context.rs @@ -482,13 +482,13 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { /// The `substs` are assumed to already be in our interpreter "universe" (param_env). pub(super) fn resolve( &self, - def_id: DefId, + def: ty::WithOptConstParam, substs: SubstsRef<'tcx>, ) -> InterpResult<'tcx, ty::Instance<'tcx>> { - trace!("resolve: {:?}, {:#?}", def_id, substs); + trace!("resolve: {:?}, {:#?}", def, substs); trace!("param_env: {:#?}", self.param_env); trace!("substs: {:#?}", substs); - match ty::Instance::resolve(*self.tcx, self.param_env, def_id, substs) { + match ty::Instance::resolve_opt_const_arg(*self.tcx, self.param_env, def, substs) { Ok(Some(instance)) => Ok(instance), Ok(None) => throw_inval!(TooGeneric), diff --git a/compiler/rustc_mir/src/interpret/operand.rs b/compiler/rustc_mir/src/interpret/operand.rs index 8c4bb19866e..9fcbd60d4a3 100644 --- a/compiler/rustc_mir/src/interpret/operand.rs +++ b/compiler/rustc_mir/src/interpret/operand.rs @@ -552,7 +552,7 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { ty::ConstKind::Param(_) => throw_inval!(TooGeneric), ty::ConstKind::Error(_) => throw_inval!(TypeckError(ErrorReported)), ty::ConstKind::Unevaluated(def, substs, promoted) => { - let instance = self.resolve(def.did, substs)?; + let instance = self.resolve(def, substs)?; return Ok(self.eval_to_allocation(GlobalId { instance, promoted })?.into()); } ty::ConstKind::Infer(..) diff --git a/compiler/rustc_mir/src/interpret/terminator.rs b/compiler/rustc_mir/src/interpret/terminator.rs index d3c0b497a16..b789cb76e9f 100644 --- a/compiler/rustc_mir/src/interpret/terminator.rs +++ b/compiler/rustc_mir/src/interpret/terminator.rs @@ -64,7 +64,12 @@ impl<'mir, 'tcx: 'mir, M: Machine<'mir, 'tcx>> InterpCx<'mir, 'tcx, M> { } ty::FnDef(def_id, substs) => { let sig = func.layout.ty.fn_sig(*self.tcx); - (FnVal::Instance(self.resolve(def_id, substs)?), sig.abi()) + ( + FnVal::Instance( + self.resolve(ty::WithOptConstParam::unknown(def_id), substs)?, + ), + sig.abi(), + ) } _ => span_bug!( terminator.source_info.span, diff --git a/src/test/ui/const-generics/issues/issue-69654-run-pass.rs b/src/test/ui/const-generics/issues/issue-69654-run-pass.rs new file mode 100644 index 00000000000..bbfd2183b06 --- /dev/null +++ b/src/test/ui/const-generics/issues/issue-69654-run-pass.rs @@ -0,0 +1,18 @@ +// run-pass +#![feature(const_generics)] +#![allow(incomplete_features, unused_braces)] + +trait Bar {} +impl Bar for [u8; {7}] {} + +struct Foo {} +impl Foo +where + [u8; N]: Bar<[(); N]>, +{ + fn foo() {} +} + +fn main() { + Foo::foo(); +} diff --git a/src/test/ui/const-generics/issues/issue-69654.rs b/src/test/ui/const-generics/issues/issue-69654.rs new file mode 100644 index 00000000000..7e775999ebd --- /dev/null +++ b/src/test/ui/const-generics/issues/issue-69654.rs @@ -0,0 +1,18 @@ +#![feature(const_generics)] +#![allow(incomplete_features)] + +trait Bar {} +impl Bar for [u8; T] {} +//~^ ERROR expected value, found type parameter `T` + +struct Foo {} +impl Foo +where + [u8; N]: Bar<[(); N]>, +{ + fn foo() {} +} + +fn main() { + Foo::foo(); +} diff --git a/src/test/ui/const-generics/issues/issue-69654.stderr b/src/test/ui/const-generics/issues/issue-69654.stderr new file mode 100644 index 00000000000..70af7bf25d8 --- /dev/null +++ b/src/test/ui/const-generics/issues/issue-69654.stderr @@ -0,0 +1,9 @@ +error[E0423]: expected value, found type parameter `T` + --> $DIR/issue-69654.rs:5:25 + | +LL | impl Bar for [u8; T] {} + | ^ not a value + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0423`. diff --git a/src/test/ui/const-generics/occurs-check/bind-param.rs b/src/test/ui/const-generics/occurs-check/bind-param.rs new file mode 100644 index 00000000000..68d18650009 --- /dev/null +++ b/src/test/ui/const-generics/occurs-check/bind-param.rs @@ -0,0 +1,17 @@ +// build-pass +#![feature(const_generics)] +#![allow(incomplete_features)] + +// This test does not use any "unevaluated" consts, so it should compile just fine. + +fn bind(value: [u8; N]) -> [u8; N] { + todo!() +} + +fn sink(_: [u8; 5]) {} + +fn main() { + let mut arr = Default::default(); + arr = bind(arr); + sink(arr); +} diff --git a/src/test/ui/const-generics/occurs-check/unify-fixpoint.rs b/src/test/ui/const-generics/occurs-check/unify-fixpoint.rs new file mode 100644 index 00000000000..3cb9b7b9da8 --- /dev/null +++ b/src/test/ui/const-generics/occurs-check/unify-fixpoint.rs @@ -0,0 +1,18 @@ +#![feature(const_generics)] //~ WARN the feature `const_generics` is incomplete + +// It depends on how we normalize constants and how const equate works if this +// compiles. +// +// Please ping @lcnr if the output if this test changes. + + +fn bind(value: [u8; N + 2]) -> [u8; N * 2] { + //~^ ERROR constant expression depends on a generic parameter + //~| ERROR constant expression depends on a generic parameter + todo!() +} + +fn main() { + let mut arr = Default::default(); + arr = bind::<2>(arr); +} diff --git a/src/test/ui/const-generics/occurs-check/unify-fixpoint.stderr b/src/test/ui/const-generics/occurs-check/unify-fixpoint.stderr new file mode 100644 index 00000000000..671f1103dcc --- /dev/null +++ b/src/test/ui/const-generics/occurs-check/unify-fixpoint.stderr @@ -0,0 +1,27 @@ +warning: the feature `const_generics` is incomplete and may not be safe to use and/or cause compiler crashes + --> $DIR/unify-fixpoint.rs:1:12 + | +LL | #![feature(const_generics)] + | ^^^^^^^^^^^^^^ + | + = note: `#[warn(incomplete_features)]` on by default + = note: see issue #44580 for more information + +error: constant expression depends on a generic parameter + --> $DIR/unify-fixpoint.rs:9:32 + | +LL | fn bind(value: [u8; N + 2]) -> [u8; N * 2] { + | ^^^^^^^^^^^ + | + = note: this may fail depending on what value the parameter takes + +error: constant expression depends on a generic parameter + --> $DIR/unify-fixpoint.rs:9:48 + | +LL | fn bind(value: [u8; N + 2]) -> [u8; N * 2] { + | ^^^^^^^^^^^ + | + = note: this may fail depending on what value the parameter takes + +error: aborting due to 2 previous errors; 1 warning emitted + diff --git a/src/test/ui/const-generics/occurs-check/unify-n-nplusone.rs b/src/test/ui/const-generics/occurs-check/unify-n-nplusone.rs new file mode 100644 index 00000000000..552b1b2a66a --- /dev/null +++ b/src/test/ui/const-generics/occurs-check/unify-n-nplusone.rs @@ -0,0 +1,17 @@ +#![feature(const_generics)] +#![allow(incomplete_features)] + +// This test would try to unify `N` with `N + 1` which must fail the occurs check. + +fn bind(value: [u8; N]) -> [u8; N + 1] { + //~^ ERROR constant expression depends on a generic parameter + todo!() +} + +fn sink(_: [u8; 5]) {} + +fn main() { + let mut arr = Default::default(); + arr = bind(arr); + sink(arr); +} diff --git a/src/test/ui/const-generics/occurs-check/unify-n-nplusone.stderr b/src/test/ui/const-generics/occurs-check/unify-n-nplusone.stderr new file mode 100644 index 00000000000..c1ac7eec1e7 --- /dev/null +++ b/src/test/ui/const-generics/occurs-check/unify-n-nplusone.stderr @@ -0,0 +1,10 @@ +error: constant expression depends on a generic parameter + --> $DIR/unify-n-nplusone.rs:6:44 + | +LL | fn bind(value: [u8; N]) -> [u8; N + 1] { + | ^^^^^^^^^^^ + | + = note: this may fail depending on what value the parameter takes + +error: aborting due to previous error + diff --git a/src/test/ui/const-generics/occurs-check/unused-substs-1.rs b/src/test/ui/const-generics/occurs-check/unused-substs-1.rs new file mode 100644 index 00000000000..f56687ecd93 --- /dev/null +++ b/src/test/ui/const-generics/occurs-check/unused-substs-1.rs @@ -0,0 +1,14 @@ +// build-pass +#![feature(const_generics)] +#![allow(incomplete_features)] + +trait Bar {} +impl Bar for A<{ 6 + 1 }> {} + +struct A +where + A: Bar; + +fn main() { + let _ = A; +} diff --git a/src/test/ui/const-generics/occurs-check/unused-substs-2.rs b/src/test/ui/const-generics/occurs-check/unused-substs-2.rs new file mode 100644 index 00000000000..12444ec5312 --- /dev/null +++ b/src/test/ui/const-generics/occurs-check/unused-substs-2.rs @@ -0,0 +1,27 @@ +// check-pass +#![feature(const_generics)] +#![allow(incomplete_features)] + +// The goal is is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst. +// +// If we are then able to infer `ty::Infer(TyVar(_#1t) := Ty` we introduced an +// artificial inference cycle. +struct Foo; + +trait Bind { + fn bind() -> (T, Self); +} + +// `N` has to be `ConstKind::Unevaluated`. +impl Bind for Foo<{ 6 + 1 }> { + fn bind() -> (T, Self) { + (panic!(), Foo) + } +} + +fn main() { + let (mut t, foo) = Foo::bind(); + // `t` is `ty::Infer(TyVar(_#1t))` + // `foo` contains `ty::Infer(TyVar(_#1t))` in its substs + t = foo; +} diff --git a/src/test/ui/const-generics/occurs-check/unused-substs-3.rs b/src/test/ui/const-generics/occurs-check/unused-substs-3.rs new file mode 100644 index 00000000000..187e27382fc --- /dev/null +++ b/src/test/ui/const-generics/occurs-check/unused-substs-3.rs @@ -0,0 +1,18 @@ +// check-pass +#![feature(const_generics)] +#![allow(incomplete_features)] + +// The goal is is to get an unevaluated const `ct` with a `Ty::Infer(TyVar(_#1t)` subst. +// +// If we are then able to infer `ty::Infer(TyVar(_#1t) := Ty` we introduced an +// artificial inference cycle. +fn bind() -> (T, [u8; 6 + 1]) { + todo!() +} + +fn main() { + let (mut t, foo) = bind(); + // `t` is `ty::Infer(TyVar(_#1t))` + // `foo` contains `ty::Infer(TyVar(_#1t))` in its substs + t = foo; +} diff --git a/src/test/ui/const-generics/occurs-check/unused-substs-4.rs b/src/test/ui/const-generics/occurs-check/unused-substs-4.rs new file mode 100644 index 00000000000..8e42ceb6d70 --- /dev/null +++ b/src/test/ui/const-generics/occurs-check/unused-substs-4.rs @@ -0,0 +1,12 @@ +// build-pass +#![feature(const_generics)] +#![allow(incomplete_features)] + +fn bind(value: [u8; N]) -> [u8; 3 + 4] { + todo!() +} + +fn main() { + let mut arr = Default::default(); + arr = bind(arr); +}