diff --git a/compiler/rustc_hir_analysis/src/variance/solve.rs b/compiler/rustc_hir_analysis/src/variance/solve.rs index 4106c1a5b6355..c11b99c517588 100644 --- a/compiler/rustc_hir_analysis/src/variance/solve.rs +++ b/compiler/rustc_hir_analysis/src/variance/solve.rs @@ -5,8 +5,9 @@ //! optimal solution to the constraints. The final variance for each //! inferred is then written into the `variance_map` in the tcx. -use rustc_hir::def_id::DefIdMap; -use rustc_middle::ty; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::{DefId, DefIdMap}; +use rustc_middle::ty::{self, Ty, TyCtxt, TypeSuperVisitable, TypeVisitable, TypeVisitor}; use tracing::debug; use super::constraints::*; @@ -40,6 +41,62 @@ struct SolveContext<'a, 'tcx> { solutions: Vec, } +/// Visitor to find "grounding" uses of type parameters which are any use that is not +/// solely through self-referential cycles. +/// +/// For example, in `struct Thing(*mut Thing)`, the use of `T` is not grounding +/// because it only appears in a self-referential cycle. In contrast, in +/// `struct Thing(Option)`, the use of `T` is grounding because it appears in a +/// non-self-referential context (`Option`). +struct GroundingUseVisitor { + item_def_id: DefId, + grounded_params: Vec, + in_self: bool, + in_alias: bool, +} + +impl<'tcx> TypeVisitor> for GroundingUseVisitor { + fn visit_ty(&mut self, ty: Ty<'tcx>) -> () { + match ty.kind() { + // Self-reference: visit args in a self-recursive context. + ty::Adt(def, _) if def.did() == self.item_def_id => { + let was_in_self = self.in_self; + self.in_self = true; + ty.super_visit_with(self); + self.in_self = was_in_self; + () + } + // Projections/aliases: treat parameter uses as grounding. + ty::Alias(..) => { + let was_in_alias = self.in_alias; + self.in_alias = true; + ty.super_visit_with(self); + self.in_alias = was_in_alias; + () + } + // Found a direct parameter use, record it + ty::Param(data) => { + if !self.in_self || self.in_alias { + self.grounded_params.push(data.index); + } + () + } + // Everything else: recurse normally via super_visit_with, + // which visits generic args of ADTs, elements of tuples, etc. + _ => ty.super_visit_with(self), + } + } + + fn visit_region(&mut self, r: ty::Region<'tcx>) -> () { + if let ty::ReEarlyParam(ref data) = r.kind() { + if !self.in_self || self.in_alias { + self.grounded_params.push(data.index); + } + } + () + } +} + pub(crate) fn solve_constraints<'tcx>( constraints_cx: ConstraintContext<'_, 'tcx>, ) -> ty::CrateVariancesMap<'tcx> { @@ -55,6 +112,7 @@ pub(crate) fn solve_constraints<'tcx>( let mut solutions_cx = SolveContext { terms_cx, constraints, solutions }; solutions_cx.solve(); + solutions_cx.fix_purely_recursive_params(); let variances = solutions_cx.create_map(); ty::CrateVariancesMap { variances } @@ -91,6 +149,75 @@ impl<'a, 'tcx> SolveContext<'a, 'tcx> { } } + /// After the fixed-point solver, check for parameters whose non-bivariance + /// is solely due to self-referential cycles (e.g. `struct Thing(*mut Thing)`). + /// Those parameters have no "grounding" use and should be bivariant. + fn fix_purely_recursive_params(&mut self) { + let tcx = self.terms_cx.tcx; + + // First pass: identify which solution slots need to be reset to Bivariant. + // We use a RefCell so the Fn closure required by `UnordItems::all` can + // accumulate results via interior mutability. + let to_reset: std::cell::RefCell> = std::cell::RefCell::new(Vec::new()); + + self.terms_cx.inferred_starts.items().all(|(&def_id, &InferredIndex(start))| { + // Skip lang items with hardcoded variance (e.g., PhantomData). + if self.terms_cx.lang_items.iter().any(|(id, _)| *id == def_id) { + return true; + } + + // Only check ADTs (structs, enums, unions). + if !matches!(tcx.def_kind(def_id), DefKind::Struct | DefKind::Enum | DefKind::Union) { + return true; + } + + let generics = tcx.generics_of(def_id); + let count = generics.count(); + + // Quick check: if all params are already bivariant, nothing to do. + if (0..count).all(|i| self.solutions[start + i] == ty::Bivariant) { + return true; + } + + // Walk all fields to find grounding uses. + let mut visitor = GroundingUseVisitor { + item_def_id: def_id.to_def_id(), + grounded_params: Default::default(), + in_self: false, + in_alias: false, + }; + let adt = tcx.adt_def(def_id); + for field in adt.all_fields() { + tcx.type_of(field.did).instantiate_identity().visit_with(&mut visitor); + } + + // Collect solution indices with no grounding use. + for i in 0..count { + if !matches!(generics.param_at(i, tcx).kind, ty::GenericParamDefKind::Type { .. }) { + continue; + } + if self.solutions[start + i] != ty::Bivariant + && !visitor.grounded_params.contains(&(i as u32)) + { + debug!( + "fix_purely_recursive_params: param {} of {:?} has no grounding use \ + (was {:?}), will reset to Bivariant", + i, + def_id, + self.solutions[start + i] + ); + to_reset.borrow_mut().push(start + i); + } + } + true + }); + + // Second pass: apply the resets. + for idx in to_reset.into_inner() { + self.solutions[idx] = ty::Bivariant; + } + } + fn enforce_const_invariance(&self, generics: &ty::Generics, variances: &mut [ty::Variance]) { let tcx = self.terms_cx.tcx; diff --git a/tests/ui/issues/issue-23046.rs b/tests/ui/issues/issue-23046.rs index a68369616d8b6..b8c9d4941240f 100644 --- a/tests/ui/issues/issue-23046.rs +++ b/tests/ui/issues/issue-23046.rs @@ -1,6 +1,7 @@ pub enum Expr<'var, VAR> { Let(Box>, - Box Fn(Expr<'v, VAR>) -> Expr<'v, VAR> + 'var>) + Box Fn(Expr<'v, VAR>) -> Expr<'v, VAR> + 'var>), + Val(VAR), } pub fn add<'var, VAR> diff --git a/tests/ui/issues/issue-23046.stderr b/tests/ui/issues/issue-23046.stderr index f70ac0c9f388a..3bb58dfc8e42d 100644 --- a/tests/ui/issues/issue-23046.stderr +++ b/tests/ui/issues/issue-23046.stderr @@ -1,5 +1,5 @@ error[E0282]: type annotations needed for `Expr<'_, _>` - --> $DIR/issue-23046.rs:17:15 + --> $DIR/issue-23046.rs:18:15 | LL | let ex = |x| { | ^ diff --git a/tests/ui/query-system/query-cycle-printing-issue-151226.rs b/tests/ui/query-system/query-cycle-printing-issue-151226.rs index 9d0a20737c9fa..17ccd21fd4fa5 100644 --- a/tests/ui/query-system/query-cycle-printing-issue-151226.rs +++ b/tests/ui/query-system/query-cycle-printing-issue-151226.rs @@ -1,5 +1,6 @@ struct A(std::sync::OnceLock); //~^ ERROR recursive type `A` has infinite size +//~| ERROR type parameter `T` is only used recursively //~| ERROR cycle detected when computing layout of `A<()>` static B: A<()> = todo!(); diff --git a/tests/ui/query-system/query-cycle-printing-issue-151226.stderr b/tests/ui/query-system/query-cycle-printing-issue-151226.stderr index 7e574b5911a39..c4f8dcfd41542 100644 --- a/tests/ui/query-system/query-cycle-printing-issue-151226.stderr +++ b/tests/ui/query-system/query-cycle-printing-issue-151226.stderr @@ -1,3 +1,14 @@ +error: type parameter `T` is only used recursively + --> $DIR/query-cycle-printing-issue-151226.rs:1:33 + | +LL | struct A(std::sync::OnceLock); + | - ^^^^ + | | + | type parameter must be used non-recursively in the definition + | + = help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData` + = note: all type parameters must be used in a non-recursive way in order to constrain their variance + error[E0072]: recursive type `A` has infinite size --> $DIR/query-cycle-printing-issue-151226.rs:1:1 | @@ -18,19 +29,19 @@ error[E0391]: cycle detected when computing layout of `A<()>` = note: ...which requires computing layout of `core::mem::maybe_dangling::MaybeDangling>`... = note: ...which again requires computing layout of `A<()>`, completing the cycle note: cycle used when checking that `B` is well-formed - --> $DIR/query-cycle-printing-issue-151226.rs:5:1 + --> $DIR/query-cycle-printing-issue-151226.rs:6:1 | LL | static B: A<()> = todo!(); | ^^^^^^^^^^^^^^^ = note: see https://rustc-dev-guide.rust-lang.org/overview.html#queries and https://rustc-dev-guide.rust-lang.org/query.html for more information error[E0080]: a cycle occurred during layout computation - --> $DIR/query-cycle-printing-issue-151226.rs:5:1 + --> $DIR/query-cycle-printing-issue-151226.rs:6:1 | LL | static B: A<()> = todo!(); | ^^^^^^^^^^^^^^^ evaluation of `B` failed here -error: aborting due to 3 previous errors +error: aborting due to 4 previous errors Some errors have detailed explanations: E0072, E0080, E0391. For more information about an error, try `rustc --explain E0072`. diff --git a/tests/ui/variance/variance-unused-type-param.rs b/tests/ui/variance/variance-unused-type-param.rs index ef3c41ca5560c..7eec4ca767190 100644 --- a/tests/ui/variance/variance-unused-type-param.rs +++ b/tests/ui/variance/variance-unused-type-param.rs @@ -3,33 +3,49 @@ // Test that we report an error for unused type parameters in types and traits, // and that we offer a helpful suggestion. -struct SomeStruct { x: u32 } -//~^ ERROR parameter `A` is never used +struct SomeStruct { + //~^ ERROR parameter `A` is never used + x: u32, +} -enum SomeEnum { Nothing } -//~^ ERROR parameter `A` is never used +enum SomeEnum { + //~^ ERROR parameter `A` is never used + Nothing, +} // Here T might *appear* used, but in fact it isn't. enum ListCell { Cons(Box>), //~^ ERROR parameter `T` is only used recursively - Nil + Nil, +} + +// Example of grounded use of T, which should not trigger an error. +enum ListCellGrounded { + Cons(Box>), + Value(T), + Nil, } +struct RecursiveInvariant(*mut RecursiveInvariant); +//~^ ERROR parameter `T` is only used recursively + struct SelfTyAlias(Box); //~^ ERROR parameter `T` is only used recursively struct WithBounds {} //~^ ERROR parameter `T` is never used -struct WithWhereBounds where T: Sized {} +struct WithWhereBounds //~^ ERROR parameter `T` is never used +where + T: Sized, {} struct WithOutlivesBounds {} //~^ ERROR parameter `T` is never used struct DoubleNothing { -//~^ ERROR parameter `T` is never used + //~^ ERROR parameter `T` is never used s: SomeStruct, } diff --git a/tests/ui/variance/variance-unused-type-param.stderr b/tests/ui/variance/variance-unused-type-param.stderr index c747532e62836..ef9e1dcc7148a 100644 --- a/tests/ui/variance/variance-unused-type-param.stderr +++ b/tests/ui/variance/variance-unused-type-param.stderr @@ -1,23 +1,23 @@ error[E0392]: type parameter `A` is never used --> $DIR/variance-unused-type-param.rs:6:19 | -LL | struct SomeStruct { x: u32 } +LL | struct SomeStruct { | ^ unused type parameter | = help: consider removing `A`, referring to it in a field, or using a marker such as `PhantomData` = help: if you intended `A` to be a const parameter, use `const A: /* Type */` instead error[E0392]: type parameter `A` is never used - --> $DIR/variance-unused-type-param.rs:9:15 + --> $DIR/variance-unused-type-param.rs:11:15 | -LL | enum SomeEnum { Nothing } +LL | enum SomeEnum { | ^ unused type parameter | = help: consider removing `A`, referring to it in a field, or using a marker such as `PhantomData` = help: if you intended `A` to be a const parameter, use `const A: /* Type */` instead error: type parameter `T` is only used recursively - --> $DIR/variance-unused-type-param.rs:14:23 + --> $DIR/variance-unused-type-param.rs:18:23 | LL | enum ListCell { | - type parameter must be used non-recursively in the definition @@ -28,7 +28,18 @@ LL | Cons(Box>), = note: all type parameters must be used in a non-recursive way in order to constrain their variance error: type parameter `T` is only used recursively - --> $DIR/variance-unused-type-param.rs:19:27 + --> $DIR/variance-unused-type-param.rs:30:54 + | +LL | struct RecursiveInvariant(*mut RecursiveInvariant); + | - ^ + | | + | type parameter must be used non-recursively in the definition + | + = help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData` + = note: all type parameters must be used in a non-recursive way in order to constrain their variance + +error: type parameter `T` is only used recursively + --> $DIR/variance-unused-type-param.rs:33:27 | LL | struct SelfTyAlias(Box); | - ^^^^ @@ -39,7 +50,7 @@ LL | struct SelfTyAlias(Box); = note: all type parameters must be used in a non-recursive way in order to constrain their variance error[E0392]: type parameter `T` is never used - --> $DIR/variance-unused-type-param.rs:22:19 + --> $DIR/variance-unused-type-param.rs:36:19 | LL | struct WithBounds {} | ^ unused type parameter @@ -47,15 +58,15 @@ LL | struct WithBounds {} = help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData` error[E0392]: type parameter `T` is never used - --> $DIR/variance-unused-type-param.rs:25:24 + --> $DIR/variance-unused-type-param.rs:39:24 | -LL | struct WithWhereBounds where T: Sized {} +LL | struct WithWhereBounds | ^ unused type parameter | = help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData` error[E0392]: type parameter `T` is never used - --> $DIR/variance-unused-type-param.rs:28:27 + --> $DIR/variance-unused-type-param.rs:44:27 | LL | struct WithOutlivesBounds {} | ^ unused type parameter @@ -63,7 +74,7 @@ LL | struct WithOutlivesBounds {} = help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData` error[E0392]: type parameter `T` is never used - --> $DIR/variance-unused-type-param.rs:31:22 + --> $DIR/variance-unused-type-param.rs:47:22 | LL | struct DoubleNothing { | ^ unused type parameter @@ -74,6 +85,6 @@ LL | s: SomeStruct, = help: consider removing `T`, referring to it in a field, or using a marker such as `PhantomData` = help: if you intended `T` to be a const parameter, use `const T: /* Type */` instead -error: aborting due to 8 previous errors +error: aborting due to 9 previous errors For more information about this error, try `rustc --explain E0392`.