From 8e51d74a2188a1860ae2f6050c6a2616fa055a54 Mon Sep 17 00:00:00 2001 From: lcnr Date: Tue, 26 Nov 2024 16:22:33 +0100 Subject: [PATCH] add alias-relate fast path optimization --- .../src/solve/alias_relate.rs | 146 +++++++++++++++++- compiler/rustc_type_ir/src/inherent.rs | 8 + .../const-in-impl-fn-return-type.next.stderr | 19 ++- .../const-in-impl-fn-return-type.rs | 1 + 4 files changed, 170 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/solve/alias_relate.rs b/compiler/rustc_next_trait_solver/src/solve/alias_relate.rs index 0fc313e33b323..1a1eb939c23e2 100644 --- a/compiler/rustc_next_trait_solver/src/solve/alias_relate.rs +++ b/compiler/rustc_next_trait_solver/src/solve/alias_relate.rs @@ -15,12 +15,20 @@ //! (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::{self as ty, Interner}; +use rustc_type_ir::{ + self as ty, Interner, TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor, +}; 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 EvalCtxt<'_, D> where @@ -46,6 +54,12 @@ where || rhs.is_error() ); + 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); @@ -106,4 +120,132 @@ 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 = Default::default(); + self.collect_placeholders_in_term( + rigid_term, + IgnoreAliases::Yes, + &mut referenced_placeholders, + ); + if referenced_placeholders.is_empty() { + return false; + } + + let mut alias_placeholders = Default::default(); + self.collect_placeholders_in_term(alias, IgnoreAliases::No, &mut alias_placeholders); + loop { + let mut has_changed = false; + for clause in param_env.caller_bounds().iter() { + match clause.kind().skip_binder() { + ty::ClauseKind::Projection(ty::ProjectionPredicate { + projection_term, + term, + }) => { + let mut required_placeholders = Default::default(); + for term in projection_term.args.iter().filter_map(|arg| arg.as_term()) { + self.collect_placeholders_in_term( + term, + IgnoreAliases::Yes, + &mut required_placeholders, + ); + } + + if !required_placeholders.is_subset(&alias_placeholders) { + continue; + } + + if term.has_non_region_infer() { + return false; + } + + has_changed |= self.collect_placeholders_in_term( + term, + IgnoreAliases::No, + &mut alias_placeholders, + ); + } + ty::ClauseKind::Trait(_) + | ty::ClauseKind::HostEffect(_) + | ty::ClauseKind::TypeOutlives(_) + | ty::ClauseKind::RegionOutlives(_) + | ty::ClauseKind::ConstArgHasType(..) + | ty::ClauseKind::WellFormed(_) + | ty::ClauseKind::ConstEvaluatable(_) => continue, + } + } + + if !has_changed { + break; + } + } + // 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, + placeholders: &mut HashSet, + ) -> bool { + // Fast path to avoid walking the term. + if !term.has_placeholders() { + return false; + } + + struct PlaceholderCollector<'a, I: Interner> { + ignore_aliases: IgnoreAliases, + has_changed: bool, + placeholders: &'a mut HashSet, + } + impl TypeVisitor for PlaceholderCollector<'_, I> { + type Result = (); + + fn visit_ty(&mut self, t: I::Ty) { + match t.kind() { + ty::Placeholder(_) => self.has_changed |= 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(_) => { + self.has_changed |= 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, has_changed: false, placeholders }; + term.visit_with(&mut visitor); + visitor.has_changed + } } diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index 59c2d3c2fc8d9..3f594327b2b65 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -266,6 +266,14 @@ pub trait GenericArg>: + From + From { + fn as_term(&self) -> Option { + match self.kind() { + ty::GenericArgKind::Lifetime(_) => None, + ty::GenericArgKind::Type(ty) => Some(ty.into()), + ty::GenericArgKind::Const(ct) => Some(ct.into()), + } + } + fn as_type(&self) -> Option { if let ty::GenericArgKind::Type(ty) = self.kind() { Some(ty) } else { None } } diff --git a/tests/ui/typeck/issue-114918/const-in-impl-fn-return-type.next.stderr b/tests/ui/typeck/issue-114918/const-in-impl-fn-return-type.next.stderr index 92ad83c330000..ac7100ad1f5a3 100644 --- a/tests/ui/typeck/issue-114918/const-in-impl-fn-return-type.next.stderr +++ b/tests/ui/typeck/issue-114918/const-in-impl-fn-return-type.next.stderr @@ -4,6 +4,20 @@ error[E0308]: mismatched types LL | fn func() -> [(); { () }] { | ^^ expected `usize`, found `()` +error[E0271]: type mismatch resolving `N == { () }` + --> $DIR/const-in-impl-fn-return-type.rs:20:5 + | +LL | fn func() -> [(); { () }] { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ types differ + | +note: the requirement `N == { () }` appears on the `impl`'s method `func` but not on the corresponding trait's method + --> $DIR/const-in-impl-fn-return-type.rs:12:8 + | +LL | trait Trait { + | ----- in this trait +LL | fn func() -> [(); N]; + | ^^^^ this trait's method doesn't have the requirement `N == { () }` + error: the constant `N` is not of type `usize` --> $DIR/const-in-impl-fn-return-type.rs:12:32 | @@ -12,6 +26,7 @@ LL | fn func() -> [(); N]; | = note: the length of array `[(); N]` must be type `usize` -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors -For more information about this error, try `rustc --explain E0308`. +Some errors have detailed explanations: E0271, E0308. +For more information about an error, try `rustc --explain E0271`. diff --git a/tests/ui/typeck/issue-114918/const-in-impl-fn-return-type.rs b/tests/ui/typeck/issue-114918/const-in-impl-fn-return-type.rs index 6bbac9d45bbe5..5a188602847e6 100644 --- a/tests/ui/typeck/issue-114918/const-in-impl-fn-return-type.rs +++ b/tests/ui/typeck/issue-114918/const-in-impl-fn-return-type.rs @@ -19,6 +19,7 @@ struct S {} impl Trait for S { fn func() -> [(); { () }] { //~^ ERROR mismatched types + //[next]~| ERROR type mismatch resolving `N == { () }` N } }