Skip to content

Commit e5a6a04

Browse files
committed
Add a note when a type implements a trait with the same name as the required one
This is useful when you have two dependencies that use different trait for the same thing and with the same name. The user can accidentally implement the bad one which might be confusing. This commits refactorizes existing diagnostics about multiple different crates with the same version and adds a note when similarly named traits are found. All diagnostics are merged into a single one.
1 parent bd4a800 commit e5a6a04

File tree

14 files changed

+358
-519
lines changed

14 files changed

+358
-519
lines changed

compiler/rustc_hir_typeck/src/method/suggest.rs

Lines changed: 10 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -4085,7 +4085,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
40854085
err: &mut Diag<'_>,
40864086
item_def_id: DefId,
40874087
hir_id: hir::HirId,
4088-
rcvr_ty: Option<Ty<'_>>,
4088+
rcvr_ty: Option<Ty<'tcx>>,
40894089
) -> bool {
40904090
let hir_id = self.tcx.parent_hir_id(hir_id);
40914091
let Some(traits) = self.tcx.in_scope_traits(hir_id) else { return false };
@@ -4096,49 +4096,16 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> {
40964096
if !self.tcx.is_trait(trait_def_id) {
40974097
return false;
40984098
}
4099-
let krate = self.tcx.crate_name(trait_def_id.krate);
4100-
let name = self.tcx.item_name(trait_def_id);
4101-
let candidates: Vec<_> = traits
4102-
.iter()
4103-
.filter(|c| {
4104-
c.def_id.krate != trait_def_id.krate
4105-
&& self.tcx.crate_name(c.def_id.krate) == krate
4106-
&& self.tcx.item_name(c.def_id) == name
4107-
})
4108-
.map(|c| (c.def_id, c.import_ids.get(0).cloned()))
4109-
.collect();
4110-
if candidates.is_empty() {
4099+
let hir::Node::Expr(rcvr) = self.tcx.hir_node(hir_id) else {
41114100
return false;
4112-
}
4113-
let item_span = self.tcx.def_span(item_def_id);
4114-
let msg = format!(
4115-
"there are multiple different versions of crate `{krate}` in the dependency graph",
4116-
);
4117-
let trait_span = self.tcx.def_span(trait_def_id);
4118-
let mut multi_span: MultiSpan = trait_span.into();
4119-
multi_span.push_span_label(trait_span, "this is the trait that is needed".to_string());
4120-
let descr = self.tcx.associated_item(item_def_id).descr();
4121-
let rcvr_ty =
4122-
rcvr_ty.map(|t| format!("`{t}`")).unwrap_or_else(|| "the receiver".to_string());
4123-
multi_span
4124-
.push_span_label(item_span, format!("the {descr} is available for {rcvr_ty} here"));
4125-
for (def_id, import_def_id) in candidates {
4126-
if let Some(import_def_id) = import_def_id {
4127-
multi_span.push_span_label(
4128-
self.tcx.def_span(import_def_id),
4129-
format!(
4130-
"`{name}` imported here doesn't correspond to the right version of crate \
4131-
`{krate}`",
4132-
),
4133-
);
4134-
}
4135-
multi_span.push_span_label(
4136-
self.tcx.def_span(def_id),
4137-
"this is the trait that was imported".to_string(),
4138-
);
4139-
}
4140-
err.span_note(multi_span, msg);
4141-
true
4101+
};
4102+
let trait_ref = ty::TraitRef::new(self.tcx, trait_def_id, rcvr_ty.into_iter());
4103+
let trait_pred = ty::Binder::dummy(ty::TraitPredicate {
4104+
trait_ref,
4105+
polarity: ty::PredicatePolarity::Positive,
4106+
});
4107+
let obligation = Obligation::new(self.tcx, self.misc(rcvr.span), self.param_env, trait_ref);
4108+
self.err_ctxt().note_different_trait_with_same_name(err, &obligation, trait_pred)
41424109
}
41434110

41444111
/// issue #102320, for `unwrap_or` with closure as argument, suggest `unwrap_or_else`

compiler/rustc_trait_selection/src/error_reporting/infer/mod.rs

Lines changed: 17 additions & 206 deletions
Original file line numberDiff line numberDiff line change
@@ -51,11 +51,8 @@ use std::path::PathBuf;
5151
use std::{cmp, fmt, iter};
5252

5353
use rustc_abi::ExternAbi;
54-
use rustc_ast::join_path_syms;
5554
use rustc_data_structures::fx::{FxIndexMap, FxIndexSet};
56-
use rustc_errors::{
57-
Applicability, Diag, DiagStyledString, IntoDiagArg, MultiSpan, StringPart, pluralize,
58-
};
55+
use rustc_errors::{Applicability, Diag, DiagStyledString, IntoDiagArg, StringPart, pluralize};
5956
use rustc_hir::def::DefKind;
6057
use rustc_hir::def_id::DefId;
6158
use rustc_hir::intravisit::Visitor;
@@ -66,15 +63,12 @@ use rustc_middle::bug;
6663
use rustc_middle::dep_graph::DepContext;
6764
use rustc_middle::traits::PatternOriginExpr;
6865
use rustc_middle::ty::error::{ExpectedFound, TypeError, TypeErrorToStringExt};
69-
use rustc_middle::ty::print::{
70-
PrintError, PrintTraitRefExt as _, WrapBinderMode, with_forced_trimmed_paths,
71-
};
66+
use rustc_middle::ty::print::{PrintTraitRefExt as _, WrapBinderMode, with_forced_trimmed_paths};
7267
use rustc_middle::ty::{
7368
self, List, ParamEnv, Region, Ty, TyCtxt, TypeFoldable, TypeSuperVisitable, TypeVisitable,
7469
TypeVisitableExt,
7570
};
76-
use rustc_span::def_id::LOCAL_CRATE;
77-
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Pos, Span, Symbol, sym};
71+
use rustc_span::{BytePos, DUMMY_SP, DesugaringKind, Pos, Span, sym};
7872
use tracing::{debug, instrument};
7973

8074
use crate::error_reporting::TypeErrCtxt;
@@ -216,213 +210,30 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> {
216210

217211
/// Adds a note if the types come from similarly named crates
218212
fn check_and_note_conflicting_crates(&self, err: &mut Diag<'_>, terr: TypeError<'tcx>) -> bool {
219-
// FIXME(estebank): unify with `report_similar_impl_candidates`. The message is similar,
220-
// even if the logic needed to detect the case is very different.
221-
use hir::def_id::CrateNum;
222-
use rustc_hir::definitions::DisambiguatedDefPathData;
223-
use ty::GenericArg;
224-
use ty::print::Printer;
225-
226-
struct ConflictingPathPrinter<'tcx> {
227-
tcx: TyCtxt<'tcx>,
228-
segments: Vec<Symbol>,
229-
}
230-
231-
impl<'tcx> Printer<'tcx> for ConflictingPathPrinter<'tcx> {
232-
fn tcx<'a>(&'a self) -> TyCtxt<'tcx> {
233-
self.tcx
234-
}
235-
236-
fn print_region(&mut self, _region: ty::Region<'_>) -> Result<(), PrintError> {
237-
unreachable!(); // because `print_path_with_generic_args` ignores the `GenericArgs`
238-
}
239-
240-
fn print_type(&mut self, _ty: Ty<'tcx>) -> Result<(), PrintError> {
241-
unreachable!(); // because `print_path_with_generic_args` ignores the `GenericArgs`
242-
}
243-
244-
fn print_dyn_existential(
245-
&mut self,
246-
_predicates: &'tcx ty::List<ty::PolyExistentialPredicate<'tcx>>,
247-
) -> Result<(), PrintError> {
248-
unreachable!(); // because `print_path_with_generic_args` ignores the `GenericArgs`
249-
}
250-
251-
fn print_const(&mut self, _ct: ty::Const<'tcx>) -> Result<(), PrintError> {
252-
unreachable!(); // because `print_path_with_generic_args` ignores the `GenericArgs`
253-
}
254-
255-
fn print_crate_name(&mut self, cnum: CrateNum) -> Result<(), PrintError> {
256-
self.segments = vec![self.tcx.crate_name(cnum)];
257-
Ok(())
258-
}
259-
260-
fn print_path_with_qualified(
261-
&mut self,
262-
_self_ty: Ty<'tcx>,
263-
_trait_ref: Option<ty::TraitRef<'tcx>>,
264-
) -> Result<(), PrintError> {
265-
Err(fmt::Error)
266-
}
267-
268-
fn print_path_with_impl(
269-
&mut self,
270-
_print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
271-
_self_ty: Ty<'tcx>,
272-
_trait_ref: Option<ty::TraitRef<'tcx>>,
273-
) -> Result<(), PrintError> {
274-
Err(fmt::Error)
275-
}
276-
277-
fn print_path_with_simple(
278-
&mut self,
279-
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
280-
disambiguated_data: &DisambiguatedDefPathData,
281-
) -> Result<(), PrintError> {
282-
print_prefix(self)?;
283-
self.segments.push(disambiguated_data.as_sym(true));
284-
Ok(())
285-
}
286-
287-
fn print_path_with_generic_args(
288-
&mut self,
289-
print_prefix: impl FnOnce(&mut Self) -> Result<(), PrintError>,
290-
_args: &[GenericArg<'tcx>],
291-
) -> Result<(), PrintError> {
292-
print_prefix(self)
293-
}
294-
}
295-
296-
let report_path_match = |err: &mut Diag<'_>, did1: DefId, did2: DefId, ty: &str| -> bool {
297-
// Only report definitions from different crates. If both definitions
298-
// are from a local module we could have false positives, e.g.
299-
// let _ = [{struct Foo; Foo}, {struct Foo; Foo}];
300-
if did1.krate != did2.krate {
301-
let abs_path = |def_id| {
302-
let mut p = ConflictingPathPrinter { tcx: self.tcx, segments: vec![] };
303-
p.print_def_path(def_id, &[]).map(|_| p.segments)
304-
};
305-
306-
// We compare strings because DefPath can be different for imported and
307-
// non-imported crates.
308-
let expected_str = self.tcx.def_path_str(did1);
309-
let found_str = self.tcx.def_path_str(did2);
310-
let Ok(expected_abs) = abs_path(did1) else { return false };
311-
let Ok(found_abs) = abs_path(did2) else { return false };
312-
let same_path = expected_str == found_str || expected_abs == found_abs;
313-
if same_path {
314-
// We want to use as unique a type path as possible. If both types are "locally
315-
// known" by the same name, we use the "absolute path" which uses the original
316-
// crate name instead.
317-
let (expected, found) = if expected_str == found_str {
318-
(join_path_syms(&expected_abs), join_path_syms(&found_abs))
319-
} else {
320-
(expected_str, found_str)
321-
};
322-
323-
// We've displayed "expected `a::b`, found `a::b`". We add context to
324-
// differentiate the different cases where that might happen.
325-
let expected_crate_name = self.tcx.crate_name(did1.krate);
326-
let found_crate_name = self.tcx.crate_name(did2.krate);
327-
let same_crate = expected_crate_name == found_crate_name;
328-
let expected_sp = self.tcx.def_span(did1);
329-
let found_sp = self.tcx.def_span(did2);
330-
331-
let both_direct_dependencies = if !did1.is_local()
332-
&& !did2.is_local()
333-
&& let Some(data1) = self.tcx.extern_crate(did1.krate)
334-
&& let Some(data2) = self.tcx.extern_crate(did2.krate)
335-
&& data1.dependency_of == LOCAL_CRATE
336-
&& data2.dependency_of == LOCAL_CRATE
337-
{
338-
// If both crates are directly depended on, we don't want to mention that
339-
// in the final message, as it is redundant wording.
340-
// We skip the case of semver trick, where one version of the local crate
341-
// depends on another version of itself by checking that both crates at play
342-
// are not the current one.
343-
true
344-
} else {
345-
false
346-
};
347-
348-
let mut span: MultiSpan = vec![expected_sp, found_sp].into();
349-
span.push_span_label(
350-
self.tcx.def_span(did1),
351-
format!("this is the expected {ty} `{expected}`"),
352-
);
353-
span.push_span_label(
354-
self.tcx.def_span(did2),
355-
format!("this is the found {ty} `{found}`"),
356-
);
357-
for def_id in [did1, did2] {
358-
let crate_name = self.tcx.crate_name(def_id.krate);
359-
if !def_id.is_local()
360-
&& let Some(data) = self.tcx.extern_crate(def_id.krate)
361-
{
362-
let descr = if same_crate {
363-
"one version of".to_string()
364-
} else {
365-
format!("one {ty} comes from")
366-
};
367-
let dependency = if both_direct_dependencies {
368-
if let rustc_session::cstore::ExternCrateSource::Extern(def_id) =
369-
data.src
370-
&& let Some(name) = self.tcx.opt_item_name(def_id)
371-
{
372-
format!(", which is renamed locally to `{name}`")
373-
} else {
374-
String::new()
375-
}
376-
} else if data.dependency_of == LOCAL_CRATE {
377-
", as a direct dependency of the current crate".to_string()
378-
} else {
379-
let dep = self.tcx.crate_name(data.dependency_of);
380-
format!(", as a dependency of crate `{dep}`")
381-
};
382-
span.push_span_label(
383-
data.span,
384-
format!("{descr} crate `{crate_name}` used here{dependency}"),
385-
);
386-
}
387-
}
388-
let msg = if (did1.is_local() || did2.is_local()) && same_crate {
389-
format!(
390-
"the crate `{expected_crate_name}` is compiled multiple times, \
391-
possibly with different configurations",
392-
)
393-
} else if same_crate {
394-
format!(
395-
"two different versions of crate `{expected_crate_name}` are being \
396-
used; two types coming from two different versions of the same crate \
397-
are different types even if they look the same",
398-
)
399-
} else {
400-
format!(
401-
"two types coming from two different crates are different types even \
402-
if they look the same",
403-
)
404-
};
405-
err.span_note(span, msg);
406-
if same_crate {
407-
err.help("you can use `cargo tree` to explore your dependency tree");
408-
}
409-
return true;
410-
}
411-
}
412-
false
413-
};
414213
match terr {
415214
TypeError::Sorts(ref exp_found) => {
416215
// if they are both "path types", there's a chance of ambiguity
417216
// due to different versions of the same crate
418217
if let (&ty::Adt(exp_adt, _), &ty::Adt(found_adt, _)) =
419218
(exp_found.expected.kind(), exp_found.found.kind())
420219
{
421-
return report_path_match(err, exp_adt.did(), found_adt.did(), "type");
220+
return self.check_same_definition_different_crate(
221+
err,
222+
exp_adt.did(),
223+
[found_adt.did()].into_iter(),
224+
|did| vec![self.tcx.def_span(did)],
225+
"type",
226+
);
422227
}
423228
}
424229
TypeError::Traits(ref exp_found) => {
425-
return report_path_match(err, exp_found.expected, exp_found.found, "trait");
230+
return self.check_same_definition_different_crate(
231+
err,
232+
exp_found.expected,
233+
[exp_found.found].into_iter(),
234+
|did| vec![self.tcx.def_span(did)],
235+
"trait",
236+
);
426237
}
427238
_ => (), // FIXME(#22750) handle traits and stuff
428239
}

0 commit comments

Comments
 (0)