Skip to content

Commit 78efca8

Browse files
committed
Auto merge of rust-lang#117278 - lcnr:try-normalize-ty, r=compiler-errors
new solver normalization improvements cool beans At the core of this PR is a `try_normalize_ty` which stops for rigid aliases by using `commit_if_ok`. Reworks alias-relate to fully normalize both the lhs and rhs and then equate the resulting rigid (or inference) types. This fixes rust-lang/trait-system-refactor-initiative#68 by avoiding the exponential blowup. Also supersedes rust-lang#116369 by only defining opaque types if the hidden type is rigid. I removed the stability check in `EvalCtxt::evaluate_goal` due to rust-lang/trait-system-refactor-initiative#75. While I personally have opinions on how to fix it, that still requires further t-types/`@nikomatsakis` buy-in, so I removed that for now. Once we've decided on our approach there, we can revert this commit. r? `@compiler-errors`
2 parents ee5ef3a + fce71ad commit 78efca8

30 files changed

+410
-316
lines changed

compiler/rustc_middle/src/traits/solve/inspect.rs

+5
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,8 @@ pub enum ProbeStep<'tcx> {
122122
/// used whenever there are multiple candidates to prove the
123123
/// current goalby .
124124
NestedProbe(Probe<'tcx>),
125+
CommitIfOkStart,
126+
CommitIfOkSuccess,
125127
}
126128

127129
/// What kind of probe we're in. In case the probe represents a candidate, or
@@ -142,6 +144,9 @@ pub enum ProbeKind<'tcx> {
142144
/// Used in the probe that wraps normalizing the non-self type for the unsize
143145
/// trait, which is also structurally matched on.
144146
UnsizeAssembly,
147+
/// A call to `EvalCtxt::commit_if_ok` which failed, causing the work
148+
/// to be discarded.
149+
CommitIfOk,
145150
/// During upcasting from some source object to target object type, used to
146151
/// do a probe to find out what projection type(s) may be used to prove that
147152
/// the source type upholds all of the target type's object bounds.

compiler/rustc_middle/src/traits/solve/inspect/format.rs

+5
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,9 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
109109
ProbeKind::UpcastProjectionCompatibility => {
110110
writeln!(self.f, "PROBING FOR PROJECTION COMPATIBILITY FOR UPCASTING:")
111111
}
112+
ProbeKind::CommitIfOk => {
113+
writeln!(self.f, "COMMIT_IF_OK:")
114+
}
112115
ProbeKind::MiscCandidate { name, result } => {
113116
writeln!(self.f, "CANDIDATE {name}: {result:?}")
114117
}
@@ -123,6 +126,8 @@ impl<'a, 'b> ProofTreeFormatter<'a, 'b> {
123126
ProbeStep::AddGoal(goal) => writeln!(this.f, "ADDED GOAL: {goal:?}")?,
124127
ProbeStep::EvaluateGoals(eval) => this.format_added_goals_evaluation(eval)?,
125128
ProbeStep::NestedProbe(probe) => this.format_probe(probe)?,
129+
ProbeStep::CommitIfOkStart => writeln!(this.f, "COMMIT_IF_OK START")?,
130+
ProbeStep::CommitIfOkSuccess => writeln!(this.f, "COMMIT_IF_OK SUCCESS")?,
126131
}
127132
}
128133
Ok(())

compiler/rustc_middle/src/ty/sty.rs

+22
Original file line numberDiff line numberDiff line change
@@ -1245,6 +1245,28 @@ impl<'tcx> AliasTy<'tcx> {
12451245
}
12461246
}
12471247

1248+
/// Whether this alias type is an opaque.
1249+
pub fn is_opaque(self, tcx: TyCtxt<'tcx>) -> bool {
1250+
matches!(self.opt_kind(tcx), Some(ty::AliasKind::Opaque))
1251+
}
1252+
1253+
/// FIXME: rename `AliasTy` to `AliasTerm` and always handle
1254+
/// constants. This function can then be removed.
1255+
pub fn opt_kind(self, tcx: TyCtxt<'tcx>) -> Option<ty::AliasKind> {
1256+
match tcx.def_kind(self.def_id) {
1257+
DefKind::AssocTy
1258+
if let DefKind::Impl { of_trait: false } =
1259+
tcx.def_kind(tcx.parent(self.def_id)) =>
1260+
{
1261+
Some(ty::Inherent)
1262+
}
1263+
DefKind::AssocTy => Some(ty::Projection),
1264+
DefKind::OpaqueTy => Some(ty::Opaque),
1265+
DefKind::TyAlias => Some(ty::Weak),
1266+
_ => None,
1267+
}
1268+
}
1269+
12481270
pub fn to_ty(self, tcx: TyCtxt<'tcx>) -> Ty<'tcx> {
12491271
Ty::new_alias(tcx, self.kind(tcx), self)
12501272
}

compiler/rustc_trait_selection/src/solve/alias_relate.rs

+98-161
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,12 @@
1111
//! * bidirectional-normalizes-to: If `A` and `B` are both projections, and both
1212
//! may apply, then we can compute the "intersection" of both normalizes-to by
1313
//! performing them together. This is used specifically to resolve ambiguities.
14-
use super::{EvalCtxt, SolverMode};
14+
use super::EvalCtxt;
15+
use rustc_infer::infer::DefineOpaqueTypes;
1516
use rustc_infer::traits::query::NoSolution;
1617
use rustc_middle::traits::solve::{Certainty, Goal, QueryResult};
1718
use rustc_middle::ty;
1819

19-
/// We may need to invert the alias relation direction if dealing an alias on the RHS.
20-
#[derive(Debug)]
21-
enum Invert {
22-
No,
23-
Yes,
24-
}
25-
2620
impl<'tcx> EvalCtxt<'_, 'tcx> {
2721
#[instrument(level = "debug", skip(self), ret)]
2822
pub(super) fn compute_alias_relate_goal(
@@ -31,187 +25,130 @@ impl<'tcx> EvalCtxt<'_, 'tcx> {
3125
) -> QueryResult<'tcx> {
3226
let tcx = self.tcx();
3327
let Goal { param_env, predicate: (lhs, rhs, direction) } = goal;
34-
if lhs.is_infer() || rhs.is_infer() {
35-
bug!(
36-
"`AliasRelate` goal with an infer var on lhs or rhs which should have been instantiated"
37-
);
38-
}
39-
40-
match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) {
41-
(None, None) => bug!("`AliasRelate` goal without an alias on either lhs or rhs"),
4228

43-
// RHS is not a projection, only way this is true is if LHS normalizes-to RHS
44-
(Some(alias_lhs), None) => self.assemble_normalizes_to_candidate(
45-
param_env,
46-
alias_lhs,
47-
rhs,
48-
direction,
49-
Invert::No,
50-
),
29+
let Some(lhs) = self.try_normalize_term(param_env, lhs)? else {
30+
return self.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW);
31+
};
5132

52-
// LHS is not a projection, only way this is true is if RHS normalizes-to LHS
53-
(None, Some(alias_rhs)) => self.assemble_normalizes_to_candidate(
54-
param_env,
55-
alias_rhs,
56-
lhs,
57-
direction,
58-
Invert::Yes,
59-
),
33+
let Some(rhs) = self.try_normalize_term(param_env, rhs)? else {
34+
return self.evaluate_added_goals_and_make_canonical_response(Certainty::OVERFLOW);
35+
};
6036

61-
(Some(alias_lhs), Some(alias_rhs)) => {
62-
debug!("both sides are aliases");
37+
let variance = match direction {
38+
ty::AliasRelationDirection::Equate => ty::Variance::Invariant,
39+
ty::AliasRelationDirection::Subtype => ty::Variance::Covariant,
40+
};
6341

64-
let mut candidates = Vec::new();
65-
// LHS normalizes-to RHS
66-
candidates.extend(self.assemble_normalizes_to_candidate(
67-
param_env,
68-
alias_lhs,
69-
rhs,
70-
direction,
71-
Invert::No,
72-
));
73-
// RHS normalizes-to RHS
74-
candidates.extend(self.assemble_normalizes_to_candidate(
75-
param_env,
76-
alias_rhs,
77-
lhs,
78-
direction,
79-
Invert::Yes,
80-
));
81-
// Relate via args
82-
candidates.extend(
83-
self.assemble_subst_relate_candidate(
84-
param_env, alias_lhs, alias_rhs, direction,
85-
),
86-
);
87-
debug!(?candidates);
42+
match (lhs.to_alias_ty(tcx), rhs.to_alias_ty(tcx)) {
43+
(None, None) => {
44+
self.relate(param_env, lhs, variance, rhs)?;
45+
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
46+
}
8847

89-
if let Some(merged) = self.try_merge_responses(&candidates) {
90-
Ok(merged)
48+
(Some(alias), None) => {
49+
if rhs.is_infer() {
50+
self.relate(param_env, lhs, variance, rhs)?;
51+
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
52+
} else if alias.is_opaque(tcx) {
53+
self.define_opaque(param_env, alias, rhs)
9154
} else {
92-
// When relating two aliases and we have ambiguity, if both
93-
// aliases can be normalized to something, we prefer
94-
// "bidirectionally normalizing" both of them within the same
95-
// candidate.
96-
//
97-
// See <https://github.com/rust-lang/trait-system-refactor-initiative/issues/25>.
98-
//
99-
// As this is incomplete, we must not do so during coherence.
100-
match self.solver_mode() {
101-
SolverMode::Normal => {
102-
if let Ok(bidirectional_normalizes_to_response) = self
103-
.assemble_bidirectional_normalizes_to_candidate(
104-
param_env, lhs, rhs, direction,
105-
)
106-
{
107-
Ok(bidirectional_normalizes_to_response)
108-
} else {
109-
self.flounder(&candidates)
110-
}
111-
}
112-
SolverMode::Coherence => self.flounder(&candidates),
113-
}
55+
Err(NoSolution)
11456
}
11557
}
58+
(None, Some(alias)) => {
59+
if lhs.is_infer() {
60+
self.relate(param_env, lhs, variance, rhs)?;
61+
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
62+
} else if alias.is_opaque(tcx) {
63+
self.define_opaque(param_env, alias, lhs)
64+
} else {
65+
Err(NoSolution)
66+
}
67+
}
68+
69+
(Some(alias_lhs), Some(alias_rhs)) => {
70+
self.relate_rigid_alias_or_opaque(param_env, alias_lhs, variance, alias_rhs)
71+
}
11672
}
11773
}
11874

119-
#[instrument(level = "debug", skip(self), ret)]
120-
fn assemble_normalizes_to_candidate(
75+
/// Normalize the `term` to equate it later. This does not define opaque types.
76+
#[instrument(level = "debug", skip(self, param_env), ret)]
77+
fn try_normalize_term(
12178
&mut self,
12279
param_env: ty::ParamEnv<'tcx>,
123-
alias: ty::AliasTy<'tcx>,
124-
other: ty::Term<'tcx>,
125-
direction: ty::AliasRelationDirection,
126-
invert: Invert,
127-
) -> QueryResult<'tcx> {
128-
self.probe_misc_candidate("normalizes-to").enter(|ecx| {
129-
ecx.normalizes_to_inner(param_env, alias, other, direction, invert)?;
130-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
131-
})
80+
term: ty::Term<'tcx>,
81+
) -> Result<Option<ty::Term<'tcx>>, NoSolution> {
82+
match term.unpack() {
83+
ty::TermKind::Ty(ty) => {
84+
// We do no define opaque types here but instead do so in `relate_rigid_alias_or_opaque`.
85+
Ok(self
86+
.try_normalize_ty_recur(param_env, DefineOpaqueTypes::No, 0, ty)
87+
.map(Into::into))
88+
}
89+
ty::TermKind::Const(_) => {
90+
if let Some(alias) = term.to_alias_ty(self.tcx()) {
91+
let term = self.next_term_infer_of_kind(term);
92+
self.add_goal(Goal::new(
93+
self.tcx(),
94+
param_env,
95+
ty::ProjectionPredicate { projection_ty: alias, term },
96+
));
97+
self.try_evaluate_added_goals()?;
98+
Ok(Some(self.resolve_vars_if_possible(term)))
99+
} else {
100+
Ok(Some(term))
101+
}
102+
}
103+
}
132104
}
133105

134-
// Computes the normalizes-to branch, with side-effects. This must be performed
135-
// in a probe in order to not taint the evaluation context.
136-
fn normalizes_to_inner(
106+
fn define_opaque(
137107
&mut self,
138108
param_env: ty::ParamEnv<'tcx>,
139-
alias: ty::AliasTy<'tcx>,
140-
other: ty::Term<'tcx>,
141-
direction: ty::AliasRelationDirection,
142-
invert: Invert,
143-
) -> Result<(), NoSolution> {
144-
let other = match direction {
145-
// This is purely an optimization. No need to instantiate a new
146-
// infer var and equate the RHS to it.
147-
ty::AliasRelationDirection::Equate => other,
148-
149-
// Instantiate an infer var and subtype our RHS to it, so that we
150-
// properly represent a subtype relation between the LHS and RHS
151-
// of the goal.
152-
ty::AliasRelationDirection::Subtype => {
153-
let fresh = self.next_term_infer_of_kind(other);
154-
let (sub, sup) = match invert {
155-
Invert::No => (fresh, other),
156-
Invert::Yes => (other, fresh),
157-
};
158-
self.sub(param_env, sub, sup)?;
159-
fresh
160-
}
161-
};
109+
opaque: ty::AliasTy<'tcx>,
110+
term: ty::Term<'tcx>,
111+
) -> QueryResult<'tcx> {
162112
self.add_goal(Goal::new(
163113
self.tcx(),
164114
param_env,
165-
ty::ProjectionPredicate { projection_ty: alias, term: other },
115+
ty::ProjectionPredicate { projection_ty: opaque, term },
166116
));
167-
168-
Ok(())
117+
self.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
169118
}
170119

171-
fn assemble_subst_relate_candidate(
120+
fn relate_rigid_alias_or_opaque(
172121
&mut self,
173122
param_env: ty::ParamEnv<'tcx>,
174-
alias_lhs: ty::AliasTy<'tcx>,
175-
alias_rhs: ty::AliasTy<'tcx>,
176-
direction: ty::AliasRelationDirection,
123+
lhs: ty::AliasTy<'tcx>,
124+
variance: ty::Variance,
125+
rhs: ty::AliasTy<'tcx>,
177126
) -> QueryResult<'tcx> {
178-
self.probe_misc_candidate("args relate").enter(|ecx| {
179-
match direction {
180-
ty::AliasRelationDirection::Equate => {
181-
ecx.eq(param_env, alias_lhs, alias_rhs)?;
182-
}
183-
ty::AliasRelationDirection::Subtype => {
184-
ecx.sub(param_env, alias_lhs, alias_rhs)?;
185-
}
186-
}
127+
let tcx = self.tcx();
128+
let mut candidates = vec![];
129+
if lhs.is_opaque(tcx) {
130+
candidates.extend(
131+
self.probe_misc_candidate("define-lhs-opaque")
132+
.enter(|ecx| ecx.define_opaque(param_env, lhs, rhs.to_ty(tcx).into())),
133+
);
134+
}
187135

188-
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
189-
})
190-
}
136+
if rhs.is_opaque(tcx) {
137+
candidates.extend(
138+
self.probe_misc_candidate("define-rhs-opaque")
139+
.enter(|ecx| ecx.define_opaque(param_env, rhs, lhs.to_ty(tcx).into())),
140+
);
141+
}
191142

192-
fn assemble_bidirectional_normalizes_to_candidate(
193-
&mut self,
194-
param_env: ty::ParamEnv<'tcx>,
195-
lhs: ty::Term<'tcx>,
196-
rhs: ty::Term<'tcx>,
197-
direction: ty::AliasRelationDirection,
198-
) -> QueryResult<'tcx> {
199-
self.probe_misc_candidate("bidir normalizes-to").enter(|ecx| {
200-
ecx.normalizes_to_inner(
201-
param_env,
202-
lhs.to_alias_ty(ecx.tcx()).unwrap(),
203-
rhs,
204-
direction,
205-
Invert::No,
206-
)?;
207-
ecx.normalizes_to_inner(
208-
param_env,
209-
rhs.to_alias_ty(ecx.tcx()).unwrap(),
210-
lhs,
211-
direction,
212-
Invert::Yes,
213-
)?;
143+
candidates.extend(self.probe_misc_candidate("args-relate").enter(|ecx| {
144+
ecx.relate(param_env, lhs, variance, rhs)?;
214145
ecx.evaluate_added_goals_and_make_canonical_response(Certainty::Yes)
215-
})
146+
}));
147+
148+
if let Some(result) = self.try_merge_responses(&candidates) {
149+
Ok(result)
150+
} else {
151+
self.flounder(&candidates)
152+
}
216153
}
217154
}

0 commit comments

Comments
 (0)