diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2b6e96b467e35..23d3e71424b2b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -41,16 +41,24 @@ jobs: TOOLSTATE_REPO: "https://github.com/rust-lang-nursery/rust-toolstate" CACHE_DOMAIN: ci-caches.rust-lang.org if: "github.event_name == 'pull_request'" + continue-on-error: "${{ matrix.tidy }}" strategy: matrix: include: - name: mingw-check + tidy: false + os: ubuntu-20.04-xl + env: {} + - name: mingw-check-tidy + tidy: true os: ubuntu-20.04-xl env: {} - name: x86_64-gnu-llvm-13 + tidy: false os: ubuntu-20.04-xl env: {} - name: x86_64-gnu-tools + tidy: false env: CI_ONLY_WHEN_SUBMODULES_CHANGED: 1 os: ubuntu-20.04-xl diff --git a/compiler/rustc_hir_typeck/src/callee.rs b/compiler/rustc_hir_typeck/src/callee.rs index 4ec71a78a0031..af14ee08a9981 100644 --- a/compiler/rustc_hir_typeck/src/callee.rs +++ b/compiler/rustc_hir_typeck/src/callee.rs @@ -399,6 +399,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } ty::FnPtr(sig) => (sig, None), _ => { + for arg in arg_exprs { + self.check_expr(arg); + } + if let hir::ExprKind::Path(hir::QPath::Resolved(_, path)) = &callee_expr.kind && let [segment] = path.segments && let Some(mut diag) = self @@ -486,7 +490,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected: Expectation<'tcx>, ) -> Option> { if let [callee_expr, rest @ ..] = arg_exprs { - let callee_ty = self.check_expr(callee_expr); + let callee_ty = self.typeck_results.borrow().expr_ty_adjusted_opt(callee_expr)?; + // First, do a probe with `IsSuggestion(true)` to avoid emitting // any strange errors. If it's successful, then we'll do a true // method lookup. diff --git a/compiler/rustc_infer/src/infer/canonical/query_response.rs b/compiler/rustc_infer/src/infer/canonical/query_response.rs index 996b1c40e3fd6..a722613e3310e 100644 --- a/compiler/rustc_infer/src/infer/canonical/query_response.rs +++ b/compiler/rustc_infer/src/infer/canonical/query_response.rs @@ -151,7 +151,11 @@ impl<'tcx> InferCtxt<'tcx> { }) } - fn take_opaque_types_for_query_response(&self) -> Vec<(Ty<'tcx>, Ty<'tcx>)> { + /// FIXME: This method should only be used for canonical queries and therefore be private. + /// + /// As the new solver does canonicalization slightly differently, this is also used there + /// for now. This should hopefully change fairly soon. + pub fn take_opaque_types_for_query_response(&self) -> Vec<(Ty<'tcx>, Ty<'tcx>)> { self.inner .borrow_mut() .opaque_type_storage diff --git a/compiler/rustc_metadata/src/locator.rs b/compiler/rustc_metadata/src/locator.rs index 15546092e41a2..dda5f4bac4289 100644 --- a/compiler/rustc_metadata/src/locator.rs +++ b/compiler/rustc_metadata/src/locator.rs @@ -1011,11 +1011,7 @@ impl CrateError { sess.emit_err(SymbolConflictsOthers { span, crate_name: root_name }); } CrateError::StableCrateIdCollision(crate_name0, crate_name1) => { - sess.emit_err(StableCrateIdCollision { - span, - crate_name0: crate_name0, - crate_name1: crate_name1, - }); + sess.emit_err(StableCrateIdCollision { span, crate_name0, crate_name1 }); } CrateError::DlOpen(s) | CrateError::DlSym(s) => { sess.emit_err(DlError { span, err: s }); @@ -1074,7 +1070,7 @@ impl CrateError { } sess.emit_err(NoCrateWithTriple { span, - crate_name: crate_name, + crate_name, locator_triple: locator.triple.triple(), add_info, found_crates, diff --git a/compiler/rustc_metadata/src/rmeta/decoder.rs b/compiler/rustc_metadata/src/rmeta/decoder.rs index 4370d4bd758d2..99d8225a4c391 100644 --- a/compiler/rustc_metadata/src/rmeta/decoder.rs +++ b/compiler/rustc_metadata/src/rmeta/decoder.rs @@ -78,10 +78,6 @@ pub(crate) struct CrateMetadata { blob: MetadataBlob, // --- Some data pre-decoded from the metadata blob, usually for performance --- - /// NOTE(eddyb) we pass `'static` to a `'tcx` parameter because this - /// lifetime is only used behind `LazyValue`, `LazyArray`, or `LazyTable`, and therefore acts like a - /// universal (`for<'tcx>`), that is paired up with whichever `TyCtxt` - /// is being used to decode those values. root: CrateRoot, /// Trait impl data. /// FIXME: Used only from queries and can use query cache, @@ -688,10 +684,10 @@ impl MetadataBlob { pub(crate) fn get_root(&self) -> CrateRoot { let slice = &self.blob()[..]; let offset = METADATA_HEADER.len(); - let pos = (((slice[offset + 0] as u32) << 24) - | ((slice[offset + 1] as u32) << 16) - | ((slice[offset + 2] as u32) << 8) - | ((slice[offset + 3] as u32) << 0)) as usize; + + let pos_bytes = slice[offset..][..4].try_into().unwrap(); + let pos = u32::from_be_bytes(pos_bytes) as usize; + LazyValue::::from_position(NonZeroUsize::new(pos).unwrap()).decode(self) } @@ -702,16 +698,14 @@ impl MetadataBlob { writeln!(out, "hash {} stable_crate_id {:?}", root.hash, root.stable_crate_id)?; writeln!(out, "proc_macro {:?}", root.proc_macro_data.is_some())?; writeln!(out, "=External Dependencies=")?; + for (i, dep) in root.crate_deps.decode(self).enumerate() { + let CrateDep { name, extra_filename, hash, host_hash, kind } = dep; + let number = i + 1; + writeln!( out, - "{} {}{} hash {} host_hash {:?} kind {:?}", - i + 1, - dep.name, - dep.extra_filename, - dep.hash, - dep.host_hash, - dep.kind + "{number} {name}{extra_filename} hash {hash} host_hash {host_hash:?} kind {kind:?}" )?; } write!(out, "\n")?; diff --git a/compiler/rustc_metadata/src/rmeta/mod.rs b/compiler/rustc_metadata/src/rmeta/mod.rs index 6b60577c9023f..26a41f633fffa 100644 --- a/compiler/rustc_metadata/src/rmeta/mod.rs +++ b/compiler/rustc_metadata/src/rmeta/mod.rs @@ -416,11 +416,6 @@ struct VariantData { is_non_exhaustive: bool, } -#[derive(TyEncodable, TyDecodable)] -struct GeneratorData<'tcx> { - layout: mir::GeneratorLayout<'tcx>, -} - // Tags used for encoding Spans: const TAG_VALID_SPAN_LOCAL: u8 = 0; const TAG_VALID_SPAN_FOREIGN: u8 = 1; diff --git a/compiler/rustc_middle/src/infer/canonical.rs b/compiler/rustc_middle/src/infer/canonical.rs index 0331d764b38a1..0b32f67a81e16 100644 --- a/compiler/rustc_middle/src/infer/canonical.rs +++ b/compiler/rustc_middle/src/infer/canonical.rs @@ -300,6 +300,16 @@ impl<'tcx, V> Canonical<'tcx, V> { let Canonical { max_universe, variables, value } = self; Canonical { max_universe, variables, value: map_op(value) } } + + /// Allows you to map the `value` of a canonical while keeping the same set of + /// bound variables. + /// + /// **WARNING:** This function is very easy to mis-use, hence the name! See + /// the comment of [Canonical::unchecked_map] for more details. + pub fn unchecked_rebind(self, value: W) -> Canonical<'tcx, W> { + let Canonical { max_universe, variables, value: _ } = self; + Canonical { max_universe, variables, value } + } } pub type QueryOutlivesConstraint<'tcx> = ( diff --git a/compiler/rustc_middle/src/traits/query.rs b/compiler/rustc_middle/src/traits/query.rs index 7380c62a6693a..6a149be3137ee 100644 --- a/compiler/rustc_middle/src/traits/query.rs +++ b/compiler/rustc_middle/src/traits/query.rs @@ -96,7 +96,7 @@ pub type CanonicalTypeOpProvePredicateGoal<'tcx> = pub type CanonicalTypeOpNormalizeGoal<'tcx, T> = Canonical<'tcx, ty::ParamEnvAnd<'tcx, type_op::Normalize>>; -#[derive(Copy, Clone, Debug, HashStable)] +#[derive(Copy, Clone, Debug, HashStable, PartialEq, Eq)] pub struct NoSolution; pub type Fallible = Result; diff --git a/compiler/rustc_middle/src/traits/specialization_graph.rs b/compiler/rustc_middle/src/traits/specialization_graph.rs index cccedc9ec6ea9..a4dd22801e69d 100644 --- a/compiler/rustc_middle/src/traits/specialization_graph.rs +++ b/compiler/rustc_middle/src/traits/specialization_graph.rs @@ -180,6 +180,7 @@ impl Iterator for Ancestors<'_> { } /// Information about the most specialized definition of an associated item. +#[derive(Debug)] pub struct LeafDef { /// The associated item described by this `LeafDef`. pub item: ty::AssocItem, diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index a8e1253e67057..f01d74539a12e 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -535,6 +535,17 @@ impl<'tcx> Predicate<'tcx> { self } + #[instrument(level = "debug", skip(tcx), ret)] + pub fn is_coinductive(self, tcx: TyCtxt<'tcx>) -> bool { + match self.kind().skip_binder() { + ty::PredicateKind::Clause(ty::Clause::Trait(data)) => { + tcx.trait_is_coinductive(data.def_id()) + } + ty::PredicateKind::WellFormed(_) => true, + _ => false, + } + } + /// Whether this projection can be soundly normalized. /// /// Wf predicates must not be normalized, as normalization @@ -1018,6 +1029,24 @@ pub struct ProjectionPredicate<'tcx> { pub term: Term<'tcx>, } +impl<'tcx> ProjectionPredicate<'tcx> { + pub fn self_ty(self) -> Ty<'tcx> { + self.projection_ty.self_ty() + } + + pub fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> ProjectionPredicate<'tcx> { + Self { projection_ty: self.projection_ty.with_self_ty(tcx, self_ty), ..self } + } + + pub fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId { + self.projection_ty.trait_def_id(tcx) + } + + pub fn def_id(self) -> DefId { + self.projection_ty.def_id + } +} + pub type PolyProjectionPredicate<'tcx> = Binder<'tcx, ProjectionPredicate<'tcx>>; impl<'tcx> PolyProjectionPredicate<'tcx> { @@ -1054,18 +1083,6 @@ impl<'tcx> PolyProjectionPredicate<'tcx> { } } -impl<'tcx> ProjectionPredicate<'tcx> { - pub fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self { - Self { - projection_ty: tcx.mk_alias_ty( - self.projection_ty.def_id, - [self_ty.into()].into_iter().chain(self.projection_ty.substs.iter().skip(1)), - ), - ..self - } - } -} - pub trait ToPolyTraitRef<'tcx> { fn to_poly_trait_ref(&self) -> PolyTraitRef<'tcx>; } diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index e13b68c83b57c..f7e4c8215698e 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -1169,7 +1169,7 @@ pub struct AliasTy<'tcx> { } impl<'tcx> AliasTy<'tcx> { - pub fn trait_def_id(&self, tcx: TyCtxt<'tcx>) -> DefId { + pub fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId { match tcx.def_kind(self.def_id) { DefKind::AssocTy | DefKind::AssocConst => tcx.parent(self.def_id), DefKind::ImplTraitPlaceholder => { @@ -1183,7 +1183,7 @@ impl<'tcx> AliasTy<'tcx> { /// For example, if this is a projection of `::Item<'a>`, /// then this function would return a `T: Iterator` trait reference and `['a]` as the own substs pub fn trait_ref_and_own_substs( - &self, + self, tcx: TyCtxt<'tcx>, ) -> (ty::TraitRef<'tcx>, &'tcx [ty::GenericArg<'tcx>]) { debug_assert!(matches!(tcx.def_kind(self.def_id), DefKind::AssocTy | DefKind::AssocConst)); @@ -1202,14 +1202,18 @@ impl<'tcx> AliasTy<'tcx> { /// WARNING: This will drop the substs for generic associated types /// consider calling [Self::trait_ref_and_own_substs] to get those /// as well. - pub fn trait_ref(&self, tcx: TyCtxt<'tcx>) -> ty::TraitRef<'tcx> { + pub fn trait_ref(self, tcx: TyCtxt<'tcx>) -> ty::TraitRef<'tcx> { let def_id = self.trait_def_id(tcx); tcx.mk_trait_ref(def_id, self.substs.truncate_to(tcx, tcx.generics_of(def_id))) } - pub fn self_ty(&self) -> Ty<'tcx> { + pub fn self_ty(self) -> Ty<'tcx> { self.substs.type_at(0) } + + pub fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self { + tcx.mk_alias_ty(self.def_id, [self_ty.into()].into_iter().chain(self.substs.iter().skip(1))) + } } #[derive(Copy, Clone, Debug, TypeFoldable, TypeVisitable, Lift)] diff --git a/compiler/rustc_middle/src/ty/subst.rs b/compiler/rustc_middle/src/ty/subst.rs index cbab4a16e94b6..0c33e5bda1a55 100644 --- a/compiler/rustc_middle/src/ty/subst.rs +++ b/compiler/rustc_middle/src/ty/subst.rs @@ -577,6 +577,10 @@ impl EarlyBinder { pub fn rebind(&self, value: U) -> EarlyBinder { EarlyBinder(value) } + + pub fn skip_binder(self) -> T { + self.0 + } } impl EarlyBinder> { diff --git a/compiler/rustc_trait_selection/src/lib.rs b/compiler/rustc_trait_selection/src/lib.rs index 975ff31a60788..a30d1df4ede52 100644 --- a/compiler/rustc_trait_selection/src/lib.rs +++ b/compiler/rustc_trait_selection/src/lib.rs @@ -19,6 +19,7 @@ #![feature(let_chains)] #![feature(if_let_guard)] #![feature(never_type)] +#![feature(result_option_inspect)] #![feature(type_alias_impl_trait)] #![recursion_limit = "512"] // For rustdoc @@ -37,4 +38,5 @@ extern crate smallvec; pub mod autoderef; pub mod errors; pub mod infer; +pub mod solve; pub mod traits; diff --git a/compiler/rustc_trait_selection/src/solve/assembly.rs b/compiler/rustc_trait_selection/src/solve/assembly.rs new file mode 100644 index 0000000000000..e9ddad11ff23e --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/assembly.rs @@ -0,0 +1,150 @@ +//! Code shared by trait and projection goals for candidate assembly. + +use super::infcx_ext::InferCtxtExt; +use super::{ + fixme_instantiate_canonical_query_response, CanonicalGoal, CanonicalResponse, Certainty, + EvalCtxt, Goal, +}; +use rustc_hir::def_id::DefId; +use rustc_infer::infer::TyCtxtInferExt; +use rustc_infer::infer::{ + canonical::{CanonicalVarValues, OriginalQueryValues}, + InferCtxt, +}; +use rustc_infer::traits::query::NoSolution; +use rustc_middle::ty::TypeFoldable; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_span::DUMMY_SP; +use std::fmt::Debug; + +/// A candidate is a possible way to prove a goal. +/// +/// It consists of both the `source`, which describes how that goal would be proven, +/// and the `result` when using the given `source`. +/// +/// For the list of possible candidates, please look at the documentation of +/// [super::trait_goals::CandidateSource] and [super::project_goals::CandidateSource]. +#[derive(Debug, Clone)] +pub(super) struct Candidate<'tcx, G: GoalKind<'tcx>> { + pub(super) source: G::CandidateSource, + pub(super) result: CanonicalResponse<'tcx>, +} + +pub(super) trait GoalKind<'tcx>: TypeFoldable<'tcx> + Copy { + type CandidateSource: Debug + Copy; + + fn self_ty(self) -> Ty<'tcx>; + + fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self; + + fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId; + + fn consider_impl_candidate( + acx: &mut AssemblyCtxt<'_, 'tcx, Self>, + goal: Goal<'tcx, Self>, + impl_def_id: DefId, + ); +} + +/// An abstraction which correctly deals with the canonical results for candidates. +/// +/// It also deduplicates the behavior between trait and projection predicates. +pub(super) struct AssemblyCtxt<'a, 'tcx, G: GoalKind<'tcx>> { + pub(super) cx: &'a mut EvalCtxt<'tcx>, + pub(super) infcx: &'a InferCtxt<'tcx>, + var_values: CanonicalVarValues<'tcx>, + candidates: Vec>, +} + +impl<'a, 'tcx, G: GoalKind<'tcx>> AssemblyCtxt<'a, 'tcx, G> { + pub(super) fn assemble_and_evaluate_candidates( + cx: &'a mut EvalCtxt<'tcx>, + goal: CanonicalGoal<'tcx, G>, + ) -> Vec> { + let (ref infcx, goal, var_values) = + cx.tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &goal); + let mut acx = AssemblyCtxt { cx, infcx, var_values, candidates: Vec::new() }; + + acx.assemble_candidates_after_normalizing_self_ty(goal); + + acx.assemble_impl_candidates(goal); + + acx.candidates + } + + pub(super) fn try_insert_candidate( + &mut self, + source: G::CandidateSource, + certainty: Certainty, + ) { + match self.infcx.make_canonical_response(self.var_values.clone(), certainty) { + Ok(result) => self.candidates.push(Candidate { source, result }), + Err(NoSolution) => debug!(?source, ?certainty, "failed leakcheck"), + } + } + + /// If the self type of a goal is a projection, computing the relevant candidates is difficult. + /// + /// To deal with this, we first try to normalize the self type and add the candidates for the normalized + /// self type to the list of candidates in case that succeeds. Note that we can't just eagerly return in + /// this case as projections as self types add ` + fn assemble_candidates_after_normalizing_self_ty(&mut self, goal: Goal<'tcx, G>) { + let tcx = self.cx.tcx; + // FIXME: We also have to normalize opaque types, not sure where to best fit that in. + let &ty::Alias(ty::Projection, projection_ty) = goal.predicate.self_ty().kind() else { + return + }; + self.infcx.probe(|_| { + let normalized_ty = self.infcx.next_ty_infer(); + let normalizes_to_goal = goal.with( + tcx, + ty::Binder::dummy(ty::ProjectionPredicate { + projection_ty, + term: normalized_ty.into(), + }), + ); + let normalization_certainty = + match self.cx.evaluate_goal(&self.infcx, normalizes_to_goal) { + Ok((_, certainty)) => certainty, + Err(NoSolution) => return, + }; + + // NOTE: Alternatively we could call `evaluate_goal` here and only have a `Normalized` candidate. + // This doesn't work as long as we use `CandidateSource` in both winnowing and to resolve associated items. + let goal = goal.with(tcx, goal.predicate.with_self_ty(tcx, normalized_ty)); + let mut orig_values = OriginalQueryValues::default(); + let goal = self.infcx.canonicalize_query(goal, &mut orig_values); + let normalized_candidates = + AssemblyCtxt::assemble_and_evaluate_candidates(self.cx, goal); + + // Map each candidate from being canonical wrt the current inference context to being + // canonical wrt the caller. + for Candidate { source, result } in normalized_candidates { + self.infcx.probe(|_| { + let candidate_certainty = fixme_instantiate_canonical_query_response( + &self.infcx, + &orig_values, + result, + ); + + // FIXME: This is a bit scary if the `normalizes_to_goal` overflows. + // + // If we have an ambiguous candidate it hides that normalization + // caused an overflow which may cause issues. + self.try_insert_candidate( + source, + normalization_certainty.unify_and(candidate_certainty), + ) + }) + } + }) + } + + fn assemble_impl_candidates(&mut self, goal: Goal<'tcx, G>) { + self.cx.tcx.for_each_relevant_impl( + goal.predicate.trait_def_id(self.cx.tcx), + goal.predicate.self_ty(), + |impl_def_id| G::consider_impl_candidate(self, goal, impl_def_id), + ); + } +} diff --git a/compiler/rustc_trait_selection/src/solve/cache.rs b/compiler/rustc_trait_selection/src/solve/cache.rs new file mode 100644 index 0000000000000..993b79890669c --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/cache.rs @@ -0,0 +1,257 @@ +//! This module both handles the global cache which stores "finished" goals, +//! and the provisional cache which contains partially computed goals. +//! +//! The provisional cache is necessary when dealing with coinductive cycles. +//! +//! For more information about the provisional cache and coinduction in general, +//! check out the relevant section of the rustc-dev-guide. +//! +//! FIXME(@lcnr): Write that section, feel free to ping me if you need help here +//! before then or if I still haven't done that before January 2023. +use super::overflow::OverflowData; +use super::CanonicalGoal; +use super::{EvalCtxt, QueryResult}; + +use rustc_data_structures::fx::FxHashMap; +use rustc_middle::ty::TyCtxt; +use std::{cmp::Ordering, collections::hash_map::Entry}; + +#[derive(Debug, Clone)] +struct ProvisionalEntry<'tcx> { + // In case we have a coinductive cycle, this is the + // the currently least restrictive result of this goal. + response: QueryResult<'tcx>, + // The lowest element on the stack on which this result + // relies on. Starts out as just being the depth at which + // we've proven this obligation, but gets lowered to the + // depth of another goal if we rely on it in a cycle. + depth: usize, +} + +struct StackElem<'tcx> { + goal: CanonicalGoal<'tcx>, + has_been_used: bool, +} + +/// The cache used for goals which are currently in progress or which depend +/// on in progress results. +/// +/// Once we're done with a goal we can store it in the global trait solver +/// cache of the `TyCtxt`. For goals which we're currently proving, or which +/// have only been proven via a coinductive cycle using a goal still on our stack +/// we have to use this separate data structure. +/// +/// The current data structure is not perfect, so there may still be room for +/// improvement here. We have the following requirements: +/// +/// ## Is there is a provisional entry for the given goal: +/// +/// ```ignore (for syntax highlighting) +/// self.entries.get(goal) +/// ``` +/// +/// ## Get all goals on the stack involved in a cycle: +/// +/// ```ignore (for syntax highlighting) +/// let entry = self.entries.get(goal).unwrap(); +/// let involved_goals = self.stack.iter().skip(entry.depth); +/// ``` +/// +/// ## Capping the depth of all entries +/// +/// Needed whenever we encounter a cycle. The current implementation always +/// iterates over all entries instead of only the ones with a larger depth. +/// Changing this may result in notable performance improvements. +/// +/// ```ignore (for syntax highlighting) +/// let cycle_depth = self.entries.get(goal).unwrap().depth; +/// for e in &mut self.entries { +/// e.depth = e.depth.min(cycle_depth); +/// } +/// ``` +/// +/// ## Checking whether we have to rerun the current goal +/// +/// A goal has to be rerun if its provisional result was used in a cycle +/// and that result is different from its final result. We update +/// [StackElem::has_been_used] for the deepest stack element involved in a cycle. +/// +/// ## Moving all finished goals into the global cache +/// +/// If `stack_elem.has_been_used` is true, iterate over all entries, moving the ones +/// with equal depth. If not, simply move this single entry. +pub(super) struct ProvisionalCache<'tcx> { + stack: Vec>, + entries: FxHashMap, ProvisionalEntry<'tcx>>, +} + +impl<'tcx> ProvisionalCache<'tcx> { + pub(super) fn empty() -> ProvisionalCache<'tcx> { + ProvisionalCache { stack: Vec::new(), entries: Default::default() } + } + + pub(super) fn current_depth(&self) -> usize { + self.stack.len() + } +} + +impl<'tcx> EvalCtxt<'tcx> { + /// Tries putting the new goal on the stack, returning an error if it is already cached. + /// + /// This correctly updates the provisional cache if there is a cycle. + pub(super) fn try_push_stack( + &mut self, + goal: CanonicalGoal<'tcx>, + ) -> Result<(), QueryResult<'tcx>> { + // FIXME: start by checking the global cache + + // Look at the provisional cache to check for cycles. + let cache = &mut self.provisional_cache; + match cache.entries.entry(goal) { + // No entry, simply push this goal on the stack after dealing with overflow. + Entry::Vacant(v) => { + if self.overflow_data.has_overflow(cache.stack.len()) { + return Err(self.deal_with_overflow()); + } + + v.insert(ProvisionalEntry { + response: fixme_response_yes_no_constraints(), + depth: cache.stack.len(), + }); + cache.stack.push(StackElem { goal, has_been_used: false }); + Ok(()) + } + // We have a nested goal which relies on a goal `root` deeper in the stack. + // + // We first store that we may have to rerun `evaluate_goal` for `root` in case the + // provisional response is not equal to the final response. We also update the depth + // of all goals which recursively depend on our current goal to depend on `root` + // instead. + // + // Finally we can return either the provisional response for that goal if we have a + // coinductive cycle or an ambiguous result if the cycle is inductive. + Entry::Occupied(entry) => { + // FIXME: `ProvisionalEntry` should be `Copy`. + let entry = entry.get().clone(); + cache.stack[entry.depth].has_been_used = true; + for provisional_entry in cache.entries.values_mut() { + provisional_entry.depth = provisional_entry.depth.min(entry.depth); + } + + // NOTE: The goals on the stack aren't the only goals involved in this cycle. + // We can also depend on goals which aren't part of the stack but coinductively + // depend on the stack themselves. We already checked whether all the goals + // between these goals and their root on the stack. This means that as long as + // each goal in a cycle is checked for coinductivity by itself simply checking + // the stack is enough. + if cache.stack[entry.depth..] + .iter() + .all(|g| g.goal.value.predicate.is_coinductive(self.tcx)) + { + Err(entry.response) + } else { + Err(fixme_response_maybe_no_constraints()) + } + } + } + } + + /// We cannot simply store the result of [EvalCtxt::compute_goal] as we have to deal with + /// coinductive cycles. + /// + /// When we encounter a coinductive cycle, we have to prove the final result of that cycle + /// while we are still computing that result. Because of this we continously recompute the + /// cycle until the result of the previous iteration is equal to the final result, at which + /// point we are done. + /// + /// This function returns `true` if we were able to finalize the goal and `false` if it has + /// updated the provisional cache and we have to recompute the current goal. + /// + /// FIXME: Refer to the rustc-dev-guide entry once it exists. + pub(super) fn try_finalize_goal( + &mut self, + actual_goal: CanonicalGoal<'tcx>, + response: QueryResult<'tcx>, + ) -> bool { + let cache = &mut self.provisional_cache; + let StackElem { goal, has_been_used } = cache.stack.pop().unwrap(); + assert_eq!(goal, actual_goal); + + let provisional_entry = cache.entries.get_mut(&goal).unwrap(); + // Check whether the current stack entry is the root of a cycle. + // + // If so, we either move all participants of that cycle to the global cache + // or, in case the provisional response used in the cycle is not equal to the + // final response, have to recompute the goal after updating the provisional + // response to the final response of this iteration. + if has_been_used { + if provisional_entry.response == response { + // We simply drop all entries according to an immutable condition, so + // query instability is not a concern here. + #[allow(rustc::potential_query_instability)] + cache.entries.retain(|goal, entry| match entry.depth.cmp(&cache.stack.len()) { + Ordering::Less => true, + Ordering::Equal => { + Self::try_move_finished_goal_to_global_cache( + self.tcx, + &mut self.overflow_data, + &cache.stack, + // FIXME: these should be `Copy` :( + goal.clone(), + entry.response.clone(), + ); + false + } + Ordering::Greater => bug!("entry with greater depth than the current leaf"), + }); + + true + } else { + provisional_entry.response = response; + cache.stack.push(StackElem { goal, has_been_used: false }); + false + } + } else { + Self::try_move_finished_goal_to_global_cache( + self.tcx, + &mut self.overflow_data, + &cache.stack, + goal, + response, + ); + cache.entries.remove(&goal); + true + } + } + + fn try_move_finished_goal_to_global_cache( + tcx: TyCtxt<'tcx>, + overflow_data: &mut OverflowData, + stack: &[StackElem<'tcx>], + goal: CanonicalGoal<'tcx>, + response: QueryResult<'tcx>, + ) { + // We move goals to the global cache if we either did not hit an overflow or if it's + // the root goal as that will now always hit the same overflow limit. + // + // NOTE: We cannot move any non-root goals to the global cache even if their final result + // isn't impacted by the overflow as that goal still has unstable query dependencies + // because it didn't go its full depth. + // + // FIXME(@lcnr): We could still cache subtrees which are not impacted by overflow though. + // Tracking that info correctly isn't trivial, so I haven't implemented it for now. + let should_cache_globally = !overflow_data.did_overflow() || stack.is_empty(); + if should_cache_globally { + // FIXME: move the provisional entry to the global cache. + let _ = (tcx, goal, response); + } + } +} + +fn fixme_response_yes_no_constraints<'tcx>() -> QueryResult<'tcx> { + unimplemented!() +} + +fn fixme_response_maybe_no_constraints<'tcx>() -> QueryResult<'tcx> { + unimplemented!() +} diff --git a/compiler/rustc_trait_selection/src/solve/fulfill.rs b/compiler/rustc_trait_selection/src/solve/fulfill.rs new file mode 100644 index 0000000000000..80115d78d88d1 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/fulfill.rs @@ -0,0 +1,92 @@ +use std::mem; + +use rustc_data_structures::fx::FxHashMap; +use rustc_infer::{ + infer::InferCtxt, + traits::{query::NoSolution, FulfillmentError, PredicateObligation, TraitEngine}, +}; +use rustc_middle::ty; + +use super::{Certainty, EvalCtxt}; + +/// A trait engine using the new trait solver. +/// +/// This is mostly identical to how `evaluate_all` works inside of the +/// solver, except that the requirements are slightly different. +/// +/// Unlike `evaluate_all` it is possible to add new obligations later on +/// and we also have to track diagnostics information by using `Obligation` +/// instead of `Goal`. +/// +/// It is also likely that we want to use slightly different datastructures +/// here as this will have to deal with far more root goals than `evaluate_all`. +pub struct FulfillmentCtxt<'tcx> { + obligations: Vec>, +} + +impl<'tcx> FulfillmentCtxt<'tcx> { + pub fn new() -> FulfillmentCtxt<'tcx> { + FulfillmentCtxt { obligations: Vec::new() } + } +} + +impl<'tcx> TraitEngine<'tcx> for FulfillmentCtxt<'tcx> { + fn register_predicate_obligation( + &mut self, + _infcx: &InferCtxt<'tcx>, + obligation: PredicateObligation<'tcx>, + ) { + self.obligations.push(obligation); + } + + fn select_all_or_error(&mut self, infcx: &InferCtxt<'tcx>) -> Vec> { + let errors = self.select_where_possible(infcx); + if !errors.is_empty() { + return errors; + } + + if self.obligations.is_empty() { + Vec::new() + } else { + unimplemented!("ambiguous obligations") + } + } + + fn select_where_possible(&mut self, infcx: &InferCtxt<'tcx>) -> Vec> { + let errors = Vec::new(); + for i in 0.. { + if !infcx.tcx.recursion_limit().value_within_limit(i) { + unimplemented!("overflow") + } + + let mut has_changed = false; + for o in mem::take(&mut self.obligations) { + let mut cx = EvalCtxt::new(infcx.tcx); + let (changed, certainty) = match cx.evaluate_goal(infcx, o.clone().into()) { + Ok(result) => result, + Err(NoSolution) => unimplemented!("error"), + }; + + has_changed |= changed; + match certainty { + Certainty::Yes => {} + Certainty::Maybe(_) => self.obligations.push(o), + } + } + + if !has_changed { + break; + } + } + + errors + } + + fn pending_obligations(&self) -> Vec> { + self.obligations.clone() + } + + fn relationships(&mut self) -> &mut FxHashMap { + unimplemented!("Should be moved out of `TraitEngine`") + } +} diff --git a/compiler/rustc_trait_selection/src/solve/infcx_ext.rs b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs new file mode 100644 index 0000000000000..436f4eea6625b --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/infcx_ext.rs @@ -0,0 +1,55 @@ +use rustc_infer::infer::canonical::CanonicalVarValues; +use rustc_infer::infer::type_variable::{TypeVariableOrigin, TypeVariableOriginKind}; +use rustc_infer::infer::InferCtxt; +use rustc_infer::traits::query::NoSolution; +use rustc_middle::ty::Ty; +use rustc_span::DUMMY_SP; + +use crate::solve::ExternalConstraints; + +use super::{Certainty, QueryResult, Response}; + +/// Methods used inside of the canonical queries of the solver. +pub(super) trait InferCtxtExt<'tcx> { + fn next_ty_infer(&self) -> Ty<'tcx>; + + fn make_canonical_response( + &self, + var_values: CanonicalVarValues<'tcx>, + certainty: Certainty, + ) -> QueryResult<'tcx>; +} + +impl<'tcx> InferCtxtExt<'tcx> for InferCtxt<'tcx> { + fn next_ty_infer(&self) -> Ty<'tcx> { + self.next_ty_var(TypeVariableOrigin { + kind: TypeVariableOriginKind::MiscVariable, + span: DUMMY_SP, + }) + } + + fn make_canonical_response( + &self, + var_values: CanonicalVarValues<'tcx>, + certainty: Certainty, + ) -> QueryResult<'tcx> { + let external_constraints = take_external_constraints(self)?; + + Ok(self.canonicalize_response(Response { var_values, external_constraints, certainty })) + } +} + +#[instrument(level = "debug", skip(infcx), ret)] +fn take_external_constraints<'tcx>( + infcx: &InferCtxt<'tcx>, +) -> Result, NoSolution> { + let region_obligations = infcx.take_registered_region_obligations(); + let opaque_types = infcx.take_opaque_types_for_query_response(); + Ok(ExternalConstraints { + // FIXME: Now that's definitely wrong :) + // + // Should also do the leak check here I think + regions: drop(region_obligations), + opaque_types, + }) +} diff --git a/compiler/rustc_trait_selection/src/solve/mod.rs b/compiler/rustc_trait_selection/src/solve/mod.rs new file mode 100644 index 0000000000000..7f5e3208f4e7c --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/mod.rs @@ -0,0 +1,309 @@ +//! The new trait solver, currently still WIP. +//! +//! As a user of the trait system, you can use `TyCtxt::evaluate_goal` to +//! interact with this solver. +//! +//! For a high-level overview of how this solver works, check out the relevant +//! section of the rustc-dev-guide. +//! +//! FIXME(@lcnr): Write that section. If you read this before then ask me +//! about it on zulip. + +// FIXME: Instead of using `infcx.canonicalize_query` we have to add a new routine which +// preserves universes and creates a unique var (in the highest universe) for each +// appearance of a region. + +// FIXME: `CanonicalVarValues` should be interned and `Copy`. + +// FIXME: uses of `infcx.at` need to enable deferred projection equality once that's implemented. + +use std::mem; + +use rustc_infer::infer::canonical::OriginalQueryValues; +use rustc_infer::infer::{InferCtxt, TyCtxtInferExt}; +use rustc_infer::traits::query::NoSolution; +use rustc_infer::traits::Obligation; +use rustc_middle::infer::canonical::{Canonical, CanonicalVarValues}; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_middle::ty::{RegionOutlivesPredicate, ToPredicate, TypeOutlivesPredicate}; +use rustc_span::DUMMY_SP; + +use self::infcx_ext::InferCtxtExt; + +mod assembly; +mod cache; +mod fulfill; +mod infcx_ext; +mod overflow; +mod project_goals; +mod trait_goals; + +pub use fulfill::FulfillmentCtxt; + +/// A goal is a statement, i.e. `predicate`, we want to prove +/// given some assumptions, i.e. `param_env`. +/// +/// Most of the time the `param_env` contains the `where`-bounds of the function +/// we're currently typechecking while the `predicate` is some trait bound. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)] +pub struct Goal<'tcx, P> { + param_env: ty::ParamEnv<'tcx>, + predicate: P, +} + +impl<'tcx, P> Goal<'tcx, P> { + pub fn new( + tcx: TyCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + predicate: impl ToPredicate<'tcx, P>, + ) -> Goal<'tcx, P> { + Goal { param_env, predicate: predicate.to_predicate(tcx) } + } + + /// Updates the goal to one with a different `predicate` but the same `param_env`. + fn with(self, tcx: TyCtxt<'tcx>, predicate: impl ToPredicate<'tcx, Q>) -> Goal<'tcx, Q> { + Goal { param_env: self.param_env, predicate: predicate.to_predicate(tcx) } + } +} + +impl<'tcx, P> From> for Goal<'tcx, P> { + fn from(obligation: Obligation<'tcx, P>) -> Goal<'tcx, P> { + Goal { param_env: obligation.param_env, predicate: obligation.predicate } + } +} + +#[derive(Debug, PartialEq, Eq, Clone, Hash, TypeFoldable, TypeVisitable)] +pub struct Response<'tcx> { + pub var_values: CanonicalVarValues<'tcx>, + /// Additional constraints returned by this query. + pub external_constraints: ExternalConstraints<'tcx>, + pub certainty: Certainty, +} + +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)] +pub enum Certainty { + Yes, + Maybe(MaybeCause), +} + +impl Certainty { + /// When proving multiple goals using **AND**, e.g. nested obligations for an impl, + /// use this function to unify the certainty of these goals + pub fn unify_and(self, other: Certainty) -> Certainty { + match (self, other) { + (Certainty::Yes, Certainty::Yes) => Certainty::Yes, + (Certainty::Yes, Certainty::Maybe(_)) => other, + (Certainty::Maybe(_), Certainty::Yes) => self, + (Certainty::Maybe(MaybeCause::Overflow), Certainty::Maybe(MaybeCause::Overflow)) => { + Certainty::Maybe(MaybeCause::Overflow) + } + // If at least one of the goals is ambiguous, hide the overflow as the ambiguous goal + // may still result in failure. + (Certainty::Maybe(MaybeCause::Ambiguity), Certainty::Maybe(_)) + | (Certainty::Maybe(_), Certainty::Maybe(MaybeCause::Ambiguity)) => { + Certainty::Maybe(MaybeCause::Ambiguity) + } + } + } +} + +/// Why we failed to evaluate a goal. +#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, TypeFoldable, TypeVisitable)] +pub enum MaybeCause { + /// We failed due to ambiguity. This ambiguity can either + /// be a true ambiguity, i.e. there are multiple different answers, + /// or we hit a case where we just don't bother, e.g. `?x: Trait` goals. + Ambiguity, + /// We gave up due to an overflow, most often by hitting the recursion limit. + Overflow, +} + +/// Additional constraints returned on success. +#[derive(Debug, PartialEq, Eq, Clone, Hash, TypeFoldable, TypeVisitable)] +pub struct ExternalConstraints<'tcx> { + // FIXME: implement this. + regions: (), + opaque_types: Vec<(Ty<'tcx>, Ty<'tcx>)>, +} + +type CanonicalGoal<'tcx, T = ty::Predicate<'tcx>> = Canonical<'tcx, Goal<'tcx, T>>; +type CanonicalResponse<'tcx> = Canonical<'tcx, Response<'tcx>>; +/// The result of evaluating a canonical query. +/// +/// FIXME: We use a different type than the existing canonical queries. This is because +/// we need to add a `Certainty` for `overflow` and may want to restructure this code without +/// having to worry about changes to currently used code. Once we've made progress on this +/// solver, merge the two responses again. +pub type QueryResult<'tcx> = Result, NoSolution>; + +pub trait TyCtxtExt<'tcx> { + fn evaluate_goal(self, goal: CanonicalGoal<'tcx>) -> QueryResult<'tcx>; +} + +impl<'tcx> TyCtxtExt<'tcx> for TyCtxt<'tcx> { + fn evaluate_goal(self, goal: CanonicalGoal<'tcx>) -> QueryResult<'tcx> { + let mut cx = EvalCtxt::new(self); + cx.evaluate_canonical_goal(goal) + } +} + +struct EvalCtxt<'tcx> { + tcx: TyCtxt<'tcx>, + + provisional_cache: cache::ProvisionalCache<'tcx>, + overflow_data: overflow::OverflowData, +} + +impl<'tcx> EvalCtxt<'tcx> { + fn new(tcx: TyCtxt<'tcx>) -> EvalCtxt<'tcx> { + EvalCtxt { + tcx, + provisional_cache: cache::ProvisionalCache::empty(), + overflow_data: overflow::OverflowData::new(tcx), + } + } + + /// Recursively evaluates `goal`, returning whether any inference vars have + /// been constrained and the certainty of the result. + fn evaluate_goal( + &mut self, + infcx: &InferCtxt<'tcx>, + goal: Goal<'tcx, ty::Predicate<'tcx>>, + ) -> Result<(bool, Certainty), NoSolution> { + let mut orig_values = OriginalQueryValues::default(); + let canonical_goal = infcx.canonicalize_query(goal, &mut orig_values); + let canonical_response = self.evaluate_canonical_goal(canonical_goal)?; + Ok(( + true, // FIXME: check whether `var_values` are an identity substitution. + fixme_instantiate_canonical_query_response(infcx, &orig_values, canonical_response), + )) + } + + fn evaluate_canonical_goal(&mut self, goal: CanonicalGoal<'tcx>) -> QueryResult<'tcx> { + match self.try_push_stack(goal) { + Ok(()) => {} + // Our goal is already on the stack, eager return. + Err(response) => return response, + } + + // We may have to repeatedly recompute the goal in case of coinductive cycles, + // check out the `cache` module for more information. + // + // FIXME: Similar to `evaluate_all`, this has to check for overflow. + loop { + let result = self.compute_goal(goal); + + // FIXME: `Response` should be `Copy` + if self.try_finalize_goal(goal, result.clone()) { + return result; + } + } + } + + fn compute_goal(&mut self, canonical_goal: CanonicalGoal<'tcx>) -> QueryResult<'tcx> { + // WARNING: We're looking at a canonical value without instantiating it here. + // + // We have to be incredibly careful to not change the order of bound variables or + // remove any. As we go from `Goal<'tcx, Predicate>` to `Goal` with the variants + // of `PredicateKind` this is the case and it is and faster than instantiating and + // recanonicalizing. + let Goal { param_env, predicate } = canonical_goal.value; + if let Some(kind) = predicate.kind().no_bound_vars() { + match kind { + ty::PredicateKind::Clause(ty::Clause::Trait(predicate)) => self.compute_trait_goal( + canonical_goal.unchecked_rebind(Goal { param_env, predicate }), + ), + ty::PredicateKind::Clause(ty::Clause::Projection(predicate)) => self + .compute_projection_goal( + canonical_goal.unchecked_rebind(Goal { param_env, predicate }), + ), + ty::PredicateKind::Clause(ty::Clause::TypeOutlives(predicate)) => self + .compute_type_outlives_goal( + canonical_goal.unchecked_rebind(Goal { param_env, predicate }), + ), + ty::PredicateKind::Clause(ty::Clause::RegionOutlives(predicate)) => self + .compute_region_outlives_goal( + canonical_goal.unchecked_rebind(Goal { param_env, predicate }), + ), + // FIXME: implement these predicates :) + ty::PredicateKind::WellFormed(_) + | ty::PredicateKind::ObjectSafe(_) + | ty::PredicateKind::ClosureKind(_, _, _) + | ty::PredicateKind::Subtype(_) + | ty::PredicateKind::Coerce(_) + | ty::PredicateKind::ConstEvaluatable(_) + | ty::PredicateKind::ConstEquate(_, _) + | ty::PredicateKind::TypeWellFormedFromEnv(_) + | ty::PredicateKind::Ambiguous => unimplemented!(), + } + } else { + let (infcx, goal, var_values) = + self.tcx.infer_ctxt().build_with_canonical(DUMMY_SP, &canonical_goal); + let kind = infcx.replace_bound_vars_with_placeholders(goal.predicate.kind()); + let goal = goal.with(self.tcx, ty::Binder::dummy(kind)); + let (_, certainty) = self.evaluate_goal(&infcx, goal)?; + infcx.make_canonical_response(var_values, certainty) + } + } + + fn compute_type_outlives_goal( + &mut self, + _goal: CanonicalGoal<'tcx, TypeOutlivesPredicate<'tcx>>, + ) -> QueryResult<'tcx> { + todo!() + } + + fn compute_region_outlives_goal( + &mut self, + _goal: CanonicalGoal<'tcx, RegionOutlivesPredicate<'tcx>>, + ) -> QueryResult<'tcx> { + todo!() + } +} + +impl<'tcx> EvalCtxt<'tcx> { + fn evaluate_all( + &mut self, + infcx: &InferCtxt<'tcx>, + mut goals: Vec>>, + ) -> Result { + let mut new_goals = Vec::new(); + self.repeat_while_none(|this| { + let mut has_changed = Err(Certainty::Yes); + for goal in goals.drain(..) { + let (changed, certainty) = match this.evaluate_goal(infcx, goal) { + Ok(result) => result, + Err(NoSolution) => return Some(Err(NoSolution)), + }; + + if changed { + has_changed = Ok(()); + } + + match certainty { + Certainty::Yes => {} + Certainty::Maybe(_) => { + new_goals.push(goal); + has_changed = has_changed.map_err(|c| c.unify_and(certainty)); + } + } + } + + match has_changed { + Ok(()) => { + mem::swap(&mut new_goals, &mut goals); + None + } + Err(certainty) => Some(Ok(certainty)), + } + }) + } +} + +fn fixme_instantiate_canonical_query_response<'tcx>( + _: &InferCtxt<'tcx>, + _: &OriginalQueryValues<'tcx>, + _: CanonicalResponse<'tcx>, +) -> Certainty { + unimplemented!() +} diff --git a/compiler/rustc_trait_selection/src/solve/overflow.rs b/compiler/rustc_trait_selection/src/solve/overflow.rs new file mode 100644 index 0000000000000..8d73a83aec96e --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/overflow.rs @@ -0,0 +1,80 @@ +use rustc_infer::traits::query::NoSolution; +use rustc_middle::ty::TyCtxt; +use rustc_session::Limit; + +use super::{Certainty, EvalCtxt, MaybeCause, QueryResult}; + +/// When detecting a solver overflow, we return ambiguity. Overflow can be +/// *hidden* by either a fatal error in an **AND** or a trivial success in an **OR**. +/// +/// This is in issue in case of exponential blowup, e.g. if each goal on the stack +/// has multiple nested (overflowing) candidates. To deal with this, we reduce the limit +/// used by the solver when hitting the default limit for the first time. +/// +/// FIXME: Get tests where always using the `default_limit` results in a hang and refer +/// to them here. We can also improve the overflow strategy if necessary. +pub(super) struct OverflowData { + default_limit: Limit, + current_limit: Limit, + /// When proving an **AND** we have to repeatedly iterate over the yet unproven goals. + /// + /// Because of this each iteration also increases the depth in addition to the stack + /// depth. + additional_depth: usize, +} + +impl OverflowData { + pub(super) fn new(tcx: TyCtxt<'_>) -> OverflowData { + let default_limit = tcx.recursion_limit(); + OverflowData { default_limit, current_limit: default_limit, additional_depth: 0 } + } + + #[inline] + pub(super) fn did_overflow(&self) -> bool { + self.default_limit.0 != self.current_limit.0 + } + + #[inline] + pub(super) fn has_overflow(&self, depth: usize) -> bool { + self.current_limit.value_within_limit(depth + self.additional_depth) + } + + /// Updating the current limit when hitting overflow. + fn deal_with_overflow(&mut self) { + // When first hitting overflow we reduce the overflow limit + // for all future goals to prevent hangs if there's an exponental + // blowup. + self.current_limit.0 = self.default_limit.0 / 8; + } +} + +impl<'tcx> EvalCtxt<'tcx> { + pub(super) fn deal_with_overflow(&mut self) -> QueryResult<'tcx> { + self.overflow_data.deal_with_overflow(); + fixme_response_overflow_no_constraints() + } + + /// A `while`-loop which tracks overflow. + pub(super) fn repeat_while_none( + &mut self, + mut loop_body: impl FnMut(&mut Self) -> Option>, + ) -> Result { + let start_depth = self.overflow_data.additional_depth; + let depth = self.provisional_cache.current_depth(); + while !self.overflow_data.has_overflow(depth) { + if let Some(result) = loop_body(self) { + self.overflow_data.additional_depth = start_depth; + return result; + } + + self.overflow_data.additional_depth += 1; + } + self.overflow_data.additional_depth = start_depth; + self.overflow_data.deal_with_overflow(); + Ok(Certainty::Maybe(MaybeCause::Overflow)) + } +} + +fn fixme_response_overflow_no_constraints<'tcx>() -> QueryResult<'tcx> { + unimplemented!() +} diff --git a/compiler/rustc_trait_selection/src/solve/project_goals.rs b/compiler/rustc_trait_selection/src/solve/project_goals.rs new file mode 100644 index 0000000000000..b50f42c4d9416 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/project_goals.rs @@ -0,0 +1,244 @@ +use crate::traits::{specialization_graph, translate_substs}; + +use super::assembly::{self, AssemblyCtxt}; +use super::{CanonicalGoal, EvalCtxt, Goal, QueryResult}; +use rustc_errors::ErrorGuaranteed; +use rustc_hir::def::DefKind; +use rustc_hir::def_id::DefId; +use rustc_infer::infer::{InferCtxt, InferOk}; +use rustc_infer::traits::query::NoSolution; +use rustc_infer::traits::specialization_graph::LeafDef; +use rustc_infer::traits::{ObligationCause, Reveal}; +use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; +use rustc_middle::ty::ProjectionPredicate; +use rustc_middle::ty::TypeVisitable; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_span::DUMMY_SP; +use std::iter; + +#[allow(dead_code)] // FIXME: implement and use all variants. +#[derive(Debug, Clone, Copy)] +pub(super) enum CandidateSource { + Impl(DefId), + ParamEnv(usize), + Builtin, +} + +type Candidate<'tcx> = assembly::Candidate<'tcx, ProjectionPredicate<'tcx>>; + +impl<'tcx> EvalCtxt<'tcx> { + pub(super) fn compute_projection_goal( + &mut self, + goal: CanonicalGoal<'tcx, ProjectionPredicate<'tcx>>, + ) -> QueryResult<'tcx> { + let candidates = AssemblyCtxt::assemble_and_evaluate_candidates(self, goal); + self.merge_project_candidates(candidates) + } + + fn merge_project_candidates( + &mut self, + mut candidates: Vec>, + ) -> QueryResult<'tcx> { + match candidates.len() { + 0 => return Err(NoSolution), + 1 => return Ok(candidates.pop().unwrap().result), + _ => {} + } + + if candidates.len() > 1 { + let mut i = 0; + 'outer: while i < candidates.len() { + for j in (0..candidates.len()).filter(|&j| i != j) { + if self.project_candidate_should_be_dropped_in_favor_of( + &candidates[i], + &candidates[j], + ) { + debug!(candidate = ?candidates[i], "Dropping candidate #{}/{}", i, candidates.len()); + candidates.swap_remove(i); + continue 'outer; + } + } + + debug!(candidate = ?candidates[i], "Retaining candidate #{}/{}", i, candidates.len()); + // If there are *STILL* multiple candidates, give up + // and report ambiguity. + i += 1; + if i > 1 { + debug!("multiple matches, ambig"); + // FIXME: return overflow if all candidates overflow, otherwise return ambiguity. + unimplemented!(); + } + } + } + + Ok(candidates.pop().unwrap().result) + } + + fn project_candidate_should_be_dropped_in_favor_of( + &self, + candidate: &Candidate<'tcx>, + other: &Candidate<'tcx>, + ) -> bool { + // FIXME: implement this + match (candidate.source, other.source) { + (CandidateSource::Impl(_), _) + | (CandidateSource::ParamEnv(_), _) + | (CandidateSource::Builtin, _) => unimplemented!(), + } + } +} + +impl<'tcx> assembly::GoalKind<'tcx> for ProjectionPredicate<'tcx> { + type CandidateSource = CandidateSource; + + fn self_ty(self) -> Ty<'tcx> { + self.self_ty() + } + + fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self { + self.with_self_ty(tcx, self_ty) + } + + fn trait_def_id(self, tcx: TyCtxt<'tcx>) -> DefId { + self.trait_def_id(tcx) + } + + fn consider_impl_candidate( + acx: &mut AssemblyCtxt<'_, 'tcx, ProjectionPredicate<'tcx>>, + goal: Goal<'tcx, ProjectionPredicate<'tcx>>, + impl_def_id: DefId, + ) { + let tcx = acx.cx.tcx; + let goal_trait_ref = goal.predicate.projection_ty.trait_ref(tcx); + let impl_trait_ref = tcx.bound_impl_trait_ref(impl_def_id).unwrap(); + let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::AsPlaceholder }; + if iter::zip(goal_trait_ref.substs, impl_trait_ref.skip_binder().substs) + .any(|(goal, imp)| !drcx.generic_args_may_unify(goal, imp)) + { + return; + } + + acx.infcx.probe(|_| { + let impl_substs = acx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); + let impl_trait_ref = impl_trait_ref.subst(tcx, impl_substs); + + let Ok(InferOk { obligations, .. }) = acx + .infcx + .at(&ObligationCause::dummy(), goal.param_env) + .define_opaque_types(false) + .eq(goal_trait_ref, impl_trait_ref) + .map_err(|e| debug!("failed to equate trait refs: {e:?}")) + else { + return + }; + + let nested_goals = obligations.into_iter().map(|o| o.into()).collect(); + let Ok(trait_ref_certainty) = acx.cx.evaluate_all(acx.infcx, nested_goals) else { return }; + + let Some(assoc_def) = fetch_eligible_assoc_item_def( + acx.infcx, + goal.param_env, + goal_trait_ref, + goal.predicate.def_id(), + impl_def_id + ) else { + return + }; + + if !assoc_def.item.defaultness(tcx).has_value() { + tcx.sess.delay_span_bug( + tcx.def_span(assoc_def.item.def_id), + "missing value for assoc item in impl", + ); + } + + // Getting the right substitutions here is complex, e.g. given: + // - a goal ` as Trait>::Assoc` + // - the applicable impl `impl Trait for Vec` + // - and the impl which defines `Assoc` being `impl Trait for Vec` + // + // We first rebase the goal substs onto the impl, going from `[Vec, i32, u64]` + // to `[u32, u64]`. + // + // And then map these substs to the substs of the defining impl of `Assoc`, going + // from `[u32, u64]` to `[u32, i32, u64]`. + let impl_substs_with_gat = goal.predicate.projection_ty.substs.rebase_onto( + tcx, + goal_trait_ref.def_id, + impl_trait_ref.substs, + ); + let substs = translate_substs( + acx.infcx, + goal.param_env, + impl_def_id, + impl_substs_with_gat, + assoc_def.defining_node, + ); + + // Finally we construct the actual value of the associated type. + let is_const = matches!(tcx.def_kind(assoc_def.item.def_id), DefKind::AssocConst); + let ty = tcx.bound_type_of(assoc_def.item.def_id); + let term: ty::EarlyBinder> = if is_const { + let identity_substs = ty::InternalSubsts::identity_for_item(tcx, assoc_def.item.def_id); + let did = ty::WithOptConstParam::unknown(assoc_def.item.def_id); + let kind = + ty::ConstKind::Unevaluated(ty::UnevaluatedConst::new(did, identity_substs)); + ty.map_bound(|ty| tcx.mk_const(kind, ty).into()) + } else { + ty.map_bound(|ty| ty.into()) + }; + + let Ok(InferOk { obligations, .. }) = acx + .infcx + .at(&ObligationCause::dummy(), goal.param_env) + .define_opaque_types(false) + .eq(goal.predicate.term, term.subst(tcx, substs)) + .map_err(|e| debug!("failed to equate trait refs: {e:?}")) + else { + return + }; + + let nested_goals = obligations.into_iter().map(|o| o.into()).collect(); + let Ok(rhs_certainty) = acx.cx.evaluate_all(acx.infcx, nested_goals) else { return }; + + let certainty = trait_ref_certainty.unify_and(rhs_certainty); + acx.try_insert_candidate(CandidateSource::Impl(impl_def_id), certainty); + }) + } +} + +/// This behavior is also implemented in `rustc_ty_utils` and in the old `project` code. +/// +/// FIXME: We should merge these 3 implementations as it's likely that they otherwise +/// diverge. +#[instrument(level = "debug", skip(infcx, param_env), ret)] +fn fetch_eligible_assoc_item_def<'tcx>( + infcx: &InferCtxt<'tcx>, + param_env: ty::ParamEnv<'tcx>, + goal_trait_ref: ty::TraitRef<'tcx>, + trait_assoc_def_id: DefId, + impl_def_id: DefId, +) -> Option { + let node_item = specialization_graph::assoc_def(infcx.tcx, impl_def_id, trait_assoc_def_id) + .map_err(|ErrorGuaranteed { .. }| ()) + .ok()?; + + let eligible = if node_item.is_final() { + // Non-specializable items are always projectable. + true + } else { + // Only reveal a specializable default if we're past type-checking + // and the obligation is monomorphic, otherwise passes such as + // transmute checking and polymorphic MIR optimizations could + // get a result which isn't correct for all monomorphizations. + if param_env.reveal() == Reveal::All { + let poly_trait_ref = infcx.resolve_vars_if_possible(goal_trait_ref); + !poly_trait_ref.still_further_specializable() + } else { + debug!(?node_item.item.def_id, "not eligible due to default"); + false + } + }; + + if eligible { Some(node_item) } else { None } +} diff --git a/compiler/rustc_trait_selection/src/solve/trait_goals.rs b/compiler/rustc_trait_selection/src/solve/trait_goals.rs new file mode 100644 index 0000000000000..10b45a77dabb0 --- /dev/null +++ b/compiler/rustc_trait_selection/src/solve/trait_goals.rs @@ -0,0 +1,180 @@ +//! Dealing with trait goals, i.e. `T: Trait<'a, U>`. + +use std::iter; + +use super::assembly::{self, AssemblyCtxt}; +use super::{CanonicalGoal, EvalCtxt, Goal, QueryResult}; +use rustc_hir::def_id::DefId; +use rustc_infer::infer::InferOk; +use rustc_infer::traits::query::NoSolution; +use rustc_infer::traits::ObligationCause; +use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; +use rustc_middle::ty::TraitPredicate; +use rustc_middle::ty::{self, Ty, TyCtxt}; +use rustc_span::DUMMY_SP; + +#[allow(dead_code)] // FIXME: implement and use all variants. +#[derive(Debug, Clone, Copy)] +pub(super) enum CandidateSource { + /// Some user-defined impl with the given `DefId`. + Impl(DefId), + /// The n-th caller bound in the `param_env` of our goal. + /// + /// This is pretty much always a bound from the `where`-clauses of the + /// currently checked item. + ParamEnv(usize), + /// A bound on the `self_ty` in case it is a projection or an opaque type. + /// + /// # Examples + /// + /// ```ignore (for syntax highlighting) + /// trait Trait { + /// type Assoc: OtherTrait; + /// } + /// ``` + /// + /// We know that `::Assoc: OtherTrait` holds by looking at + /// the bounds on `Trait::Assoc`. + AliasBound(usize), + /// A builtin implementation for some specific traits, used in cases + /// where we cannot rely an ordinary library implementations. + /// + /// The most notable examples are `Sized`, `Copy` and `Clone`. This is also + /// used for the `DiscriminantKind` and `Pointee` trait, both of which have + /// an associated type. + Builtin, + /// An automatic impl for an auto trait, e.g. `Send`. These impls recursively look + /// at the constituent types of the `self_ty` to check whether the auto trait + /// is implemented for those. + AutoImpl, +} + +type Candidate<'tcx> = assembly::Candidate<'tcx, TraitPredicate<'tcx>>; + +impl<'tcx> assembly::GoalKind<'tcx> for TraitPredicate<'tcx> { + type CandidateSource = CandidateSource; + + fn self_ty(self) -> Ty<'tcx> { + self.self_ty() + } + + fn with_self_ty(self, tcx: TyCtxt<'tcx>, self_ty: Ty<'tcx>) -> Self { + self.with_self_ty(tcx, self_ty) + } + + fn trait_def_id(self, _: TyCtxt<'tcx>) -> DefId { + self.def_id() + } + + fn consider_impl_candidate( + acx: &mut AssemblyCtxt<'_, 'tcx, Self>, + goal: Goal<'tcx, TraitPredicate<'tcx>>, + impl_def_id: DefId, + ) { + let impl_trait_ref = acx.cx.tcx.bound_impl_trait_ref(impl_def_id).unwrap(); + let drcx = DeepRejectCtxt { treat_obligation_params: TreatParams::AsPlaceholder }; + if iter::zip(goal.predicate.trait_ref.substs, impl_trait_ref.skip_binder().substs) + .any(|(goal, imp)| !drcx.generic_args_may_unify(goal, imp)) + { + return; + } + + acx.infcx.probe(|_| { + let impl_substs = acx.infcx.fresh_substs_for_item(DUMMY_SP, impl_def_id); + let impl_trait_ref = impl_trait_ref.subst(acx.cx.tcx, impl_substs); + + let Ok(InferOk { obligations, .. }) = acx + .infcx + .at(&ObligationCause::dummy(), goal.param_env) + .define_opaque_types(false) + .eq(goal.predicate.trait_ref, impl_trait_ref) + .map_err(|e| debug!("failed to equate trait refs: {e:?}")) + else { + return + }; + + let nested_goals = obligations.into_iter().map(|o| o.into()).collect(); + + let Ok(certainty) = acx.cx.evaluate_all(acx.infcx, nested_goals) else { return }; + acx.try_insert_candidate(CandidateSource::Impl(impl_def_id), certainty); + }) + } +} + +impl<'tcx> EvalCtxt<'tcx> { + pub(super) fn compute_trait_goal( + &mut self, + goal: CanonicalGoal<'tcx, TraitPredicate<'tcx>>, + ) -> QueryResult<'tcx> { + let candidates = AssemblyCtxt::assemble_and_evaluate_candidates(self, goal); + self.merge_trait_candidates_discard_reservation_impls(candidates) + } + + #[instrument(level = "debug", skip(self), ret)] + pub(super) fn merge_trait_candidates_discard_reservation_impls( + &mut self, + mut candidates: Vec>, + ) -> QueryResult<'tcx> { + match candidates.len() { + 0 => return Err(NoSolution), + 1 => return Ok(self.discard_reservation_impl(candidates.pop().unwrap()).result), + _ => {} + } + + if candidates.len() > 1 { + let mut i = 0; + 'outer: while i < candidates.len() { + for j in (0..candidates.len()).filter(|&j| i != j) { + if self.trait_candidate_should_be_dropped_in_favor_of( + &candidates[i], + &candidates[j], + ) { + debug!(candidate = ?candidates[i], "Dropping candidate #{}/{}", i, candidates.len()); + candidates.swap_remove(i); + continue 'outer; + } + } + + debug!(candidate = ?candidates[i], "Retaining candidate #{}/{}", i, candidates.len()); + // If there are *STILL* multiple candidates, give up + // and report ambiguity. + i += 1; + if i > 1 { + debug!("multiple matches, ambig"); + // FIXME: return overflow if all candidates overflow, otherwise return ambiguity. + unimplemented!(); + } + } + } + + Ok(self.discard_reservation_impl(candidates.pop().unwrap()).result) + } + + fn trait_candidate_should_be_dropped_in_favor_of( + &self, + candidate: &Candidate<'tcx>, + other: &Candidate<'tcx>, + ) -> bool { + // FIXME: implement this + match (candidate.source, other.source) { + (CandidateSource::Impl(_), _) + | (CandidateSource::ParamEnv(_), _) + | (CandidateSource::AliasBound(_), _) + | (CandidateSource::Builtin, _) + | (CandidateSource::AutoImpl, _) => unimplemented!(), + } + } + + fn discard_reservation_impl(&self, candidate: Candidate<'tcx>) -> Candidate<'tcx> { + if let CandidateSource::Impl(def_id) = candidate.source { + if let ty::ImplPolarity::Reservation = self.tcx.impl_polarity(def_id) { + debug!("Selected reservation impl"); + // FIXME: reduce candidate to ambiguous + // FIXME: replace `var_values` with identity, yeet external constraints. + unimplemented!() + } + } + + candidate + } +} diff --git a/compiler/rustc_trait_selection/src/traits/project.rs b/compiler/rustc_trait_selection/src/traits/project.rs index 84d7244c1db78..5276da2e49c75 100644 --- a/compiler/rustc_trait_selection/src/traits/project.rs +++ b/compiler/rustc_trait_selection/src/traits/project.rs @@ -25,7 +25,6 @@ use rustc_data_structures::sso::SsoHashSet; use rustc_data_structures::stack::ensure_sufficient_stack; use rustc_errors::ErrorGuaranteed; use rustc_hir::def::DefKind; -use rustc_hir::def_id::DefId; use rustc_hir::lang_items::LangItem; use rustc_infer::infer::at::At; use rustc_infer::infer::resolve::OpportunisticRegionResolver; @@ -1553,7 +1552,7 @@ fn assemble_candidates_from_impls<'cx, 'tcx>( // NOTE: This should be kept in sync with the similar code in // `rustc_ty_utils::instance::resolve_associated_item()`. let node_item = - assoc_def(selcx, impl_data.impl_def_id, obligation.predicate.def_id) + specialization_graph::assoc_def(selcx.tcx(), impl_data.impl_def_id, obligation.predicate.def_id) .map_err(|ErrorGuaranteed { .. }| ())?; if node_item.is_final() { @@ -2113,7 +2112,7 @@ fn confirm_impl_candidate<'cx, 'tcx>( let trait_def_id = tcx.trait_id_of_impl(impl_def_id).unwrap(); let param_env = obligation.param_env; - let Ok(assoc_ty) = assoc_def(selcx, impl_def_id, assoc_item_id) else { + let Ok(assoc_ty) = specialization_graph::assoc_def(tcx, impl_def_id, assoc_item_id) else { return Progress { term: tcx.ty_error().into(), obligations: nested }; }; @@ -2210,7 +2209,7 @@ fn confirm_impl_trait_in_trait_candidate<'tcx>( let mut obligations = data.nested; let trait_fn_def_id = tcx.impl_trait_in_trait_parent(obligation.predicate.def_id); - let Ok(leaf_def) = assoc_def(selcx, data.impl_def_id, trait_fn_def_id) else { + let Ok(leaf_def) = specialization_graph::assoc_def(tcx, data.impl_def_id, trait_fn_def_id) else { return Progress { term: tcx.ty_error().into(), obligations }; }; if !leaf_def.item.defaultness(tcx).has_value() { @@ -2347,58 +2346,6 @@ fn assoc_ty_own_obligations<'cx, 'tcx>( } } -/// Locate the definition of an associated type in the specialization hierarchy, -/// starting from the given impl. -/// -/// Based on the "projection mode", this lookup may in fact only examine the -/// topmost impl. See the comments for `Reveal` for more details. -fn assoc_def( - selcx: &SelectionContext<'_, '_>, - impl_def_id: DefId, - assoc_def_id: DefId, -) -> Result { - let tcx = selcx.tcx(); - let trait_def_id = tcx.impl_trait_ref(impl_def_id).unwrap().def_id; - let trait_def = tcx.trait_def(trait_def_id); - - // This function may be called while we are still building the - // specialization graph that is queried below (via TraitDef::ancestors()), - // so, in order to avoid unnecessary infinite recursion, we manually look - // for the associated item at the given impl. - // If there is no such item in that impl, this function will fail with a - // cycle error if the specialization graph is currently being built. - if let Some(&impl_item_id) = tcx.impl_item_implementor_ids(impl_def_id).get(&assoc_def_id) { - let item = tcx.associated_item(impl_item_id); - let impl_node = specialization_graph::Node::Impl(impl_def_id); - return Ok(specialization_graph::LeafDef { - item: *item, - defining_node: impl_node, - finalizing_node: if item.defaultness(tcx).is_default() { - None - } else { - Some(impl_node) - }, - }); - } - - let ancestors = trait_def.ancestors(tcx, impl_def_id)?; - if let Some(assoc_item) = ancestors.leaf_def(tcx, assoc_def_id) { - Ok(assoc_item) - } else { - // This is saying that neither the trait nor - // the impl contain a definition for this - // associated type. Normally this situation - // could only arise through a compiler bug -- - // if the user wrote a bad item name, it - // should have failed in astconv. - bug!( - "No associated type `{}` for {}", - tcx.item_name(assoc_def_id), - tcx.def_path_str(impl_def_id) - ) - } -} - pub(crate) trait ProjectionCacheKeyExt<'cx, 'tcx>: Sized { fn from_poly_projection_predicate( selcx: &mut SelectionContext<'cx, 'tcx>, diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index 792933096b15c..81e8f9e914c23 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -1171,19 +1171,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { where I: Iterator>, { - cycle.all(|predicate| self.coinductive_predicate(predicate)) - } - - fn coinductive_predicate(&self, predicate: ty::Predicate<'tcx>) -> bool { - let result = match predicate.kind().skip_binder() { - ty::PredicateKind::Clause(ty::Clause::Trait(ref data)) => { - self.tcx().trait_is_coinductive(data.def_id()) - } - ty::PredicateKind::WellFormed(_) => true, - _ => false, - }; - debug!(?predicate, ?result, "coinductive_predicate"); - result + cycle.all(|predicate| predicate.is_coinductive(self.tcx())) } /// Further evaluates `candidate` to decide whether all type parameters match and whether nested diff --git a/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs b/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs index 4546c95339300..02b0667774028 100644 --- a/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs +++ b/compiler/rustc_trait_selection/src/traits/specialize/specialization_graph.rs @@ -1,6 +1,7 @@ use super::OverlapError; use crate::traits; +use rustc_errors::ErrorGuaranteed; use rustc_hir::def_id::DefId; use rustc_middle::ty::fast_reject::{self, SimplifiedType, TreatParams}; use rustc_middle::ty::{self, TyCtxt, TypeVisitable}; @@ -379,3 +380,51 @@ impl<'tcx> GraphExt<'tcx> for Graph { self.children.entry(parent).or_default().insert_blindly(tcx, child); } } + +/// Locate the definition of an associated type in the specialization hierarchy, +/// starting from the given impl. +pub(crate) fn assoc_def( + tcx: TyCtxt<'_>, + impl_def_id: DefId, + assoc_def_id: DefId, +) -> Result { + let trait_def_id = tcx.impl_trait_ref(impl_def_id).unwrap().def_id; + let trait_def = tcx.trait_def(trait_def_id); + + // This function may be called while we are still building the + // specialization graph that is queried below (via TraitDef::ancestors()), + // so, in order to avoid unnecessary infinite recursion, we manually look + // for the associated item at the given impl. + // If there is no such item in that impl, this function will fail with a + // cycle error if the specialization graph is currently being built. + if let Some(&impl_item_id) = tcx.impl_item_implementor_ids(impl_def_id).get(&assoc_def_id) { + let &item = tcx.associated_item(impl_item_id); + let impl_node = Node::Impl(impl_def_id); + return Ok(LeafDef { + item, + defining_node: impl_node, + finalizing_node: if item.defaultness(tcx).is_default() { + None + } else { + Some(impl_node) + }, + }); + } + + let ancestors = trait_def.ancestors(tcx, impl_def_id)?; + if let Some(assoc_item) = ancestors.leaf_def(tcx, assoc_def_id) { + Ok(assoc_item) + } else { + // This is saying that neither the trait nor + // the impl contain a definition for this + // associated type. Normally this situation + // could only arise through a compiler bug -- + // if the user wrote a bad item name, it + // should have failed in astconv. + bug!( + "No associated type `{}` for {}", + tcx.item_name(assoc_def_id), + tcx.def_path_str(impl_def_id) + ) + } +} diff --git a/src/ci/docker/host-x86_64/mingw-check-tidy/Dockerfile b/src/ci/docker/host-x86_64/mingw-check-tidy/Dockerfile new file mode 100644 index 0000000000000..78fee152eb9d4 --- /dev/null +++ b/src/ci/docker/host-x86_64/mingw-check-tidy/Dockerfile @@ -0,0 +1,36 @@ +FROM ubuntu:18.04 +# FIXME: when bumping the version, remove the Python 3.6-specific changes in +# the reuse-requirements.in file, regenerate reuse-requirements.txt and remove +# this comment. + +RUN apt-get update && apt-get install -y --no-install-recommends \ + g++ \ + make \ + ninja-build \ + file \ + curl \ + ca-certificates \ + python3 \ + python3-pip \ + python3-pkg-resources \ + git \ + cmake \ + sudo \ + gdb \ + xz-utils \ + libssl-dev \ + pkg-config \ + mingw-w64 \ + && rm -rf /var/lib/apt/lists/* + +COPY scripts/sccache.sh /scripts/ +RUN sh /scripts/sccache.sh + +COPY host-x86_64/mingw-check/reuse-requirements.txt /tmp/ +RUN pip3 install --no-deps --no-cache-dir --require-hashes -r /tmp/reuse-requirements.txt + +COPY host-x86_64/mingw-check/validate-toolstate.sh /scripts/ +COPY host-x86_64/mingw-check/validate-error-codes.sh /scripts/ + +ENV RUN_CHECK_WITH_PARALLEL_QUERIES 1 +ENV SCRIPT python3 ../x.py test --stage 0 src/tools/tidy diff --git a/src/ci/docker/host-x86_64/mingw-check/Dockerfile b/src/ci/docker/host-x86_64/mingw-check/Dockerfile index 9e2568af13f68..adf6bb4b37752 100644 --- a/src/ci/docker/host-x86_64/mingw-check/Dockerfile +++ b/src/ci/docker/host-x86_64/mingw-check/Dockerfile @@ -40,7 +40,6 @@ COPY host-x86_64/mingw-check/validate-error-codes.sh /scripts/ ENV RUN_CHECK_WITH_PARALLEL_QUERIES 1 ENV SCRIPT python3 ../x.py --stage 2 test src/tools/expand-yaml-anchors && \ - python3 ../x.py test --stage 0 src/tools/tidy && \ python3 ../x.py check --target=i686-pc-windows-gnu --host=i686-pc-windows-gnu --all-targets && \ python3 ../x.py build --stage 0 src/tools/build-manifest && \ python3 ../x.py test --stage 0 src/tools/compiletest && \ diff --git a/src/ci/github-actions/ci.yml b/src/ci/github-actions/ci.yml index d1ba46ad30de6..d33396dcc80fe 100644 --- a/src/ci/github-actions/ci.yml +++ b/src/ci/github-actions/ci.yml @@ -287,19 +287,27 @@ jobs: env: <<: [*shared-ci-variables, *public-variables] if: github.event_name == 'pull_request' + continue-on-error: ${{ matrix.tidy }} strategy: matrix: include: - name: mingw-check <<: *job-linux-xl + tidy: false + + - name: mingw-check-tidy + <<: *job-linux-xl + tidy: true - name: x86_64-gnu-llvm-13 <<: *job-linux-xl + tidy: false - name: x86_64-gnu-tools + <<: *job-linux-xl + tidy: false env: CI_ONLY_WHEN_SUBMODULES_CHANGED: 1 - <<: *job-linux-xl auto: permissions: diff --git a/src/etc/pre-push.sh b/src/etc/pre-push.sh index be7de3ebaf571..2a3086338b4bf 100755 --- a/src/etc/pre-push.sh +++ b/src/etc/pre-push.sh @@ -10,16 +10,8 @@ set -Eeuo pipefail # https://github.com/rust-lang/rust/issues/77620#issuecomment-705144570 unset GIT_DIR ROOT_DIR="$(git rev-parse --show-toplevel)" -COMMAND="$ROOT_DIR/x.py test tidy" -if [[ "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then - COMMAND="python $COMMAND" -elif ! command -v python &> /dev/null; then - COMMAND="python3 $COMMAND" -fi - -echo "Running pre-push script '$COMMAND'" +echo "Running pre-push script $ROOT_DIR/x test tidy" cd "$ROOT_DIR" - -$COMMAND +./x test tidy diff --git a/src/test/ui/issues/issue-3044.rs b/src/test/ui/fn/issue-3044.rs similarity index 100% rename from src/test/ui/issues/issue-3044.rs rename to src/test/ui/fn/issue-3044.rs diff --git a/src/test/ui/issues/issue-3044.stderr b/src/test/ui/fn/issue-3044.stderr similarity index 100% rename from src/test/ui/issues/issue-3044.stderr rename to src/test/ui/fn/issue-3044.stderr diff --git a/src/test/ui/issues/issue-3904.rs b/src/test/ui/fn/issue-3904.rs similarity index 100% rename from src/test/ui/issues/issue-3904.rs rename to src/test/ui/fn/issue-3904.rs diff --git a/src/test/ui/issues/issue-6470.rs b/src/test/ui/issues/issue-6470.rs deleted file mode 100644 index 8c6192a59dba2..0000000000000 --- a/src/test/ui/issues/issue-6470.rs +++ /dev/null @@ -1,17 +0,0 @@ -// build-pass -#![allow(dead_code)] -#![allow(improper_ctypes)] -// pretty-expanded FIXME #23616 -#![allow(non_snake_case)] - -pub mod Bar { - pub struct Foo { - v: isize, - } - - extern "C" { - pub fn foo(v: *const Foo) -> Foo; - } -} - -pub fn main() {} diff --git a/src/test/ui/issues/issue-36381.rs b/src/test/ui/late-bound-lifetimes/issue-36381.rs similarity index 100% rename from src/test/ui/issues/issue-36381.rs rename to src/test/ui/late-bound-lifetimes/issue-36381.rs diff --git a/src/test/ui/issues/issue-25385.rs b/src/test/ui/macros/issue-25385.rs similarity index 100% rename from src/test/ui/issues/issue-25385.rs rename to src/test/ui/macros/issue-25385.rs diff --git a/src/test/ui/issues/issue-25385.stderr b/src/test/ui/macros/issue-25385.stderr similarity index 100% rename from src/test/ui/issues/issue-25385.stderr rename to src/test/ui/macros/issue-25385.stderr diff --git a/src/test/ui/issues/issue-5530.rs b/src/test/ui/match/issue-5530.rs similarity index 100% rename from src/test/ui/issues/issue-5530.rs rename to src/test/ui/match/issue-5530.rs diff --git a/src/test/ui/issues/auxiliary/issue-30535.rs b/src/test/ui/resolve/auxiliary/issue-30535.rs similarity index 100% rename from src/test/ui/issues/auxiliary/issue-30535.rs rename to src/test/ui/resolve/auxiliary/issue-30535.rs diff --git a/src/test/ui/issues/issue-30535.rs b/src/test/ui/resolve/issue-30535.rs similarity index 100% rename from src/test/ui/issues/issue-30535.rs rename to src/test/ui/resolve/issue-30535.rs diff --git a/src/test/ui/issues/issue-30535.stderr b/src/test/ui/resolve/issue-30535.stderr similarity index 100% rename from src/test/ui/issues/issue-30535.stderr rename to src/test/ui/resolve/issue-30535.stderr diff --git a/src/test/ui/issues/issue-39559-2.rs b/src/test/ui/resolve/issue-39559-2.rs similarity index 100% rename from src/test/ui/issues/issue-39559-2.rs rename to src/test/ui/resolve/issue-39559-2.rs diff --git a/src/test/ui/issues/issue-39559-2.stderr b/src/test/ui/resolve/issue-39559-2.stderr similarity index 100% rename from src/test/ui/issues/issue-39559-2.stderr rename to src/test/ui/resolve/issue-39559-2.stderr diff --git a/src/test/ui/issues/issue-39559.rs b/src/test/ui/resolve/issue-39559.rs similarity index 100% rename from src/test/ui/issues/issue-39559.rs rename to src/test/ui/resolve/issue-39559.rs diff --git a/src/test/ui/issues/issue-39559.stderr b/src/test/ui/resolve/issue-39559.stderr similarity index 100% rename from src/test/ui/issues/issue-39559.stderr rename to src/test/ui/resolve/issue-39559.stderr diff --git a/src/test/ui/issues/issue-18118-2.rs b/src/test/ui/static/issue-18118-2.rs similarity index 100% rename from src/test/ui/issues/issue-18118-2.rs rename to src/test/ui/static/issue-18118-2.rs diff --git a/src/test/ui/issues/issue-18118-2.stderr b/src/test/ui/static/issue-18118-2.stderr similarity index 100% rename from src/test/ui/issues/issue-18118-2.stderr rename to src/test/ui/static/issue-18118-2.stderr diff --git a/src/test/ui/issues/issue-18118.rs b/src/test/ui/static/issue-18118.rs similarity index 100% rename from src/test/ui/issues/issue-18118.rs rename to src/test/ui/static/issue-18118.rs diff --git a/src/test/ui/issues/issue-18118.stderr b/src/test/ui/static/issue-18118.stderr similarity index 100% rename from src/test/ui/issues/issue-18118.stderr rename to src/test/ui/static/issue-18118.stderr diff --git a/src/test/ui/suggestions/fn-to-method-deeply-nested.rs b/src/test/ui/suggestions/fn-to-method-deeply-nested.rs new file mode 100644 index 0000000000000..58ee3d6409a7e --- /dev/null +++ b/src/test/ui/suggestions/fn-to-method-deeply-nested.rs @@ -0,0 +1,13 @@ +fn main() -> Result<(), ()> { + a(b(c(d(e( + //~^ ERROR cannot find function `a` in this scope + //~| ERROR cannot find function `b` in this scope + //~| ERROR cannot find function `c` in this scope + //~| ERROR cannot find function `d` in this scope + //~| ERROR cannot find function `e` in this scope + z???????????????????????????????????????????????????????????????????????????????????????? + ????????????????????????????????????????????????????????????????????????????????????????? + ?????????????????????????????????????????????????????????????????? + //~^^^ ERROR cannot find value `z` in this scope + ))))) +} diff --git a/src/test/ui/suggestions/fn-to-method-deeply-nested.stderr b/src/test/ui/suggestions/fn-to-method-deeply-nested.stderr new file mode 100644 index 0000000000000..ce813ea7abae8 --- /dev/null +++ b/src/test/ui/suggestions/fn-to-method-deeply-nested.stderr @@ -0,0 +1,39 @@ +error[E0425]: cannot find value `z` in this scope + --> $DIR/fn-to-method-deeply-nested.rs:8:9 + | +LL | z???????????????????????????????????????????????????????????????????????????????????????? + | ^ not found in this scope + +error[E0425]: cannot find function `e` in this scope + --> $DIR/fn-to-method-deeply-nested.rs:2:13 + | +LL | a(b(c(d(e( + | ^ not found in this scope + +error[E0425]: cannot find function `d` in this scope + --> $DIR/fn-to-method-deeply-nested.rs:2:11 + | +LL | a(b(c(d(e( + | ^ not found in this scope + +error[E0425]: cannot find function `c` in this scope + --> $DIR/fn-to-method-deeply-nested.rs:2:9 + | +LL | a(b(c(d(e( + | ^ not found in this scope + +error[E0425]: cannot find function `b` in this scope + --> $DIR/fn-to-method-deeply-nested.rs:2:7 + | +LL | a(b(c(d(e( + | ^ not found in this scope + +error[E0425]: cannot find function `a` in this scope + --> $DIR/fn-to-method-deeply-nested.rs:2:5 + | +LL | a(b(c(d(e( + | ^ not found in this scope + +error: aborting due to 6 previous errors + +For more information about this error, try `rustc --explain E0425`. diff --git a/src/test/ui/issues/issue-52557.rs b/src/test/ui/test-attrs/issue-52557.rs similarity index 100% rename from src/test/ui/issues/issue-52557.rs rename to src/test/ui/test-attrs/issue-52557.rs diff --git a/src/test/ui/traits/solver-cycles/inductive-canonical-cycle.rs b/src/test/ui/traits/solver-cycles/inductive-canonical-cycle.rs new file mode 100644 index 0000000000000..a3bb76d7e3b73 --- /dev/null +++ b/src/test/ui/traits/solver-cycles/inductive-canonical-cycle.rs @@ -0,0 +1,28 @@ +// known-bug + +// This should compile but fails with the current solver. +// +// This checks that the new solver uses `Ambiguous` when hitting the +// inductive cycle here when proving `exists<^0, ^1> (): Trait<^0, ^1>` +// which requires proving `Trait` but that has the same +// canonical representation. +trait Trait {} + +impl Trait for () +where + (): Trait, + T: OtherTrait, +{} + +trait OtherTrait {} +impl OtherTrait for u32 {} + +fn require_trait() +where + (): Trait +{} + +fn main() { + require_trait::<_, _>(); + //~^ ERROR overflow evaluating +} diff --git a/src/test/ui/traits/solver-cycles/inductive-canonical-cycle.stderr b/src/test/ui/traits/solver-cycles/inductive-canonical-cycle.stderr new file mode 100644 index 0000000000000..e4b84e07822d2 --- /dev/null +++ b/src/test/ui/traits/solver-cycles/inductive-canonical-cycle.stderr @@ -0,0 +1,26 @@ +error[E0275]: overflow evaluating the requirement `_: Sized` + --> $DIR/inductive-canonical-cycle.rs:26:5 + | +LL | require_trait::<_, _>(); + | ^^^^^^^^^^^^^^^^^^^^^ + | + = help: consider increasing the recursion limit by adding a `#![recursion_limit = "256"]` attribute to your crate (`inductive_canonical_cycle`) +note: required for `()` to implement `Trait<_, _>` + --> $DIR/inductive-canonical-cycle.rs:11:12 + | +LL | impl Trait for () + | ^^^^^^^^^^^ ^^ + = note: 128 redundant requirements hidden + = note: required for `()` to implement `Trait<_, _>` +note: required by a bound in `require_trait` + --> $DIR/inductive-canonical-cycle.rs:22:9 + | +LL | fn require_trait() + | ------------- required by a bound in this +LL | where +LL | (): Trait + | ^^^^^^^^^^^ required by this bound in `require_trait` + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0275`. diff --git a/src/test/ui/typeck/check-args-on-fn-err-2.rs b/src/test/ui/typeck/check-args-on-fn-err-2.rs new file mode 100644 index 0000000000000..af57dbe33177d --- /dev/null +++ b/src/test/ui/typeck/check-args-on-fn-err-2.rs @@ -0,0 +1,5 @@ +fn main() { + a((), 1i32 == 2u32); + //~^ ERROR cannot find function `a` in this scope + //~| ERROR mismatched types +} diff --git a/src/test/ui/typeck/check-args-on-fn-err-2.stderr b/src/test/ui/typeck/check-args-on-fn-err-2.stderr new file mode 100644 index 0000000000000..301bb88dbacf6 --- /dev/null +++ b/src/test/ui/typeck/check-args-on-fn-err-2.stderr @@ -0,0 +1,23 @@ +error[E0308]: mismatched types + --> $DIR/check-args-on-fn-err-2.rs:2:19 + | +LL | a((), 1i32 == 2u32); + | ---- ^^^^ expected `i32`, found `u32` + | | + | expected because this is `i32` + | +help: change the type of the numeric literal from `u32` to `i32` + | +LL | a((), 1i32 == 2i32); + | ~~~ + +error[E0425]: cannot find function `a` in this scope + --> $DIR/check-args-on-fn-err-2.rs:2:5 + | +LL | a((), 1i32 == 2u32); + | ^ not found in this scope + +error: aborting due to 2 previous errors + +Some errors have detailed explanations: E0308, E0425. +For more information about an error, try `rustc --explain E0308`. diff --git a/src/test/ui/typeck/check-args-on-fn-err.rs b/src/test/ui/typeck/check-args-on-fn-err.rs new file mode 100644 index 0000000000000..04b98ddd95295 --- /dev/null +++ b/src/test/ui/typeck/check-args-on-fn-err.rs @@ -0,0 +1,6 @@ +fn main() { + unknown(1, |glyf| { + //~^ ERROR: cannot find function `unknown` in this scope + let actual = glyf; + }); +} diff --git a/src/test/ui/typeck/check-args-on-fn-err.stderr b/src/test/ui/typeck/check-args-on-fn-err.stderr new file mode 100644 index 0000000000000..864d33e0e93bb --- /dev/null +++ b/src/test/ui/typeck/check-args-on-fn-err.stderr @@ -0,0 +1,9 @@ +error[E0425]: cannot find function `unknown` in this scope + --> $DIR/check-args-on-fn-err.rs:2:5 + | +LL | unknown(1, |glyf| { + | ^^^^^^^ not found in this scope + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0425`. diff --git a/src/test/ui/issues/issue-22375.rs b/src/test/ui/typeck/issue-22375.rs similarity index 100% rename from src/test/ui/issues/issue-22375.rs rename to src/test/ui/typeck/issue-22375.rs diff --git a/src/tools/tidy/src/ui_tests.rs b/src/tools/tidy/src/ui_tests.rs index 19e2528bb240a..f746bdeffd7ca 100644 --- a/src/tools/tidy/src/ui_tests.rs +++ b/src/tools/tidy/src/ui_tests.rs @@ -10,7 +10,7 @@ use std::path::Path; const ENTRY_LIMIT: usize = 1000; // FIXME: The following limits should be reduced eventually. const ROOT_ENTRY_LIMIT: usize = 939; -const ISSUES_ENTRY_LIMIT: usize = 2040; +const ISSUES_ENTRY_LIMIT: usize = 2020; fn check_entries(path: &Path, bad: &mut bool) { for dir in Walk::new(&path.join("test/ui")) { diff --git a/triagebot.toml b/triagebot.toml index 46a3bab42a17e..c7158a51861e5 100644 --- a/triagebot.toml +++ b/triagebot.toml @@ -342,6 +342,10 @@ cc = ["@BoxyUwU"] message = "Some changes occured in `rustc_ty_utils::consts.rs`" cc = ["@BoxyUwU"] +[mentions."compiler/rustc_trait_selection/src/solve] +message = "Some changes occurred to the core trait solver" +cc = ["@lcnr"] + [mentions."compiler/rustc_trait_selection/src/traits/engine.rs"] message = """ Some changes occurred in engine.rs, potentially modifying the public API \