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

[DO NOT MERGE] bootstrap with -Znext-solver=globally #133502

Draft
wants to merge 6 commits into
base: master
Choose a base branch
from
Draft
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
9 changes: 8 additions & 1 deletion compiler/rustc_middle/src/ty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,13 @@ pub use adt::*;
pub use assoc::*;
pub use generic_args::{GenericArgKind, TermKind, *};
pub use generics::*;
// Can't use a glob import here as it would cause
// ambiguity when importing the actual types implementing
// the inherent traits from this module.
// FIXME(clippy#13764): remove this `allow`.
#[allow(clippy::useless_attribute)]
#[allow(rustc::non_glob_import_of_type_ir_inherent)]
use inherent::SliceLike;
pub use intrinsic::IntrinsicDef;
use rustc_abi::{Align, FieldIdx, Integer, IntegerType, ReprFlags, ReprOptions, VariantIdx};
use rustc_ast::expand::StrippedCfgItem;
Expand Down Expand Up @@ -972,7 +979,7 @@ pub struct ParamEnv<'tcx> {
}

impl<'tcx> rustc_type_ir::inherent::ParamEnv<TyCtxt<'tcx>> for ParamEnv<'tcx> {
fn caller_bounds(self) -> impl IntoIterator<Item = ty::Clause<'tcx>> {
fn caller_bounds(self) -> impl SliceLike<Item = ty::Clause<'tcx>> {
self.caller_bounds()
}
}
Expand Down
119 changes: 95 additions & 24 deletions compiler/rustc_next_trait_solver/src/canonicalizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::cmp::Ordering;
use rustc_type_ir::data_structures::HashMap;
use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
use rustc_type_ir::inherent::*;
use rustc_type_ir::solve::{Goal, QueryInput};
use rustc_type_ir::visit::TypeVisitableExt;
use rustc_type_ir::{
self as ty, Canonical, CanonicalTyVarKind, CanonicalVarInfo, CanonicalVarKind, InferCtxtLike,
Expand All @@ -17,8 +18,11 @@ use crate::delegate::SolverDelegate;
/// while canonicalizing the response happens in the context of the
/// query.
#[derive(Debug, Clone, Copy)]
pub enum CanonicalizeMode {
Input,
enum CanonicalizeMode {
/// When canonicalizing the `param_env`, we keep `'static` as merging
/// trait candidates relies on it when deciding whether a where-bound
/// is trivial.
Input { keep_static: bool },
/// FIXME: We currently return region constraints referring to
/// placeholders and inference variables from a binder instantiated
/// inside of the query.
Expand Down Expand Up @@ -59,15 +63,15 @@ pub struct Canonicalizer<'a, D: SolverDelegate<Interner = I>, I: Interner> {
}

impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
pub fn canonicalize<T: TypeFoldable<I>>(
pub fn canonicalize_response<T: TypeFoldable<I>>(
delegate: &'a D,
canonicalize_mode: CanonicalizeMode,
max_input_universe: ty::UniverseIndex,
variables: &'a mut Vec<I::GenericArg>,
value: T,
) -> ty::Canonical<I, T> {
let mut canonicalizer = Canonicalizer {
delegate,
canonicalize_mode,
canonicalize_mode: CanonicalizeMode::Response { max_input_universe },

variables,
variable_lookup_table: Default::default(),
Expand All @@ -80,9 +84,67 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
let value = value.fold_with(&mut canonicalizer);
assert!(!value.has_infer(), "unexpected infer in {value:?}");
assert!(!value.has_placeholders(), "unexpected placeholders in {value:?}");

let (max_universe, variables) = canonicalizer.finalize();
Canonical { max_universe, variables, value }
}

/// When canonicalizing query inputs, we keep `'static` in the `param_env`
/// but erase it everywhere else. We generally don't want to depend on region
/// identity, so while it should not matter whether `'static` is kept in the
/// value or opaque type storage as well, this prevents us from accidentally
/// relying on it in the future.
///
/// We want to keep the option of canonicalizing `'static` to an existential
/// variable in the future by changing the way we detect global where-bounds.
pub fn canonicalize_input<P: TypeFoldable<I>>(
delegate: &'a D,
variables: &'a mut Vec<I::GenericArg>,
input: QueryInput<I, P>,
) -> ty::Canonical<I, QueryInput<I, P>> {
// First canonicalize the `param_env` while keeping `'static`
let mut env_canonicalizer = Canonicalizer {
delegate,
canonicalize_mode: CanonicalizeMode::Input { keep_static: true },

variables,
variable_lookup_table: Default::default(),
primitive_var_infos: Vec::new(),
binder_index: ty::INNERMOST,

cache: Default::default(),
};
let param_env = input.goal.param_env.fold_with(&mut env_canonicalizer);
debug_assert_eq!(env_canonicalizer.binder_index, ty::INNERMOST);
// Then canonicalize the rest of the input without keeping `'static`
// while *mostly* reusing the canonicalizer from above.
let mut rest_canonicalizer = Canonicalizer {
delegate,
canonicalize_mode: CanonicalizeMode::Input { keep_static: false },

variables: env_canonicalizer.variables,
// We're able to reuse the `variable_lookup_table` as whether or not
// it already contains an entry for `'static` does not matter.
variable_lookup_table: env_canonicalizer.variable_lookup_table,
primitive_var_infos: env_canonicalizer.primitive_var_infos,
binder_index: ty::INNERMOST,

// We do not reuse the cache as it may contain entries whose canonicalized
// value contains `'static`. While we could alternatively handle this by
// checking for `'static` when using cached entries, this does not
// feel worth the effort. I do not expect that a `ParamEnv` will ever
// contain large enough types for caching to be necessary.
cache: Default::default(),
};

let predicate = input.goal.predicate.fold_with(&mut rest_canonicalizer);
let goal = Goal { param_env, predicate };
let predefined_opaques_in_body =
input.predefined_opaques_in_body.fold_with(&mut rest_canonicalizer);
let value = QueryInput { goal, predefined_opaques_in_body };

assert!(!value.has_infer(), "unexpected infer in {value:?}");
assert!(!value.has_placeholders(), "unexpected placeholders in {value:?}");
let (max_universe, variables) = rest_canonicalizer.finalize();
Canonical { max_universe, variables, value }
}

Expand Down Expand Up @@ -126,7 +188,7 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
// all information which should not matter for the solver.
//
// For this we compress universes as much as possible.
CanonicalizeMode::Input => {}
CanonicalizeMode::Input { .. } => {}
// When canonicalizing a response we map a universes already entered
// by the caller to the root universe and only return useful universe
// information for placeholders and inference variables created inside
Expand Down Expand Up @@ -290,17 +352,15 @@ impl<'a, D: SolverDelegate<Interner = I>, I: Interner> Canonicalizer<'a, D, I> {
}
},
ty::Placeholder(placeholder) => match self.canonicalize_mode {
CanonicalizeMode::Input => CanonicalVarKind::PlaceholderTy(PlaceholderLike::new(
placeholder.universe(),
self.variables.len().into(),
)),
CanonicalizeMode::Input { .. } => CanonicalVarKind::PlaceholderTy(
PlaceholderLike::new(placeholder.universe(), self.variables.len().into()),
),
CanonicalizeMode::Response { .. } => CanonicalVarKind::PlaceholderTy(placeholder),
},
ty::Param(_) => match self.canonicalize_mode {
CanonicalizeMode::Input => CanonicalVarKind::PlaceholderTy(PlaceholderLike::new(
ty::UniverseIndex::ROOT,
self.variables.len().into(),
)),
CanonicalizeMode::Input { .. } => CanonicalVarKind::PlaceholderTy(
PlaceholderLike::new(ty::UniverseIndex::ROOT, self.variables.len().into()),
),
CanonicalizeMode::Response { .. } => panic!("param ty in response: {t:?}"),
},
ty::Bool
Expand Down Expand Up @@ -357,29 +417,38 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
let kind = match r.kind() {
ty::ReBound(..) => return r,

// We may encounter `ReStatic` in item signatures or the hidden type
// of an opaque. `ReErased` should only be encountered in the hidden
// We don't canonicalize `ReStatic` in the `param_env` as we use it
// when checking whether a `ParamEnv` candidate is global.
ty::ReStatic => match self.canonicalize_mode {
CanonicalizeMode::Input { keep_static: false } => {
CanonicalVarKind::Region(ty::UniverseIndex::ROOT)
}
CanonicalizeMode::Input { keep_static: true }
| CanonicalizeMode::Response { .. } => return r,
},

// `ReErased` should only be encountered in the hidden
// type of an opaque for regions that are ignored for the purposes of
// captures.
//
// FIXME: We should investigate the perf implications of not uniquifying
// `ReErased`. We may be able to short-circuit registering region
// obligations if we encounter a `ReErased` on one side, for example.
ty::ReStatic | ty::ReErased | ty::ReError(_) => match self.canonicalize_mode {
CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
ty::ReErased | ty::ReError(_) => match self.canonicalize_mode {
CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Response { .. } => return r,
},

ty::ReEarlyParam(_) | ty::ReLateParam(_) => match self.canonicalize_mode {
CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Response { .. } => {
panic!("unexpected region in response: {r:?}")
}
},

ty::RePlaceholder(placeholder) => match self.canonicalize_mode {
// We canonicalize placeholder regions as existentials in query inputs.
CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Input { .. } => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Response { max_input_universe } => {
// If we have a placeholder region inside of a query, it must be from
// a new universe.
Expand All @@ -397,7 +466,9 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
"region vid should have been resolved fully before canonicalization"
);
match self.canonicalize_mode {
CanonicalizeMode::Input => CanonicalVarKind::Region(ty::UniverseIndex::ROOT),
CanonicalizeMode::Input { keep_static: _ } => {
CanonicalVarKind::Region(ty::UniverseIndex::ROOT)
}
CanonicalizeMode::Response { .. } => {
CanonicalVarKind::Region(self.delegate.universe_of_lt(vid).unwrap())
}
Expand Down Expand Up @@ -434,15 +505,15 @@ impl<D: SolverDelegate<Interner = I>, I: Interner> TypeFolder<I> for Canonicaliz
ty::InferConst::Fresh(_) => todo!(),
},
ty::ConstKind::Placeholder(placeholder) => match self.canonicalize_mode {
CanonicalizeMode::Input => CanonicalVarKind::PlaceholderConst(
CanonicalizeMode::Input { .. } => CanonicalVarKind::PlaceholderConst(
PlaceholderLike::new(placeholder.universe(), self.variables.len().into()),
),
CanonicalizeMode::Response { .. } => {
CanonicalVarKind::PlaceholderConst(placeholder)
}
},
ty::ConstKind::Param(_) => match self.canonicalize_mode {
CanonicalizeMode::Input => CanonicalVarKind::PlaceholderConst(
CanonicalizeMode::Input { .. } => CanonicalVarKind::PlaceholderConst(
PlaceholderLike::new(ty::UniverseIndex::ROOT, self.variables.len().into()),
),
CanonicalizeMode::Response { .. } => panic!("param ty in response: {c:?}"),
Expand Down
111 changes: 110 additions & 1 deletion compiler/rustc_next_trait_solver/src/solve/alias_relate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,19 @@
//! (3.) Otherwise, if we end with two rigid (non-projection) or infer types,
//! relate them structurally.

use rustc_type_ir::data_structures::HashSet;
use rustc_type_ir::inherent::*;
use rustc_type_ir::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor};
use rustc_type_ir::{self as ty, Interner};
use tracing::{instrument, trace};

use crate::delegate::SolverDelegate;
use crate::solve::{Certainty, EvalCtxt, Goal, QueryResult};
use crate::solve::{Certainty, EvalCtxt, Goal, NoSolution, QueryResult};

enum IgnoreAliases {
Yes,
No,
}

impl<D, I> EvalCtxt<'_, D>
where
Expand All @@ -36,6 +43,12 @@ where
let Goal { param_env, predicate: (lhs, rhs, direction) } = goal;
debug_assert!(lhs.to_alias_term().is_some() || rhs.to_alias_term().is_some());

if self.alias_cannot_name_placeholder_in_rigid(param_env, lhs, rhs)
|| self.alias_cannot_name_placeholder_in_rigid(param_env, rhs, lhs)
{
return Err(NoSolution);
}

// Structurally normalize the lhs.
let lhs = if let Some(alias) = lhs.to_alias_term() {
let term = self.next_term_infer_of_kind(lhs);
Expand Down Expand Up @@ -96,4 +109,100 @@ where
}
}
}

/// In case a rigid term refers to a placeholder which is not referenced by the
/// alias, the alias cannot be normalized to that rigid term unless it contains
/// either inference variables or these placeholders are referenced in a term
/// of a `Projection`-clause in the environment.
fn alias_cannot_name_placeholder_in_rigid(
&mut self,
param_env: I::ParamEnv,
rigid_term: I::Term,
alias: I::Term,
) -> bool {
// Check that the rigid term is actually rigid.
if rigid_term.to_alias_term().is_some() || alias.to_alias_term().is_none() {
return false;
}

// If the alias has any type or const inference variables,
// do not try to apply the fast path as these inference variables
// may resolve to something containing placeholders.
if alias.has_non_region_infer() {
return false;
}

let mut referenced_placeholders =
self.collect_placeholders_in_term(rigid_term, IgnoreAliases::Yes);
for clause in param_env.caller_bounds().iter() {
match clause.kind().skip_binder() {
ty::ClauseKind::Projection(ty::ProjectionPredicate { term, .. }) => {
if term.has_non_region_infer() {
return false;
}

let env_term_placeholders =
self.collect_placeholders_in_term(term, IgnoreAliases::No);
#[allow(rustc::potential_query_instability)]
referenced_placeholders.retain(|p| !env_term_placeholders.contains(p));
}
ty::ClauseKind::Trait(_)
| ty::ClauseKind::HostEffect(_)
| ty::ClauseKind::TypeOutlives(_)
| ty::ClauseKind::RegionOutlives(_)
| ty::ClauseKind::ConstArgHasType(..)
| ty::ClauseKind::WellFormed(_)
| ty::ClauseKind::ConstEvaluatable(_) => continue,
}
}

if referenced_placeholders.is_empty() {
return false;
}

let alias_placeholders = self.collect_placeholders_in_term(alias, IgnoreAliases::No);
// If the rigid term references a placeholder not mentioned by the alias,
// they can never unify.
!referenced_placeholders.is_subset(&alias_placeholders)
}

fn collect_placeholders_in_term(
&mut self,
term: I::Term,
ignore_aliases: IgnoreAliases,
) -> HashSet<I::Term> {
// Fast path to avoid walking the term.
if !term.has_placeholders() {
return Default::default();
}

struct PlaceholderCollector<I: Interner> {
ignore_aliases: IgnoreAliases,
placeholders: HashSet<I::Term>,
}
impl<I: Interner> TypeVisitor<I> for PlaceholderCollector<I> {
type Result = ();

fn visit_ty(&mut self, t: I::Ty) {
match t.kind() {
ty::Placeholder(_) => drop(self.placeholders.insert(t.into())),
ty::Alias(..) if matches!(self.ignore_aliases, IgnoreAliases::Yes) => {}
_ => t.super_visit_with(self),
}
}

fn visit_const(&mut self, ct: I::Const) {
match ct.kind() {
ty::ConstKind::Placeholder(_) => drop(self.placeholders.insert(ct.into())),
ty::ConstKind::Unevaluated(_) | ty::ConstKind::Expr(_)
if matches!(self.ignore_aliases, IgnoreAliases::Yes) => {}
_ => ct.super_visit_with(self),
}
}
}

let mut visitor = PlaceholderCollector { ignore_aliases, placeholders: Default::default() };
term.visit_with(&mut visitor);
visitor.placeholders
}
}
Loading
Loading