Skip to content

Don't build ParamEnv and do trait solving in ItemCtxts when lowering IATs #140247

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 98 additions & 3 deletions compiler/rustc_hir_analysis/src/collect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,22 @@ use rustc_infer::traits::ObligationCause;
use rustc_middle::hir::nested_filter;
use rustc_middle::query::Providers;
use rustc_middle::ty::util::{Discr, IntTypeExt};
use rustc_middle::ty::{self, AdtKind, Const, IsSuggestable, Ty, TyCtxt, TypingMode, fold_regions};
use rustc_middle::ty::{
self, AdtKind, Const, IsSuggestable, Ty, TyCtxt, TypeFolder, TypeSuperFoldable,
TypeVisitableExt, TypingMode, fold_regions,
};
use rustc_middle::{bug, span_bug};
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym};
use rustc_trait_selection::error_reporting::traits::suggestions::NextTypeParamName;
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::ObligationCtxt;
use rustc_trait_selection::traits::{FulfillmentError, ObligationCtxt};
use tracing::{debug, instrument};

use crate::errors;
use crate::hir_ty_lowering::errors::assoc_tag_str;
use crate::hir_ty_lowering::{FeedConstTy, HirTyLowerer, RegionInferReason};
use crate::hir_ty_lowering::{
FeedConstTy, HirTyLowerer, InherentAssocCandidate, RegionInferReason,
};

pub(crate) mod dump;
mod generics_of;
Expand Down Expand Up @@ -444,6 +449,96 @@ impl<'tcx> HirTyLowerer<'tcx> for ItemCtxt<'tcx> {
self.tcx.at(span).type_param_predicates((self.item_def_id, def_id, assoc_ident))
}

fn select_inherent_assoc_candidates(
&self,
span: Span,
self_ty: Ty<'tcx>,
candidates: &Vec<InherentAssocCandidate>,
) -> (Vec<InherentAssocCandidate>, Vec<FulfillmentError<'tcx>>) {
// This is all rather hacky. We attempt to unify impl headers against this self ty
// (even though we aren't really in a position to do so) as users of IATs in signatures
// are unable to explicitly disambiguate which impl to use other than by specifying the
// self type.

struct ReplaceAliasesWithInfer<'tcx, 'a>(&'a InferCtxt<'tcx>);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need this folder w/ the new solver? We could just relate and throw away all the alias-relate predicates?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah with the new solver this is unnecessary


impl<'tcx> TypeFolder<TyCtxt<'tcx>> for ReplaceAliasesWithInfer<'tcx, '_> {
fn cx(&self) -> TyCtxt<'tcx> {
self.0.tcx
}

fn fold_ty(&mut self, ty: Ty<'tcx>) -> Ty<'tcx> {
if let ty::Alias(_, _) = ty.kind() {
Copy link
Member Author

@BoxyUwU BoxyUwU Apr 24, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Possibly we're supposed to handle free type aliases specially here instead of replacing them with inference variables as it should be possible to just normalize them. Though that's not the behaviour that would happen w/ new solver deferred projection equality + ignoring returned goals

return self.0.next_ty_var(DUMMY_SP);
}

ty.super_fold_with(self)
}

fn fold_const(&mut self, ct: Const<'tcx>) -> Const<'tcx> {
if let ty::ConstKind::Unevaluated(_) = ct.kind() {
return self.0.next_const_var(DUMMY_SP);
}

ct.super_fold_with(self)
}
}

assert!(!self_ty.has_infer());
let infcx =
self.tcx().infer_ctxt().ignoring_regions().build(TypingMode::non_body_analysis());

// We replace all the aliases in the self type with infer vars to avoid filtering out
// candidates that only differ in how normalized alias is. This shouldn't be necessary
// under the new solver which emits alias relate goals instead of expecting inputs to
// already be fully normalized. FIXME(-Znext-solver)
//
// We don't just call the normal normalization routine here as we can't provide the
// correct `ParamEnv` and it seems dubious to invoke arbitrary trait solving under
// the wrong `ParamEnv`.
let self_ty = ReplaceAliasesWithInfer(&infcx).fold_ty(self_ty);

let mut universes = if self_ty.has_escaping_bound_vars() {
vec![None; self_ty.outer_exclusive_binder().as_usize()]
} else {
vec![]
};

let candidates = rustc_trait_selection::traits::with_replaced_escaping_bound_vars(
&infcx,
&mut universes,
self_ty,
|self_ty| {
infcx.probe(|_| {
candidates
.into_iter()
.filter(|&&InherentAssocCandidate { impl_, .. }| {
let impl_ty = self
.tcx()
.type_of(impl_)
.instantiate(self.tcx(), infcx.fresh_args_for_item(span, impl_));
// See comment on doing this operation for `self_ty`
let impl_ty = ReplaceAliasesWithInfer(&infcx).fold_ty(impl_ty);

use rustc_trait_selection::infer::DefineOpaqueTypes;
infcx
// Using an empty `ParamEnv` is pretty weird but we don't actually
// do anything with any of the returned obligations so it doesn't
// matter too much. Building the correct `ParamEnv` would also result
// in undesirable query cycles.
.at(&ObligationCause::dummy(), ty::ParamEnv::empty())
.eq(DefineOpaqueTypes::No, self_ty, impl_ty)
.is_ok()
})
.cloned()
.collect()
})
},
);

(candidates, vec![])
}

fn lower_assoc_shared(
&self,
span: Span,
Expand Down
7 changes: 4 additions & 3 deletions compiler/rustc_hir_analysis/src/hir_ty_lowering/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ use rustc_trait_selection::traits::{
};
use smallvec::SmallVec;

use super::InherentAssocCandidate;
use crate::errors::{
self, AssocItemConstraintsNotAllowedHere, ManualImplementation, MissingTypeParams,
ParenthesizedFnTraitExpansion, TraitObjectDeclaredWithNoTraits,
Expand Down Expand Up @@ -570,7 +571,7 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
&self,
name: Ident,
self_ty: Ty<'tcx>,
candidates: Vec<(DefId, (DefId, DefId))>,
candidates: Vec<InherentAssocCandidate>,
fulfillment_errors: Vec<FulfillmentError<'tcx>>,
span: Span,
assoc_tag: ty::AssocTag,
Expand Down Expand Up @@ -604,8 +605,8 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
let type_candidates = candidates
.iter()
.take(limit)
.map(|&(impl_, _)| {
format!("- `{}`", tcx.at(span).type_of(impl_).instantiate_identity())
.map(|cand| {
format!("- `{}`", tcx.at(span).type_of(cand.impl_).instantiate_identity())
})
.collect::<Vec<_>>()
.join("\n");
Expand Down
150 changes: 38 additions & 112 deletions compiler/rustc_hir_analysis/src/hir_ty_lowering/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,13 @@ use rustc_hir::def::{CtorKind, CtorOf, DefKind, Res};
use rustc_hir::def_id::{DefId, LocalDefId};
use rustc_hir::{self as hir, AnonConst, GenericArg, GenericArgs, HirId};
use rustc_infer::infer::{InferCtxt, TyCtxtInferExt};
use rustc_infer::traits::ObligationCause;
use rustc_macros::{TypeFoldable, TypeVisitable};
use rustc_middle::middle::stability::AllowUnstable;
use rustc_middle::mir::interpret::LitToConstInput;
use rustc_middle::ty::print::PrintPolyTraitRefExt as _;
use rustc_middle::ty::{
self, AssocTag, Const, GenericArgKind, GenericArgsRef, GenericParamDefKind, ParamEnv, Ty,
TyCtxt, TypeVisitableExt, TypingMode, Upcast, fold_regions,
self, AssocTag, Const, GenericArgKind, GenericArgsRef, GenericParamDefKind, Ty, TyCtxt,
TypeVisitableExt, TypingMode, Upcast, fold_regions,
};
use rustc_middle::{bug, span_bug};
use rustc_session::lint::builtin::AMBIGUOUS_ASSOCIATED_ITEMS;
Expand All @@ -48,7 +48,7 @@ use rustc_span::edit_distance::find_best_match_for_name;
use rustc_span::{DUMMY_SP, Ident, Span, Symbol, kw, sym};
use rustc_trait_selection::infer::InferCtxtExt;
use rustc_trait_selection::traits::wf::object_region_bounds;
use rustc_trait_selection::traits::{self, ObligationCtxt};
use rustc_trait_selection::traits::{self, FulfillmentError};
use tracing::{debug, instrument};

use self::errors::assoc_tag_str;
Expand Down Expand Up @@ -101,6 +101,13 @@ pub enum RegionInferReason<'a> {
OutlivesBound,
}

#[derive(Copy, Clone, TypeFoldable, TypeVisitable, Debug)]
pub struct InherentAssocCandidate {
pub impl_: DefId,
pub assoc_item: DefId,
pub scope: DefId,
}

/// A context which can lower type-system entities from the [HIR][hir] to
/// the [`rustc_middle::ty`] representation.
///
Expand Down Expand Up @@ -150,6 +157,13 @@ pub trait HirTyLowerer<'tcx> {
assoc_ident: Ident,
) -> ty::EarlyBinder<'tcx, &'tcx [(ty::Clause<'tcx>, Span)]>;

fn select_inherent_assoc_candidates(
&self,
span: Span,
self_ty: Ty<'tcx>,
candidates: &Vec<InherentAssocCandidate>,
) -> (Vec<InherentAssocCandidate>, Vec<FulfillmentError<'tcx>>);

/// Lower an associated type/const (from a trait) to a projection.
///
/// This method has to be defined by the concrete lowering context because
Expand Down Expand Up @@ -1513,48 +1527,32 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
.filter_map(|&impl_| {
let (item, scope) =
self.probe_assoc_item_unchecked(name, assoc_tag, block, impl_)?;
Some((impl_, (item.def_id, scope)))
Some(InherentAssocCandidate { impl_, assoc_item: item.def_id, scope })
})
.collect();

if candidates.is_empty() {
return Ok(None);
}

//
// Select applicable inherent associated type candidates modulo regions.
//

// In contexts that have no inference context, just make a new one.
// We do need a local variable to store it, though.
let infcx = match self.infcx() {
Some(infcx) => infcx,
None => {
assert!(!self_ty.has_infer());
&tcx.infer_ctxt().ignoring_regions().build(TypingMode::non_body_analysis())
}
};
let (applicable_candidates, fulfillment_errors) =
self.select_inherent_assoc_candidates(span, self_ty, &candidates);

// FIXME(inherent_associated_types): Acquiring the ParamEnv this early leads to cycle errors
// when inside of an ADT (#108491) or where clause.
let param_env = tcx.param_env(block.owner);
let InherentAssocCandidate { impl_, assoc_item, scope: def_scope } =
match &applicable_candidates[..] {
&[] => Err(self.complain_about_inherent_assoc_not_found(
name,
self_ty,
candidates,
fulfillment_errors,
span,
assoc_tag,
)),

let mut universes = if self_ty.has_escaping_bound_vars() {
vec![None; self_ty.outer_exclusive_binder().as_usize()]
} else {
vec![]
};
&[applicable_candidate] => Ok(applicable_candidate),

let (impl_, (assoc_item, def_scope)) = crate::traits::with_replaced_escaping_bound_vars(
infcx,
&mut universes,
self_ty,
|self_ty| {
self.select_inherent_assoc_candidates(
infcx, name, span, self_ty, param_env, candidates, assoc_tag,
)
},
)?;
&[_, ..] => Err(self.complain_about_ambiguous_inherent_assoc(
name,
candidates.into_iter().map(|cand| cand.assoc_item).collect(),
span,
)),
}?;

self.check_assoc_item(assoc_item, name, def_scope, block, span);

Expand All @@ -1571,78 +1569,6 @@ impl<'tcx> dyn HirTyLowerer<'tcx> + '_ {
Ok(Some((assoc_item, args)))
}

fn select_inherent_assoc_candidates(
&self,
infcx: &InferCtxt<'tcx>,
name: Ident,
span: Span,
self_ty: Ty<'tcx>,
param_env: ParamEnv<'tcx>,
candidates: Vec<(DefId, (DefId, DefId))>,
assoc_tag: ty::AssocTag,
) -> Result<(DefId, (DefId, DefId)), ErrorGuaranteed> {
let tcx = self.tcx();
let mut fulfillment_errors = Vec::new();

let applicable_candidates: Vec<_> = candidates
.iter()
.copied()
.filter(|&(impl_, _)| {
infcx.probe(|_| {
let ocx = ObligationCtxt::new_with_diagnostics(infcx);
let self_ty = ocx.normalize(&ObligationCause::dummy(), param_env, self_ty);

let impl_args = infcx.fresh_args_for_item(span, impl_);
let impl_ty = tcx.type_of(impl_).instantiate(tcx, impl_args);
let impl_ty = ocx.normalize(&ObligationCause::dummy(), param_env, impl_ty);

// Check that the self types can be related.
if ocx.eq(&ObligationCause::dummy(), param_env, impl_ty, self_ty).is_err() {
return false;
}

// Check whether the impl imposes obligations we have to worry about.
let impl_bounds = tcx.predicates_of(impl_).instantiate(tcx, impl_args);
let impl_bounds =
ocx.normalize(&ObligationCause::dummy(), param_env, impl_bounds);
let impl_obligations = traits::predicates_for_generics(
|_, _| ObligationCause::dummy(),
param_env,
impl_bounds,
);
ocx.register_obligations(impl_obligations);

let mut errors = ocx.select_where_possible();
if !errors.is_empty() {
fulfillment_errors.append(&mut errors);
return false;
}

true
})
})
.collect();

match &applicable_candidates[..] {
&[] => Err(self.complain_about_inherent_assoc_not_found(
name,
self_ty,
candidates,
fulfillment_errors,
span,
assoc_tag,
)),

&[applicable_candidate] => Ok(applicable_candidate),

&[_, ..] => Err(self.complain_about_ambiguous_inherent_assoc(
name,
applicable_candidates.into_iter().map(|(_, (candidate, _))| candidate).collect(),
span,
)),
}
}

/// Given name and kind search for the assoc item in the provided scope and check if it's accessible[^1].
///
/// [^1]: I.e., accessible in the provided scope wrt. visibility and stability.
Expand Down
Loading
Loading