Skip to content

Commit

Permalink
Auto merge of rust-lang#133502 - lcnr:rust4, r=<try>
Browse files Browse the repository at this point in the history
[DO NOT MERGE] bootstrap with `-Znext-solver=globally`

A revival of rust-lang#124812.

Current status:

~~`./x.py b --stage 2` passes 🎉~~

`try` builds succeed 🎉 🎉 🎉

[first perf run](rust-lang#133502 (comment)) 👻

### in-flight changes

- ce66d92 is a rebased version of rust-lang#125334, unsure whether I actually want to land this PR for now
- rust-lang#136824

r? `@ghost`
  • Loading branch information
bors committed Feb 11, 2025
2 parents 69482e8 + 3c982f3 commit bbbbbb4
Show file tree
Hide file tree
Showing 19 changed files with 572 additions and 330 deletions.
8 changes: 8 additions & 0 deletions compiler/rustc_hir_typeck/src/method/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -494,6 +494,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
}
}

// HACK: TODO
let self_ty = {
let infer = self.infcx.next_ty_var(span);
self.demand_eqtype(span, infer, self_ty);
self.select_obligations_where_possible(|_| {});
self.resolve_vars_if_possible(infer)
};

let pick = self.probe_for_name(
probe::Mode::Path,
method_name,
Expand Down
2 changes: 1 addition & 1 deletion compiler/rustc_infer/src/infer/relate/generalize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ impl<'tcx> InferCtxt<'tcx> {
/// This is *not* expected to be used anywhere except for an implementation of
/// `TypeRelation`. Do not use this, and instead please use `At::eq`, for all
/// other usecases (i.e. setting the value of a type var).
#[instrument(level = "debug", skip(self, relation))]
#[instrument(level = "debug", skip(self, relation), fields(structurally_relate_aliases = ?relation.structurally_relate_aliases()))]
pub fn instantiate_ty_var<R: PredicateEmittingRelation<InferCtxt<'tcx>>>(
&self,
relation: &mut R,
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
}
}
31 changes: 23 additions & 8 deletions compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -789,11 +789,12 @@ where
/// treat the alias as rigid.
///
/// See trait-system-refactor-initiative#124 for more details.
#[instrument(level = "debug", skip(self), ret)]
#[instrument(level = "debug", skip(self, inject_normalize_to_rigid_candidate), ret)]
pub(super) fn merge_candidates(
&mut self,
proven_via: Option<TraitGoalProvenVia>,
candidates: Vec<Candidate<I>>,
inject_normalize_to_rigid_candidate: impl FnOnce(&mut EvalCtxt<'_, D>) -> QueryResult<I>,
) -> QueryResult<I> {
let Some(proven_via) = proven_via else {
// We don't care about overflow. If proving the trait goal overflowed, then
Expand All @@ -810,13 +811,27 @@ where
// FIXME(const_trait_impl): should this behavior also be used by
// constness checking. Doing so is *at least theoretically* breaking,
// see github.com/rust-lang/rust/issues/133044#issuecomment-2500709754
TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => candidates
.iter()
.filter(|c| {
matches!(c.source, CandidateSource::AliasBound | CandidateSource::ParamEnv(_))
})
.map(|c| c.result)
.collect(),
TraitGoalProvenVia::ParamEnv | TraitGoalProvenVia::AliasBound => {
let mut candidates_from_env: Vec<_> = candidates
.iter()
.filter(|c| {
matches!(
c.source,
CandidateSource::AliasBound | CandidateSource::ParamEnv(_)
)
})
.map(|c| c.result)
.collect();

// If the trait goal has been proven by using the environment, we want to treat
// aliases as rigid if there are no applicable projection bounds in the environment.
if candidates_from_env.is_empty() {
if let Ok(response) = inject_normalize_to_rigid_candidate(self) {
candidates_from_env.push(response);
}
}
candidates_from_env
}
TraitGoalProvenVia::Misc => candidates.iter().map(|c| c.result).collect(),
};

Expand Down
17 changes: 16 additions & 1 deletion compiler/rustc_next_trait_solver/src/solve/effect_goals.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,12 +154,27 @@ where
let impl_trait_ref = impl_trait_ref.instantiate(cx, impl_args);

ecx.eq(goal.param_env, goal.predicate.trait_ref, impl_trait_ref)?;
// Resolve inference variables here to improve the debug output :)
let impl_trait_ref = ecx.resolve_vars_if_possible(impl_trait_ref);

let where_clause_bounds = cx
.predicates_of(impl_def_id)
.iter_instantiated(cx, impl_args)
.map(|pred| goal.with(cx, pred));
ecx.add_goals(GoalSource::ImplWhereBound, where_clause_bounds);

// When using an impl, we have to check that its super trait bounds are actually satisfied.
// This is necessary as otherwise the impl `impl<T: Magic> Magic for T` would allow us to
// incorrectly assume all super traits of `Magic`.
for clause in elaborate::elaborate(
cx,
cx.explicit_super_predicates_of(impl_trait_ref.def_id)
.iter_instantiated(cx, impl_trait_ref.args)
.map(|(pred, _)| pred),
) {
ecx.add_goal(GoalSource::Misc, goal.with(cx, clause));
}

// For this impl to be `const`, we need to check its `~const` bounds too.
let const_conditions = cx
.const_conditions(impl_def_id)
Expand Down Expand Up @@ -398,6 +413,6 @@ where
goal.with(ecx.cx(), goal.predicate.trait_ref);
ecx.compute_trait_goal(trait_goal)
})?;
self.merge_candidates(proven_via, candidates)
self.merge_candidates(proven_via, candidates, |_ecx| Err(NoSolution))
}
}
28 changes: 25 additions & 3 deletions compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ use rustc_type_ir::fold::{TypeFoldable, TypeFolder, TypeSuperFoldable};
use rustc_type_ir::inherent::*;
use rustc_type_ir::relate::Relate;
use rustc_type_ir::relate::solver_relating::RelateExt;
use rustc_type_ir::search_graph::PathKind;
use rustc_type_ir::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor};
use rustc_type_ir::{self as ty, CanonicalVarValues, InferCtxtLike, Interner, TypingMode};
use rustc_type_ir_macros::{Lift_Generic, TypeFoldable_Generic, TypeVisitable_Generic};
Expand Down Expand Up @@ -59,6 +60,12 @@ where
/// when then adds these to its own context. The caller is always an `AliasRelate`
/// goal so this never leaks out of the solver.
is_normalizes_to_goal: bool,

/// Whether the current `probe` should be treated as a coinductive step when encountering
/// trait solver cycles. This changes the step kind of all nested goals computed in that
/// probe to be coinductive.
is_coinductive_probe: bool,

pub(super) var_values: CanonicalVarValues<I>,

predefined_opaques_in_body: I::PredefinedOpaques,
Expand Down Expand Up @@ -230,6 +237,18 @@ where
self.is_normalizes_to_goal = true;
}

pub(super) fn step_kind_for_source(&self, source: GoalSource) -> PathKind {
if self.is_coinductive_probe {
return PathKind::Coinductive;
}

match source {
GoalSource::ImplWhereBound | GoalSource::AliasWellFormed => PathKind::Coinductive,
_ => PathKind::Inductive,

}
}

/// Creates a root evaluation context and search graph. This should only be
/// used from outside of any evaluation, and other methods should be preferred
/// over using this manually (such as [`SolverDelegateEvalExt::evaluate_root_goal`]).
Expand Down Expand Up @@ -257,6 +276,7 @@ where
variables: Default::default(),
var_values: CanonicalVarValues::dummy(),
is_normalizes_to_goal: false,
is_coinductive_probe: false,
origin_span,
tainted: Ok(()),
};
Expand Down Expand Up @@ -295,6 +315,7 @@ where
variables: canonical_input.canonical.variables,
var_values,
is_normalizes_to_goal: false,
is_coinductive_probe: false,
predefined_opaques_in_body: input.predefined_opaques_in_body,
max_input_universe: canonical_input.canonical.max_universe,
search_graph,
Expand Down Expand Up @@ -340,6 +361,7 @@ where
cx: I,
search_graph: &'a mut SearchGraph<D>,
canonical_input: CanonicalInput<I>,
step_kind_from_parent: PathKind,
goal_evaluation: &mut ProofTreeBuilder<D>,
) -> QueryResult<I> {
let mut canonical_goal_evaluation =
Expand All @@ -352,6 +374,7 @@ where
search_graph.with_new_goal(
cx,
canonical_input,
step_kind_from_parent,
&mut canonical_goal_evaluation,
|search_graph, canonical_goal_evaluation| {
EvalCtxt::enter_canonical(
Expand Down Expand Up @@ -395,12 +418,10 @@ where
/// `NormalizesTo` is only used by `AliasRelate`, all other callsites
/// should use [`EvalCtxt::evaluate_goal`] which discards that empty
/// storage.
// FIXME(-Znext-solver=coinduction): `_source` is currently unused but will
// be necessary once we implement the new coinduction approach.
pub(super) fn evaluate_goal_raw(
&mut self,
goal_evaluation_kind: GoalEvaluationKind,
_source: GoalSource,
source: GoalSource,
goal: Goal<I, I::Predicate>,
) -> Result<(NestedNormalizationGoals<I>, HasChanged, Certainty), NoSolution> {
let (orig_values, canonical_goal) = self.canonicalize_goal(goal);
Expand All @@ -410,6 +431,7 @@ where
self.cx(),
self.search_graph,
canonical_goal,
self.step_kind_for_source(source),
&mut goal_evaluation,
);
let response = match canonical_response {
Expand Down
36 changes: 36 additions & 0 deletions compiler/rustc_next_trait_solver/src/solve/eval_ctxt/probe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,42 @@ where
variables: outer_ecx.variables,
var_values: outer_ecx.var_values,
is_normalizes_to_goal: outer_ecx.is_normalizes_to_goal,
is_coinductive_probe: outer_ecx.is_coinductive_probe,
predefined_opaques_in_body: outer_ecx.predefined_opaques_in_body,
max_input_universe,
search_graph: outer_ecx.search_graph,
nested_goals: outer_ecx.nested_goals.clone(),
origin_span: outer_ecx.origin_span,
tainted: outer_ecx.tainted,
inspect: outer_ecx.inspect.take_and_enter_probe(),
};
let r = nested_ecx.delegate.probe(|| {
let r = f(&mut nested_ecx);
nested_ecx.inspect.probe_final_state(delegate, max_input_universe);
r
});
if !nested_ecx.inspect.is_noop() {
let probe_kind = probe_kind(&r);
nested_ecx.inspect.probe_kind(probe_kind);
outer_ecx.inspect = nested_ecx.inspect.finish_probe();
}
r
}

pub(in crate::solve) fn enter_coinductively(
self,
f: impl FnOnce(&mut EvalCtxt<'_, D>) -> T,
) -> T {
let ProbeCtxt { ecx: outer_ecx, probe_kind, _result } = self;

let delegate = outer_ecx.delegate;
let max_input_universe = outer_ecx.max_input_universe;
let mut nested_ecx = EvalCtxt {
delegate,
variables: outer_ecx.variables,
var_values: outer_ecx.var_values,
is_normalizes_to_goal: outer_ecx.is_normalizes_to_goal,
is_coinductive_probe: true,
predefined_opaques_in_body: outer_ecx.predefined_opaques_in_body,
max_input_universe,
search_graph: outer_ecx.search_graph,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ where
//
// FIXME(-Znext-solver=coinductive): I think this should be split
// and we tag the impl bounds with `GoalSource::ImplWhereBound`?
// Right not this includes both the impl and the assoc item where bounds,
// Right now this includes both the impl and the assoc item where bounds,
// and I don't think the assoc item where-bounds are allowed to be coinductive.
self.add_goals(
GoalSource::Misc,
Expand Down
Loading

0 comments on commit bbbbbb4

Please sign in to comment.