Skip to content

Commit

Permalink
Auto merge of #105961 - fmease:iat-type-directed-probing, r=jackh726
Browse files Browse the repository at this point in the history
Type-directed probing for inherent associated types

When probing for inherent associated types (IATs), equate the Self-type found in the projection with the Self-type of the relevant inherent impl blocks and check if all predicates are satisfied.
Previously, we didn't look at the Self-type or at the bounds and just picked the first inherent impl block containing an associated type with the name we were searching for which is obviously incorrect.

Regarding the implementation, I basically copied what we do during method probing (`assemble_inherent_impl_probe`, `consider_probe`). Unfortunately, I had to duplicate a lot of the diagnostic code found in `rustc_hir_typeck::method::suggest` which we don't have access to in `rustc_hir_analysis`. Not sure if there is a simple way to unify the error handling. Note that in the future, `rustc_hir_analysis::astconv` might not actually be the place where we resolve inherent associated types (see #103621 (comment)) but `rustc_hir_typeck` (?) in which case the duplication may naturally just disappear. While inherent associated *constants* are currently resolved during "method" probing, I did not find a straightforward way to incorporate IAT lookup into it as types and values (functions & constants) are two separate entities for which distinct code paths are taken.

Fixes #104251 (incl. #104251 (comment)).
Fixes #105305.
Fixes #107468.

`@rustbot` label T-types F-inherent_associated_types
r? types
  • Loading branch information
bors committed Feb 20, 2023
2 parents 21e5b94 + f2253da commit 7b55296
Show file tree
Hide file tree
Showing 29 changed files with 946 additions and 84 deletions.
230 changes: 228 additions & 2 deletions compiler/rustc_hir_analysis/src/astconv/errors.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
use crate::astconv::AstConv;
use crate::errors::{ManualImplementation, MissingTypeParams};
use rustc_data_structures::fx::FxHashMap;
use rustc_errors::{pluralize, struct_span_err, Applicability, ErrorGuaranteed};
use rustc_errors::{pluralize, struct_span_err, Applicability, Diagnostic, ErrorGuaranteed};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use rustc_middle::ty;
use rustc_infer::traits::FulfillmentError;
use rustc_middle::ty::{self, Ty};
use rustc_session::parse::feature_err;
use rustc_span::lev_distance::find_best_match_for_name;
use rustc_span::symbol::{sym, Ident};
Expand Down Expand Up @@ -221,6 +222,231 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
err.emit()
}

pub(crate) fn complain_about_ambiguous_inherent_assoc_type(
&self,
name: Ident,
candidates: Vec<DefId>,
span: Span,
) -> ErrorGuaranteed {
let mut err = struct_span_err!(
self.tcx().sess,
name.span,
E0034,
"multiple applicable items in scope"
);
err.span_label(name.span, format!("multiple `{name}` found"));
self.note_ambiguous_inherent_assoc_type(&mut err, candidates, span);
err.emit()
}

// FIXME(fmease): Heavily adapted from `rustc_hir_typeck::method::suggest`. Deduplicate.
fn note_ambiguous_inherent_assoc_type(
&self,
err: &mut Diagnostic,
candidates: Vec<DefId>,
span: Span,
) {
let tcx = self.tcx();

// Dynamic limit to avoid hiding just one candidate, which is silly.
let limit = if candidates.len() == 5 { 5 } else { 4 };

for (index, &item) in candidates.iter().take(limit).enumerate() {
let impl_ = tcx.impl_of_method(item).unwrap();

let note_span = if item.is_local() {
Some(tcx.def_span(item))
} else if impl_.is_local() {
Some(tcx.def_span(impl_))
} else {
None
};

let title = if candidates.len() > 1 {
format!("candidate #{}", index + 1)
} else {
"the candidate".into()
};

let impl_ty = tcx.at(span).type_of(impl_).subst_identity();
let note = format!("{title} is defined in an impl for the type `{impl_ty}`");

if let Some(span) = note_span {
err.span_note(span, &note);
} else {
err.note(&note);
}
}
if candidates.len() > limit {
err.note(&format!("and {} others", candidates.len() - limit));
}
}

// FIXME(inherent_associated_types): Find similarly named associated types and suggest them.
pub(crate) fn complain_about_inherent_assoc_type_not_found(
&self,
name: Ident,
self_ty: Ty<'tcx>,
candidates: Vec<(DefId, (DefId, DefId))>,
fulfillment_errors: Vec<FulfillmentError<'tcx>>,
span: Span,
) -> ErrorGuaranteed {
// FIXME(fmease): This was copied in parts from an old version of `rustc_hir_typeck::method::suggest`.
// Either
// * update this code by applying changes similar to #106702 or by taking a
// Vec<(DefId, (DefId, DefId), Option<Vec<FulfillmentError<'tcx>>>)> or
// * deduplicate this code across the two crates.

let tcx = self.tcx();

let adt_did = self_ty.ty_adt_def().map(|def| def.did());
let add_def_label = |err: &mut Diagnostic| {
if let Some(did) = adt_did {
err.span_label(
tcx.def_span(did),
format!(
"associated item `{name}` not found for this {}",
tcx.def_kind(did).descr(did)
),
);
}
};

if fulfillment_errors.is_empty() {
// FIXME(fmease): Copied from `rustc_hir_typeck::method::probe`. Deduplicate.

let limit = if candidates.len() == 5 { 5 } else { 4 };
let type_candidates = candidates
.iter()
.take(limit)
.map(|&(impl_, _)| format!("- `{}`", tcx.at(span).type_of(impl_).subst_identity()))
.collect::<Vec<_>>()
.join("\n");
let additional_types = if candidates.len() > limit {
format!("\nand {} more types", candidates.len() - limit)
} else {
String::new()
};

let mut err = struct_span_err!(
tcx.sess,
name.span,
E0220,
"associated type `{name}` not found for `{self_ty}` in the current scope"
);
err.span_label(name.span, format!("associated item not found in `{self_ty}`"));
err.note(&format!(
"the associated type was found for\n{type_candidates}{additional_types}",
));
add_def_label(&mut err);
return err.emit();
}

let mut bound_spans = Vec::new();

let mut bound_span_label = |self_ty: Ty<'_>, obligation: &str, quiet: &str| {
let msg = format!(
"doesn't satisfy `{}`",
if obligation.len() > 50 { quiet } else { obligation }
);
match &self_ty.kind() {
// Point at the type that couldn't satisfy the bound.
ty::Adt(def, _) => bound_spans.push((tcx.def_span(def.did()), msg)),
// Point at the trait object that couldn't satisfy the bound.
ty::Dynamic(preds, _, _) => {
for pred in preds.iter() {
match pred.skip_binder() {
ty::ExistentialPredicate::Trait(tr) => {
bound_spans.push((tcx.def_span(tr.def_id), msg.clone()))
}
ty::ExistentialPredicate::Projection(_)
| ty::ExistentialPredicate::AutoTrait(_) => {}
}
}
}
// Point at the closure that couldn't satisfy the bound.
ty::Closure(def_id, _) => {
bound_spans.push((tcx.def_span(*def_id), format!("doesn't satisfy `{quiet}`")))
}
_ => {}
}
};

let format_pred = |pred: ty::Predicate<'tcx>| {
let bound_predicate = pred.kind();
match bound_predicate.skip_binder() {
ty::PredicateKind::Clause(ty::Clause::Projection(pred)) => {
let pred = bound_predicate.rebind(pred);
// `<Foo as Iterator>::Item = String`.
let projection_ty = pred.skip_binder().projection_ty;

let substs_with_infer_self = tcx.mk_substs(
std::iter::once(tcx.mk_ty_var(ty::TyVid::from_u32(0)).into())
.chain(projection_ty.substs.iter().skip(1)),
);

let quiet_projection_ty =
tcx.mk_alias_ty(projection_ty.def_id, substs_with_infer_self);

let term = pred.skip_binder().term;

let obligation = format!("{projection_ty} = {term}");
let quiet = format!("{quiet_projection_ty} = {term}");

bound_span_label(projection_ty.self_ty(), &obligation, &quiet);
Some((obligation, projection_ty.self_ty()))
}
ty::PredicateKind::Clause(ty::Clause::Trait(poly_trait_ref)) => {
let p = poly_trait_ref.trait_ref;
let self_ty = p.self_ty();
let path = p.print_only_trait_path();
let obligation = format!("{self_ty}: {path}");
let quiet = format!("_: {path}");
bound_span_label(self_ty, &obligation, &quiet);
Some((obligation, self_ty))
}
_ => None,
}
};

// FIXME(fmease): `rustc_hir_typeck::method::suggest` uses a `skip_list` to filter out some bounds.
// I would do the same here if it didn't mean more code duplication.
let mut bounds: Vec<_> = fulfillment_errors
.into_iter()
.map(|error| error.root_obligation.predicate)
.filter_map(format_pred)
.map(|(p, _)| format!("`{}`", p))
.collect();
bounds.sort();
bounds.dedup();

let mut err = tcx.sess.struct_span_err(
name.span,
&format!("the associated type `{name}` exists for `{self_ty}`, but its trait bounds were not satisfied")
);
if !bounds.is_empty() {
err.note(&format!(
"the following trait bounds were not satisfied:\n{}",
bounds.join("\n")
));
}
err.span_label(
name.span,
format!("associated type cannot be referenced on `{self_ty}` due to unsatisfied trait bounds")
);

bound_spans.sort();
bound_spans.dedup();
for (span, msg) in bound_spans {
if !tcx.sess.source_map().is_span_accessible(span) {
continue;
}
err.span_label(span, &msg);
}
add_def_label(&mut err);
err.emit()
}

/// When there are any missing associated types, emit an E0191 error and attempt to supply a
/// reasonable suggestion on how to write it. For the case of multiple associated types in the
/// same trait bound have the same name (as they come from different supertraits), we instead
Expand Down
Loading

0 comments on commit 7b55296

Please sign in to comment.