Skip to content
Open
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
131 changes: 129 additions & 2 deletions compiler/rustc_hir_analysis/src/variance/solve.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -40,6 +41,62 @@ struct SolveContext<'a, 'tcx> {
solutions: Vec<ty::Variance>,
}

/// 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<T>(*mut Thing<T>)`, the use of `T` is not grounding
/// because it only appears in a self-referential cycle. In contrast, in
/// `struct Thing<T>(Option<T>)`, the use of `T` is grounding because it appears in a
/// non-self-referential context (`Option<T>`).
struct GroundingUseVisitor {
item_def_id: DefId,
grounded_params: Vec<u32>,
in_self: bool,
in_alias: bool,
}

impl<'tcx> TypeVisitor<TyCtxt<'tcx>> 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> {
Expand All @@ -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 }
Expand Down Expand Up @@ -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<T>(*mut Thing<T>)`).
/// 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<Vec<usize>> = 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;

Expand Down
3 changes: 2 additions & 1 deletion tests/ui/issues/issue-23046.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
pub enum Expr<'var, VAR> {
Let(Box<Expr<'var, VAR>>,
Box<dyn for<'v> Fn(Expr<'v, VAR>) -> Expr<'v, VAR> + 'var>)
Box<dyn for<'v> Fn(Expr<'v, VAR>) -> Expr<'v, VAR> + 'var>),
Val(VAR),
}

pub fn add<'var, VAR>
Expand Down
2 changes: 1 addition & 1 deletion tests/ui/issues/issue-23046.stderr
Original file line number Diff line number Diff line change
@@ -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| {
| ^
Expand Down
1 change: 1 addition & 0 deletions tests/ui/query-system/query-cycle-printing-issue-151226.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
struct A<T>(std::sync::OnceLock<Self>);
//~^ 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!();
Expand Down
17 changes: 14 additions & 3 deletions tests/ui/query-system/query-cycle-printing-issue-151226.stderr
Original file line number Diff line number Diff line change
@@ -1,3 +1,14 @@
error: type parameter `T` is only used recursively
--> $DIR/query-cycle-printing-issue-151226.rs:1:33
|
LL | struct A<T>(std::sync::OnceLock<Self>);
| - ^^^^
| |
| 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
|
Expand All @@ -18,19 +29,19 @@ error[E0391]: cycle detected when computing layout of `A<()>`
= note: ...which requires computing layout of `core::mem::maybe_dangling::MaybeDangling<A<()>>`...
= 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`.
30 changes: 23 additions & 7 deletions tests/ui/variance/variance-unused-type-param.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<A> { x: u32 }
//~^ ERROR parameter `A` is never used
struct SomeStruct<A> {
//~^ ERROR parameter `A` is never used
x: u32,
}

enum SomeEnum<A> { Nothing }
//~^ ERROR parameter `A` is never used
enum SomeEnum<A> {
//~^ ERROR parameter `A` is never used
Nothing,
}

// Here T might *appear* used, but in fact it isn't.
enum ListCell<T> {
Cons(Box<ListCell<T>>),
//~^ ERROR parameter `T` is only used recursively
Nil
Nil,
}

// Example of grounded use of T, which should not trigger an error.
enum ListCellGrounded<T> {
Cons(Box<ListCellGrounded<T>>),
Value(T),
Nil,
}

struct RecursiveInvariant<T>(*mut RecursiveInvariant<T>);
//~^ ERROR parameter `T` is only used recursively

struct SelfTyAlias<T>(Box<Self>);
//~^ ERROR parameter `T` is only used recursively

struct WithBounds<T: Sized> {}
//~^ ERROR parameter `T` is never used

struct WithWhereBounds<T> where T: Sized {}
struct WithWhereBounds<T>
//~^ ERROR parameter `T` is never used
where
T: Sized, {}

struct WithOutlivesBounds<T: 'static> {}
//~^ ERROR parameter `T` is never used

struct DoubleNothing<T> {
//~^ ERROR parameter `T` is never used
//~^ ERROR parameter `T` is never used
s: SomeStruct<T>,
}

Expand Down
33 changes: 22 additions & 11 deletions tests/ui/variance/variance-unused-type-param.stderr
Original file line number Diff line number Diff line change
@@ -1,23 +1,23 @@
error[E0392]: type parameter `A` is never used
--> $DIR/variance-unused-type-param.rs:6:19
|
LL | struct SomeStruct<A> { x: u32 }
LL | struct SomeStruct<A> {
| ^ 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<A> { Nothing }
LL | enum SomeEnum<A> {
| ^ 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<T> {
| - type parameter must be used non-recursively in the definition
Expand All @@ -28,7 +28,18 @@ LL | Cons(Box<ListCell<T>>),
= 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<T>(*mut RecursiveInvariant<T>);
| - ^
| |
| 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<T>(Box<Self>);
| - ^^^^
Expand All @@ -39,31 +50,31 @@ LL | struct SelfTyAlias<T>(Box<Self>);
= 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<T: Sized> {}
| ^ 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:25:24
--> $DIR/variance-unused-type-param.rs:39:24
|
LL | struct WithWhereBounds<T> where T: Sized {}
LL | struct WithWhereBounds<T>
| ^ 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<T: 'static> {}
| ^ 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:31:22
--> $DIR/variance-unused-type-param.rs:47:22
|
LL | struct DoubleNothing<T> {
| ^ unused type parameter
Expand All @@ -74,6 +85,6 @@ LL | s: SomeStruct<T>,
= 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`.
Loading