Skip to content

Commit 17f3c98

Browse files
committed
add alias-relate fast path optimization
1 parent 5986ff0 commit 17f3c98

File tree

4 files changed

+168
-3
lines changed

4 files changed

+168
-3
lines changed

compiler/rustc_next_trait_solver/src/solve/alias_relate.rs

+142-1
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,19 @@
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::*;
20+
use rustc_type_ir::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor};
1921
use rustc_type_ir::{self as ty, Interner};
2022
use tracing::{instrument, trace};
2123

2224
use crate::delegate::SolverDelegate;
23-
use crate::solve::{Certainty, EvalCtxt, Goal, QueryResult};
25+
use crate::solve::{Certainty, EvalCtxt, Goal, NoSolution, QueryResult};
26+
27+
enum IgnoreAliases {
28+
Yes,
29+
No,
30+
}
2431

2532
impl<D, I> EvalCtxt<'_, D>
2633
where
@@ -46,6 +53,12 @@ where
4653
|| rhs.is_error()
4754
);
4855

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

compiler/rustc_type_ir/src/inherent.rs

+8
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,14 @@ pub trait GenericArg<I: Interner<GenericArg = Self>>:
319319
+ From<I::Region>
320320
+ From<I::Const>
321321
{
322+
fn as_term(&self) -> Option<I::Term> {
323+
match self.kind() {
324+
ty::GenericArgKind::Lifetime(_) => None,
325+
ty::GenericArgKind::Type(ty) => Some(ty.into()),
326+
ty::GenericArgKind::Const(ct) => Some(ct.into()),
327+
}
328+
}
329+
322330
fn as_type(&self) -> Option<I::Ty> {
323331
if let ty::GenericArgKind::Type(ty) = self.kind() { Some(ty) } else { None }
324332
}

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

+17-2
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

+1
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)