Skip to content

Commit 4c0e9b9

Browse files
committed
add alias-relate fast path optimization
1 parent d6b0d0b commit 4c0e9b9

File tree

4 files changed

+170
-4
lines changed

4 files changed

+170
-4
lines changed

compiler/rustc_next_trait_solver/src/solve/alias_relate.rs

+144-2
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,20 @@
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::*;
19-
use rustc_type_ir::{self as ty, Interner};
20+
use rustc_type_ir::{
21+
self as ty, Interner, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor,
22+
};
2023
use tracing::{instrument, trace};
2124

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

2533
impl<D, I> EvalCtxt<'_, D>
2634
where
@@ -46,6 +54,12 @@ where
4654
|| rhs.is_error()
4755
);
4856

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

compiler/rustc_type_ir/src/inherent.rs

+8
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,14 @@ pub trait GenericArg<I: Interner<GenericArg = Self>>:
299299
+ From<I::Region>
300300
+ From<I::Const>
301301
{
302+
fn as_term(&self) -> Option<I::Term> {
303+
match self.kind() {
304+
ty::GenericArgKind::Lifetime(_) => None,
305+
ty::GenericArgKind::Type(ty) => Some(ty.into()),
306+
ty::GenericArgKind::Const(ct) => Some(ct.into()),
307+
}
308+
}
309+
302310
fn as_type(&self) -> Option<I::Ty> {
303311
if let ty::GenericArgKind::Type(ty) = self.kind() { Some(ty) } else { None }
304312
}

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)