Skip to content

Commit 7b55296

Browse files
committed
Auto merge of rust-lang#105961 - fmease:iat-type-directed-probing, r=jackh726
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 rust-lang#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 rust-lang#104251 (incl. rust-lang#104251 (comment)). Fixes rust-lang#105305. Fixes rust-lang#107468. `@rustbot` label T-types F-inherent_associated_types r? types
2 parents 21e5b94 + f2253da commit 7b55296

29 files changed

+946
-84
lines changed

compiler/rustc_hir_analysis/src/astconv/errors.rs

+228-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
11
use crate::astconv::AstConv;
22
use crate::errors::{ManualImplementation, MissingTypeParams};
33
use rustc_data_structures::fx::FxHashMap;
4-
use rustc_errors::{pluralize, struct_span_err, Applicability, ErrorGuaranteed};
4+
use rustc_errors::{pluralize, struct_span_err, Applicability, Diagnostic, ErrorGuaranteed};
55
use rustc_hir as hir;
66
use rustc_hir::def_id::DefId;
7-
use rustc_middle::ty;
7+
use rustc_infer::traits::FulfillmentError;
8+
use rustc_middle::ty::{self, Ty};
89
use rustc_session::parse::feature_err;
910
use rustc_span::lev_distance::find_best_match_for_name;
1011
use rustc_span::symbol::{sym, Ident};
@@ -221,6 +222,231 @@ impl<'o, 'tcx> dyn AstConv<'tcx> + 'o {
221222
err.emit()
222223
}
223224

225+
pub(crate) fn complain_about_ambiguous_inherent_assoc_type(
226+
&self,
227+
name: Ident,
228+
candidates: Vec<DefId>,
229+
span: Span,
230+
) -> ErrorGuaranteed {
231+
let mut err = struct_span_err!(
232+
self.tcx().sess,
233+
name.span,
234+
E0034,
235+
"multiple applicable items in scope"
236+
);
237+
err.span_label(name.span, format!("multiple `{name}` found"));
238+
self.note_ambiguous_inherent_assoc_type(&mut err, candidates, span);
239+
err.emit()
240+
}
241+
242+
// FIXME(fmease): Heavily adapted from `rustc_hir_typeck::method::suggest`. Deduplicate.
243+
fn note_ambiguous_inherent_assoc_type(
244+
&self,
245+
err: &mut Diagnostic,
246+
candidates: Vec<DefId>,
247+
span: Span,
248+
) {
249+
let tcx = self.tcx();
250+
251+
// Dynamic limit to avoid hiding just one candidate, which is silly.
252+
let limit = if candidates.len() == 5 { 5 } else { 4 };
253+
254+
for (index, &item) in candidates.iter().take(limit).enumerate() {
255+
let impl_ = tcx.impl_of_method(item).unwrap();
256+
257+
let note_span = if item.is_local() {
258+
Some(tcx.def_span(item))
259+
} else if impl_.is_local() {
260+
Some(tcx.def_span(impl_))
261+
} else {
262+
None
263+
};
264+
265+
let title = if candidates.len() > 1 {
266+
format!("candidate #{}", index + 1)
267+
} else {
268+
"the candidate".into()
269+
};
270+
271+
let impl_ty = tcx.at(span).type_of(impl_).subst_identity();
272+
let note = format!("{title} is defined in an impl for the type `{impl_ty}`");
273+
274+
if let Some(span) = note_span {
275+
err.span_note(span, &note);
276+
} else {
277+
err.note(&note);
278+
}
279+
}
280+
if candidates.len() > limit {
281+
err.note(&format!("and {} others", candidates.len() - limit));
282+
}
283+
}
284+
285+
// FIXME(inherent_associated_types): Find similarly named associated types and suggest them.
286+
pub(crate) fn complain_about_inherent_assoc_type_not_found(
287+
&self,
288+
name: Ident,
289+
self_ty: Ty<'tcx>,
290+
candidates: Vec<(DefId, (DefId, DefId))>,
291+
fulfillment_errors: Vec<FulfillmentError<'tcx>>,
292+
span: Span,
293+
) -> ErrorGuaranteed {
294+
// FIXME(fmease): This was copied in parts from an old version of `rustc_hir_typeck::method::suggest`.
295+
// Either
296+
// * update this code by applying changes similar to #106702 or by taking a
297+
// Vec<(DefId, (DefId, DefId), Option<Vec<FulfillmentError<'tcx>>>)> or
298+
// * deduplicate this code across the two crates.
299+
300+
let tcx = self.tcx();
301+
302+
let adt_did = self_ty.ty_adt_def().map(|def| def.did());
303+
let add_def_label = |err: &mut Diagnostic| {
304+
if let Some(did) = adt_did {
305+
err.span_label(
306+
tcx.def_span(did),
307+
format!(
308+
"associated item `{name}` not found for this {}",
309+
tcx.def_kind(did).descr(did)
310+
),
311+
);
312+
}
313+
};
314+
315+
if fulfillment_errors.is_empty() {
316+
// FIXME(fmease): Copied from `rustc_hir_typeck::method::probe`. Deduplicate.
317+
318+
let limit = if candidates.len() == 5 { 5 } else { 4 };
319+
let type_candidates = candidates
320+
.iter()
321+
.take(limit)
322+
.map(|&(impl_, _)| format!("- `{}`", tcx.at(span).type_of(impl_).subst_identity()))
323+
.collect::<Vec<_>>()
324+
.join("\n");
325+
let additional_types = if candidates.len() > limit {
326+
format!("\nand {} more types", candidates.len() - limit)
327+
} else {
328+
String::new()
329+
};
330+
331+
let mut err = struct_span_err!(
332+
tcx.sess,
333+
name.span,
334+
E0220,
335+
"associated type `{name}` not found for `{self_ty}` in the current scope"
336+
);
337+
err.span_label(name.span, format!("associated item not found in `{self_ty}`"));
338+
err.note(&format!(
339+
"the associated type was found for\n{type_candidates}{additional_types}",
340+
));
341+
add_def_label(&mut err);
342+
return err.emit();
343+
}
344+
345+
let mut bound_spans = Vec::new();
346+
347+
let mut bound_span_label = |self_ty: Ty<'_>, obligation: &str, quiet: &str| {
348+
let msg = format!(
349+
"doesn't satisfy `{}`",
350+
if obligation.len() > 50 { quiet } else { obligation }
351+
);
352+
match &self_ty.kind() {
353+
// Point at the type that couldn't satisfy the bound.
354+
ty::Adt(def, _) => bound_spans.push((tcx.def_span(def.did()), msg)),
355+
// Point at the trait object that couldn't satisfy the bound.
356+
ty::Dynamic(preds, _, _) => {
357+
for pred in preds.iter() {
358+
match pred.skip_binder() {
359+
ty::ExistentialPredicate::Trait(tr) => {
360+
bound_spans.push((tcx.def_span(tr.def_id), msg.clone()))
361+
}
362+
ty::ExistentialPredicate::Projection(_)
363+
| ty::ExistentialPredicate::AutoTrait(_) => {}
364+
}
365+
}
366+
}
367+
// Point at the closure that couldn't satisfy the bound.
368+
ty::Closure(def_id, _) => {
369+
bound_spans.push((tcx.def_span(*def_id), format!("doesn't satisfy `{quiet}`")))
370+
}
371+
_ => {}
372+
}
373+
};
374+
375+
let format_pred = |pred: ty::Predicate<'tcx>| {
376+
let bound_predicate = pred.kind();
377+
match bound_predicate.skip_binder() {
378+
ty::PredicateKind::Clause(ty::Clause::Projection(pred)) => {
379+
let pred = bound_predicate.rebind(pred);
380+
// `<Foo as Iterator>::Item = String`.
381+
let projection_ty = pred.skip_binder().projection_ty;
382+
383+
let substs_with_infer_self = tcx.mk_substs(
384+
std::iter::once(tcx.mk_ty_var(ty::TyVid::from_u32(0)).into())
385+
.chain(projection_ty.substs.iter().skip(1)),
386+
);
387+
388+
let quiet_projection_ty =
389+
tcx.mk_alias_ty(projection_ty.def_id, substs_with_infer_self);
390+
391+
let term = pred.skip_binder().term;
392+
393+
let obligation = format!("{projection_ty} = {term}");
394+
let quiet = format!("{quiet_projection_ty} = {term}");
395+
396+
bound_span_label(projection_ty.self_ty(), &obligation, &quiet);
397+
Some((obligation, projection_ty.self_ty()))
398+
}
399+
ty::PredicateKind::Clause(ty::Clause::Trait(poly_trait_ref)) => {
400+
let p = poly_trait_ref.trait_ref;
401+
let self_ty = p.self_ty();
402+
let path = p.print_only_trait_path();
403+
let obligation = format!("{self_ty}: {path}");
404+
let quiet = format!("_: {path}");
405+
bound_span_label(self_ty, &obligation, &quiet);
406+
Some((obligation, self_ty))
407+
}
408+
_ => None,
409+
}
410+
};
411+
412+
// FIXME(fmease): `rustc_hir_typeck::method::suggest` uses a `skip_list` to filter out some bounds.
413+
// I would do the same here if it didn't mean more code duplication.
414+
let mut bounds: Vec<_> = fulfillment_errors
415+
.into_iter()
416+
.map(|error| error.root_obligation.predicate)
417+
.filter_map(format_pred)
418+
.map(|(p, _)| format!("`{}`", p))
419+
.collect();
420+
bounds.sort();
421+
bounds.dedup();
422+
423+
let mut err = tcx.sess.struct_span_err(
424+
name.span,
425+
&format!("the associated type `{name}` exists for `{self_ty}`, but its trait bounds were not satisfied")
426+
);
427+
if !bounds.is_empty() {
428+
err.note(&format!(
429+
"the following trait bounds were not satisfied:\n{}",
430+
bounds.join("\n")
431+
));
432+
}
433+
err.span_label(
434+
name.span,
435+
format!("associated type cannot be referenced on `{self_ty}` due to unsatisfied trait bounds")
436+
);
437+
438+
bound_spans.sort();
439+
bound_spans.dedup();
440+
for (span, msg) in bound_spans {
441+
if !tcx.sess.source_map().is_span_accessible(span) {
442+
continue;
443+
}
444+
err.span_label(span, &msg);
445+
}
446+
add_def_label(&mut err);
447+
err.emit()
448+
}
449+
224450
/// When there are any missing associated types, emit an E0191 error and attempt to supply a
225451
/// reasonable suggestion on how to write it. For the case of multiple associated types in the
226452
/// same trait bound have the same name (as they come from different supertraits), we instead

0 commit comments

Comments
 (0)