Skip to content

Commit 01692cc

Browse files
committed
add alias-relate fast path optimization
1 parent 4f865bf commit 01692cc

File tree

3 files changed

+162
-4
lines changed

3 files changed

+162
-4
lines changed

compiler/rustc_next_trait_solver/src/solve/alias_relate.rs

Lines changed: 144 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,21 @@
1515
//! (3.) Otherwise, if we end with two rigid (non-projection) or infer types,
1616
//! relate them structurally.
1717
18+
use rustc_type_ir::data_structures::HashSet;
1819
use rustc_type_ir::inherent::*;
1920
use rustc_type_ir::solve::GoalSource;
20-
use rustc_type_ir::{self as ty, Interner};
21+
use rustc_type_ir::{
22+
self as ty, Interner, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
23+
};
2124
use tracing::{instrument, trace};
2225

2326
use crate::delegate::SolverDelegate;
24-
use crate::solve::{Certainty, EvalCtxt, Goal, QueryResult};
27+
use crate::solve::{Certainty, EvalCtxt, Goal, NoSolution, QueryResult};
28+
29+
enum IgnoreAliases {
30+
Yes,
31+
No,
32+
}
2533

2634
impl<D, I> EvalCtxt<'_, D>
2735
where
@@ -47,6 +55,12 @@ where
4755
|| rhs.is_error()
4856
);
4957

58+
if self.alias_cannot_name_placeholder_in_rigid(param_env, lhs, rhs)
59+
|| self.alias_cannot_name_placeholder_in_rigid(param_env, rhs, lhs)
60+
{
61+
return Err(NoSolution);
62+
}
63+
5064
// Structurally normalize the lhs.
5165
let lhs = if let Some(alias) = lhs.to_alias_term() {
5266
let term = self.next_term_infer_of_kind(lhs);
@@ -113,4 +127,132 @@ where
113127
}
114128
}
115129
}
130+
131+
/// In case a rigid term refers to a placeholder which is not referenced by the
132+
/// alias, the alias cannot be normalized to that rigid term unless it contains
133+
/// either inference variables or these placeholders are referenced in a term
134+
/// of a `Projection`-clause in the environment.
135+
fn alias_cannot_name_placeholder_in_rigid(
136+
&mut self,
137+
param_env: I::ParamEnv,
138+
rigid_term: I::Term,
139+
alias: I::Term,
140+
) -> bool {
141+
// Check that the rigid term is actually rigid.
142+
if rigid_term.to_alias_term().is_some() || alias.to_alias_term().is_none() {
143+
return false;
144+
}
145+
146+
// If the alias has any type or const inference variables,
147+
// do not try to apply the fast path as these inference variables
148+
// may resolve to something containing placeholders.
149+
if alias.has_non_region_infer() {
150+
return false;
151+
}
152+
153+
let mut referenced_placeholders = Default::default();
154+
self.collect_placeholders_in_term(
155+
rigid_term,
156+
IgnoreAliases::Yes,
157+
&mut referenced_placeholders,
158+
);
159+
if referenced_placeholders.is_empty() {
160+
return false;
161+
}
162+
163+
let mut alias_placeholders = Default::default();
164+
self.collect_placeholders_in_term(alias, IgnoreAliases::No, &mut alias_placeholders);
165+
loop {
166+
let mut has_changed = false;
167+
for clause in param_env.caller_bounds().iter() {
168+
match clause.kind().skip_binder() {
169+
ty::ClauseKind::Projection(ty::ProjectionPredicate {
170+
projection_term,
171+
term,
172+
}) => {
173+
let mut required_placeholders = Default::default();
174+
for term in projection_term.args.iter().filter_map(|arg| arg.as_term()) {
175+
self.collect_placeholders_in_term(
176+
term,
177+
IgnoreAliases::Yes,
178+
&mut required_placeholders,
179+
);
180+
}
181+
182+
if !required_placeholders.is_subset(&alias_placeholders) {
183+
continue;
184+
}
185+
186+
if term.has_non_region_infer() {
187+
return false;
188+
}
189+
190+
has_changed |= self.collect_placeholders_in_term(
191+
term,
192+
IgnoreAliases::No,
193+
&mut alias_placeholders,
194+
);
195+
}
196+
ty::ClauseKind::Trait(_)
197+
| ty::ClauseKind::HostEffect(_)
198+
| ty::ClauseKind::TypeOutlives(_)
199+
| ty::ClauseKind::RegionOutlives(_)
200+
| ty::ClauseKind::ConstArgHasType(..)
201+
| ty::ClauseKind::WellFormed(_)
202+
| ty::ClauseKind::ConstEvaluatable(_) => continue,
203+
}
204+
}
205+
206+
if !has_changed {
207+
break;
208+
}
209+
}
210+
// If the rigid term references a placeholder not mentioned by the alias,
211+
// they can never unify.
212+
!referenced_placeholders.is_subset(&alias_placeholders)
213+
}
214+
215+
fn collect_placeholders_in_term(
216+
&mut self,
217+
term: I::Term,
218+
ignore_aliases: IgnoreAliases,
219+
placeholders: &mut HashSet<I::Term>,
220+
) -> bool {
221+
// Fast path to avoid walking the term.
222+
if !term.has_placeholders() {
223+
return false;
224+
}
225+
226+
struct PlaceholderCollector<'a, I: Interner> {
227+
ignore_aliases: IgnoreAliases,
228+
has_changed: bool,
229+
placeholders: &'a mut HashSet<I::Term>,
230+
}
231+
impl<I: Interner> TypeVisitor<I> for PlaceholderCollector<'_, I> {
232+
type Result = ();
233+
234+
fn visit_ty(&mut self, t: I::Ty) {
235+
match t.kind() {
236+
ty::Placeholder(_) => self.has_changed |= self.placeholders.insert(t.into()),
237+
ty::Alias(..) if matches!(self.ignore_aliases, IgnoreAliases::Yes) => {}
238+
_ => t.super_visit_with(self),
239+
}
240+
}
241+
242+
fn visit_const(&mut self, ct: I::Const) {
243+
match ct.kind() {
244+
ty::ConstKind::Placeholder(_) => {
245+
self.has_changed |= self.placeholders.insert(ct.into())
246+
}
247+
ty::ConstKind::Unevaluated(_) | ty::ConstKind::Expr(_)
248+
if matches!(self.ignore_aliases, IgnoreAliases::Yes) => {}
249+
_ => ct.super_visit_with(self),
250+
}
251+
}
252+
}
253+
254+
let mut visitor = PlaceholderCollector { ignore_aliases, has_changed: false, placeholders };
255+
term.visit_with(&mut visitor);
256+
visitor.has_changed
257+
}
116258
}

tests/ui/typeck/issue-114918/const-in-impl-fn-return-type.next.stderr

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,20 @@ error[E0308]: mismatched types
44
LL | fn func<const N: u32>() -> [(); { () }] {
55
| ^^ expected `usize`, found `()`
66

7+
error[E0271]: type mismatch resolving `N == { () }`
8+
--> $DIR/const-in-impl-fn-return-type.rs:20:5
9+
|
10+
LL | fn func<const N: u32>() -> [(); { () }] {
11+
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ
12+
|
13+
note: the requirement `N == { () }` appears on the `impl`'s method `func` but not on the corresponding trait's method
14+
--> $DIR/const-in-impl-fn-return-type.rs:12:8
15+
|
16+
LL | trait Trait {
17+
| ----- in this trait
18+
LL | fn func<const N: u32>() -> [(); N];
19+
| ^^^^ this trait's method doesn't have the requirement `N == { () }`
20+
721
error: the constant `N` is not of type `usize`
822
--> $DIR/const-in-impl-fn-return-type.rs:12:32
923
|
@@ -12,6 +26,7 @@ LL | fn func<const N: u32>() -> [(); N];
1226
|
1327
= note: the length of array `[(); N]` must be type `usize`
1428

15-
error: aborting due to 2 previous errors
29+
error: aborting due to 3 previous errors
1630

17-
For more information about this error, try `rustc --explain E0308`.
31+
Some errors have detailed explanations: E0271, E0308.
32+
For more information about an error, try `rustc --explain E0271`.

tests/ui/typeck/issue-114918/const-in-impl-fn-return-type.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ struct S {}
1919
impl Trait for S {
2020
fn func<const N: u32>() -> [(); { () }] {
2121
//~^ ERROR mismatched types
22+
//[next]~| ERROR type mismatch resolving `N == { () }`
2223
N
2324
}
2425
}

0 commit comments

Comments
 (0)