From e1ae7b783781b93ec6a112ce586f75ac8db8c0d4 Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Mon, 22 Sep 2025 07:22:37 +0200 Subject: [PATCH 1/4] tests: Check error when struct from wrong crate version is used for impl --- tests/run-make/duplicate-dependency/foo-v1.rs | 1 + tests/run-make/duplicate-dependency/foo-v2.rs | 1 + tests/run-make/duplicate-dependency/main.rs | 15 +++++++ .../run-make/duplicate-dependency/main.stderr | 18 ++++++++ .../duplicate-dependency/re-export-foo.rs | 3 ++ tests/run-make/duplicate-dependency/rmake.rs | 43 +++++++++++++++++++ 6 files changed, 81 insertions(+) create mode 100644 tests/run-make/duplicate-dependency/foo-v1.rs create mode 100644 tests/run-make/duplicate-dependency/foo-v2.rs create mode 100644 tests/run-make/duplicate-dependency/main.rs create mode 100644 tests/run-make/duplicate-dependency/main.stderr create mode 100644 tests/run-make/duplicate-dependency/re-export-foo.rs create mode 100644 tests/run-make/duplicate-dependency/rmake.rs diff --git a/tests/run-make/duplicate-dependency/foo-v1.rs b/tests/run-make/duplicate-dependency/foo-v1.rs new file mode 100644 index 0000000000000..4a835673a596b --- /dev/null +++ b/tests/run-make/duplicate-dependency/foo-v1.rs @@ -0,0 +1 @@ +pub struct Foo; diff --git a/tests/run-make/duplicate-dependency/foo-v2.rs b/tests/run-make/duplicate-dependency/foo-v2.rs new file mode 100644 index 0000000000000..4a835673a596b --- /dev/null +++ b/tests/run-make/duplicate-dependency/foo-v2.rs @@ -0,0 +1 @@ +pub struct Foo; diff --git a/tests/run-make/duplicate-dependency/main.rs b/tests/run-make/duplicate-dependency/main.rs new file mode 100644 index 0000000000000..b22d9581c9a40 --- /dev/null +++ b/tests/run-make/duplicate-dependency/main.rs @@ -0,0 +1,15 @@ +struct Bar; + +impl From for foo::Foo { + fn from(_: Bar) -> Self { + foo::Foo + } +} + +fn main() { + // The user might wrongly expect this to work since From for Foo + // implies Into for Bar. What the user missed is that different + // versions of Foo exist in the dependency graph, and the impl is for the + // wrong version. + re_export_foo::into_foo(Bar); +} diff --git a/tests/run-make/duplicate-dependency/main.stderr b/tests/run-make/duplicate-dependency/main.stderr new file mode 100644 index 0000000000000..2c912f872fe35 --- /dev/null +++ b/tests/run-make/duplicate-dependency/main.stderr @@ -0,0 +1,18 @@ +error[E0277]: the trait bound `re_export_foo::foo::Foo: From` is not satisfied + --> main.rs:14:29 + | +LL | re_export_foo::into_foo(Bar); + | ----------------------- ^^^ the trait `From` is not implemented for `re_export_foo::foo::Foo` + | | + | required by a bound introduced by this call + | + = note: required for `Bar` to implement `Into` +note: required by a bound in `into_foo` + --> $DIR/re-export-foo.rs:3:25 + | +LL | pub fn into_foo(_: impl Into) {} + | ^^^^^^^^^^^^^^ required by this bound in `into_foo` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0277`. diff --git a/tests/run-make/duplicate-dependency/re-export-foo.rs b/tests/run-make/duplicate-dependency/re-export-foo.rs new file mode 100644 index 0000000000000..b346cfcee9b1f --- /dev/null +++ b/tests/run-make/duplicate-dependency/re-export-foo.rs @@ -0,0 +1,3 @@ +pub use foo; + +pub fn into_foo(_: impl Into) {} diff --git a/tests/run-make/duplicate-dependency/rmake.rs b/tests/run-make/duplicate-dependency/rmake.rs new file mode 100644 index 0000000000000..13ab4caaba56f --- /dev/null +++ b/tests/run-make/duplicate-dependency/rmake.rs @@ -0,0 +1,43 @@ +use run_make_support::{Rustc, cwd, diff, rust_lib_name, rustc}; + +fn rustc_with_common_args() -> Rustc { + let mut rustc = rustc(); + rustc.remap_path_prefix(cwd(), "$DIR"); + rustc.edition("2018"); // Don't require `extern crate` + rustc +} + +fn main() { + rustc_with_common_args() + .input("foo-v1.rs") + .crate_type("rlib") + .crate_name("foo") + .extra_filename("-v1") + .metadata("-v1") + .run(); + + rustc_with_common_args() + .input("foo-v2.rs") + .crate_type("rlib") + .crate_name("foo") + .extra_filename("-v2") + .metadata("-v2") + .run(); + + rustc_with_common_args() + .input("re-export-foo.rs") + .crate_type("rlib") + .extern_("foo", rust_lib_name("foo-v2")) + .run(); + + let stderr = rustc_with_common_args() + .input("main.rs") + .extern_("foo", rust_lib_name("foo-v1")) + .extern_("re_export_foo", rust_lib_name("re_export_foo")) + .library_search_path(cwd()) + .ui_testing() + .run_fail() + .stderr_utf8(); + + diff().expected_file("main.stderr").actual_text("(rustc)", &stderr).run(); +} From 78c2e58a7acb3a73e65b9ccfe4a73e79baa0d9ca Mon Sep 17 00:00:00 2001 From: Yotam Ofek Date: Wed, 1 Oct 2025 19:58:49 +0300 Subject: [PATCH 2/4] [rustdoc] Optimize "highlight::end_expansion" --- src/librustdoc/html/highlight.rs | 39 +++++++++++++------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/src/librustdoc/html/highlight.rs b/src/librustdoc/html/highlight.rs index 1dcb4dcc3ff83..eeb916494d1e7 100644 --- a/src/librustdoc/html/highlight.rs +++ b/src/librustdoc/html/highlight.rs @@ -8,7 +8,7 @@ use std::borrow::Cow; use std::collections::VecDeque; use std::fmt::{self, Display, Write}; -use std::iter; +use std::{cmp, iter}; use rustc_data_structures::fx::FxIndexMap; use rustc_lexer::{Cursor, FrontmatterAllowed, LiteralKind, TokenKind}; @@ -345,33 +345,26 @@ fn end_expansion<'a, W: Write>( token_handler.pending_elems.push((Cow::Borrowed(""), Some(Class::Expansion))); return Some(expanded_code); } - if expansion_start_tags.is_empty() && token_handler.closing_tags.is_empty() { - // No need tag opened so we can just close expansion. - token_handler.pending_elems.push((Cow::Borrowed(""), Some(Class::Expansion))); - return None; - } - // If tags were opened inside the expansion, we need to close them and re-open them outside - // of the expansion span. - let mut out = String::new(); - let mut end = String::new(); + let skip = iter::zip(token_handler.closing_tags.as_slice(), expansion_start_tags) + .position(|(tag, start_tag)| tag != start_tag) + .unwrap_or_else(|| cmp::min(token_handler.closing_tags.len(), expansion_start_tags.len())); - let mut closing_tags = token_handler.closing_tags.iter().peekable(); - let mut start_closing_tags = expansion_start_tags.iter().peekable(); + let tags = iter::chain( + expansion_start_tags.iter().skip(skip), + token_handler.closing_tags.iter().skip(skip), + ); - while let (Some(tag), Some(start_tag)) = (closing_tags.peek(), start_closing_tags.peek()) - && tag == start_tag - { - closing_tags.next(); - start_closing_tags.next(); + let mut elem = Cow::Borrowed(""); + + for (tag, _) in tags.clone() { + elem.to_mut().push_str(tag); } - for (tag, class) in start_closing_tags.chain(closing_tags) { - out.push_str(tag); - end.push_str(&format!("", class.as_html())); + for (_, class) in tags { + write!(elem.to_mut(), "", class.as_html()).unwrap(); } - token_handler - .pending_elems - .push((Cow::Owned(format!("{out}{end}")), Some(Class::Expansion))); + + token_handler.pending_elems.push((elem, Some(Class::Expansion))); None } From 21dd997aec1af5200a8b726ae6645f40b87baf7c Mon Sep 17 00:00:00 2001 From: usamoi Date: Tue, 26 Aug 2025 16:24:29 +0800 Subject: [PATCH 3/4] support link modifier `as-needed` for raw-dylib-elf --- compiler/rustc_attr_parsing/messages.ftl | 2 +- .../src/attributes/link_attrs.rs | 9 ++-- compiler/rustc_codegen_ssa/src/back/link.rs | 12 +++--- .../src/back/link/raw_dylib.rs | 24 ++++++----- .../rustc_hir/src/attrs/data_structures.rs | 18 +++++--- compiler/rustc_metadata/src/native_libs.rs | 2 +- .../rustc_session/src/config/native_libs.rs | 3 +- .../linkage-attr/raw-dylib/elf/as_needed.rs | 43 +++++++++++++++++++ 8 files changed, 82 insertions(+), 31 deletions(-) create mode 100644 tests/ui/linkage-attr/raw-dylib/elf/as_needed.rs diff --git a/compiler/rustc_attr_parsing/messages.ftl b/compiler/rustc_attr_parsing/messages.ftl index a432fa099ef4a..8b6b762f43102 100644 --- a/compiler/rustc_attr_parsing/messages.ftl +++ b/compiler/rustc_attr_parsing/messages.ftl @@ -1,5 +1,5 @@ attr_parsing_as_needed_compatibility = - linking modifier `as-needed` is only compatible with `dylib` and `framework` linking kinds + linking modifier `as-needed` is only compatible with `dylib`, `framework` and `raw-dylib` linking kinds attr_parsing_bundle_needs_static = linking modifier `bundle` is only compatible with `static` linking kind diff --git a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs index 04eaa485f736c..09f7bfa24e823 100644 --- a/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs +++ b/compiler/rustc_attr_parsing/src/attributes/link_attrs.rs @@ -180,7 +180,8 @@ impl CombineAttributeParser for LinkParser { } (sym::as_dash_needed, Some(NativeLibKind::Dylib { as_needed })) - | (sym::as_dash_needed, Some(NativeLibKind::Framework { as_needed })) => { + | (sym::as_dash_needed, Some(NativeLibKind::Framework { as_needed })) + | (sym::as_dash_needed, Some(NativeLibKind::RawDylib { as_needed })) => { report_unstable_modifier!(native_link_modifiers_as_needed); assign_modifier(as_needed) } @@ -219,12 +220,12 @@ impl CombineAttributeParser for LinkParser { // Do this outside of the loop so that `import_name_type` can be specified before `kind`. if let Some((_, span)) = import_name_type { - if kind != Some(NativeLibKind::RawDylib) { + if !matches!(kind, Some(NativeLibKind::RawDylib { .. })) { cx.emit_err(ImportNameTypeRaw { span }); } } - if let Some(NativeLibKind::RawDylib) = kind + if let Some(NativeLibKind::RawDylib { .. }) = kind && name.as_str().contains('\0') { cx.emit_err(RawDylibNoNul { span: name_span }); @@ -315,7 +316,7 @@ impl LinkParser { cx.emit_err(RawDylibOnlyWindows { span: nv.value_span }); } - NativeLibKind::RawDylib + NativeLibKind::RawDylib { as_needed: None } } sym::link_dash_arg => { if !features.link_arg_attribute() { diff --git a/compiler/rustc_codegen_ssa/src/back/link.rs b/compiler/rustc_codegen_ssa/src/back/link.rs index db2f2dd65b0b5..ea538d3d46981 100644 --- a/compiler/rustc_codegen_ssa/src/back/link.rs +++ b/compiler/rustc_codegen_ssa/src/back/link.rs @@ -1490,7 +1490,7 @@ fn print_native_static_libs( NativeLibKind::Static { bundle: None | Some(true), .. } | NativeLibKind::LinkArg | NativeLibKind::WasmImportModule - | NativeLibKind::RawDylib => None, + | NativeLibKind::RawDylib { .. } => None, } }) // deduplication of consecutive repeated libraries, see rust-lang/rust#113209 @@ -2364,13 +2364,13 @@ fn linker_with_args( cmd.add_object(&output_path); } } else { - for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects( + for (link_path, as_needed) in raw_dylib::create_raw_dylib_elf_stub_shared_objects( sess, codegen_results.crate_info.used_libraries.iter(), &raw_dylib_dir, ) { // Always use verbatim linkage, see comments in create_raw_dylib_elf_stub_shared_objects. - cmd.link_dylib_by_name(&link_path, true, false); + cmd.link_dylib_by_name(&link_path, true, as_needed); } } // As with add_upstream_native_libraries, we need to add the upstream raw-dylib symbols in case @@ -2411,13 +2411,13 @@ fn linker_with_args( cmd.add_object(&output_path); } } else { - for link_path in raw_dylib::create_raw_dylib_elf_stub_shared_objects( + for (link_path, as_needed) in raw_dylib::create_raw_dylib_elf_stub_shared_objects( sess, native_libraries_from_nonstatics, &raw_dylib_dir, ) { // Always use verbatim linkage, see comments in create_raw_dylib_elf_stub_shared_objects. - cmd.link_dylib_by_name(&link_path, true, false); + cmd.link_dylib_by_name(&link_path, true, as_needed); } } @@ -2726,7 +2726,7 @@ fn add_native_libs_from_crate( cmd.link_framework_by_name(name, verbatim, as_needed.unwrap_or(true)) } } - NativeLibKind::RawDylib => { + NativeLibKind::RawDylib { as_needed: _ } => { // Handled separately in `linker_with_args`. } NativeLibKind::WasmImportModule => {} diff --git a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs index 9f42991d4c06a..7321bc1da391a 100644 --- a/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs +++ b/compiler/rustc_codegen_ssa/src/back/link/raw_dylib.rs @@ -31,7 +31,7 @@ fn collate_raw_dylibs_windows<'a>( let mut dylib_table = FxIndexMap::>::default(); for lib in used_libraries { - if lib.kind == NativeLibKind::RawDylib { + if let NativeLibKind::RawDylib { .. } = lib.kind { let ext = if lib.verbatim { "" } else { ".dll" }; let name = format!("{}{}", lib.name, ext); let imports = dylib_table.entry(name.clone()).or_default(); @@ -128,12 +128,12 @@ pub(super) fn create_raw_dylib_dll_import_libs<'a>( fn collate_raw_dylibs_elf<'a>( sess: &Session, used_libraries: impl IntoIterator, -) -> Vec<(String, Vec)> { +) -> Vec<(String, Vec, bool)> { // Use index maps to preserve original order of imports and libraries. - let mut dylib_table = FxIndexMap::>::default(); + let mut dylib_table = FxIndexMap::, bool)>::default(); for lib in used_libraries { - if lib.kind == NativeLibKind::RawDylib { + if let NativeLibKind::RawDylib { as_needed } = lib.kind { let filename = if lib.verbatim { lib.name.as_str().to_owned() } else { @@ -142,17 +142,19 @@ fn collate_raw_dylibs_elf<'a>( format!("{prefix}{}{ext}", lib.name) }; - let imports = dylib_table.entry(filename.clone()).or_default(); + let (stub_imports, stub_as_needed) = + dylib_table.entry(filename.clone()).or_insert((Default::default(), true)); for import in &lib.dll_imports { - imports.insert(import.name, import); + stub_imports.insert(import.name, import); } + *stub_as_needed = *stub_as_needed && as_needed.unwrap_or(true); } } sess.dcx().abort_if_errors(); dylib_table .into_iter() - .map(|(name, imports)| { - (name, imports.into_iter().map(|(_, import)| import.clone()).collect()) + .map(|(name, (imports, as_needed))| { + (name, imports.into_iter().map(|(_, import)| import.clone()).collect(), as_needed) }) .collect() } @@ -161,10 +163,10 @@ pub(super) fn create_raw_dylib_elf_stub_shared_objects<'a>( sess: &Session, used_libraries: impl IntoIterator, raw_dylib_so_dir: &Path, -) -> Vec { +) -> Vec<(String, bool)> { collate_raw_dylibs_elf(sess, used_libraries) .into_iter() - .map(|(load_filename, raw_dylib_imports)| { + .map(|(load_filename, raw_dylib_imports, as_needed)| { use std::hash::Hash; // `load_filename` is the *target/loader* filename that will end up in NEEDED. @@ -205,7 +207,7 @@ pub(super) fn create_raw_dylib_elf_stub_shared_objects<'a>( }); }; - temporary_lib_name + (temporary_lib_name, as_needed) }) .collect() } diff --git a/compiler/rustc_hir/src/attrs/data_structures.rs b/compiler/rustc_hir/src/attrs/data_structures.rs index beeca7332cb17..2435363ef0eb8 100644 --- a/compiler/rustc_hir/src/attrs/data_structures.rs +++ b/compiler/rustc_hir/src/attrs/data_structures.rs @@ -309,7 +309,10 @@ pub enum NativeLibKind { }, /// Dynamic library (e.g. `foo.dll` on Windows) without a corresponding import library. /// On Linux, it refers to a generated shared library stub. - RawDylib, + RawDylib { + /// Whether the dynamic library will be linked only if it satisfies some undefined symbols + as_needed: Option, + }, /// A macOS-specific kind of dynamic libraries. Framework { /// Whether the framework will be linked only if it satisfies some undefined symbols @@ -332,11 +335,10 @@ impl NativeLibKind { NativeLibKind::Static { bundle, whole_archive } => { bundle.is_some() || whole_archive.is_some() } - NativeLibKind::Dylib { as_needed } | NativeLibKind::Framework { as_needed } => { - as_needed.is_some() - } - NativeLibKind::RawDylib - | NativeLibKind::Unspecified + NativeLibKind::Dylib { as_needed } + | NativeLibKind::Framework { as_needed } + | NativeLibKind::RawDylib { as_needed } => as_needed.is_some(), + NativeLibKind::Unspecified | NativeLibKind::LinkArg | NativeLibKind::WasmImportModule => false, } @@ -349,7 +351,9 @@ impl NativeLibKind { pub fn is_dllimport(&self) -> bool { matches!( self, - NativeLibKind::Dylib { .. } | NativeLibKind::RawDylib | NativeLibKind::Unspecified + NativeLibKind::Dylib { .. } + | NativeLibKind::RawDylib { .. } + | NativeLibKind::Unspecified ) } } diff --git a/compiler/rustc_metadata/src/native_libs.rs b/compiler/rustc_metadata/src/native_libs.rs index 82738c68c5921..15572063d45a6 100644 --- a/compiler/rustc_metadata/src/native_libs.rs +++ b/compiler/rustc_metadata/src/native_libs.rs @@ -218,7 +218,7 @@ impl<'tcx> Collector<'tcx> { .flatten() { let dll_imports = match attr.kind { - NativeLibKind::RawDylib => foreign_items + NativeLibKind::RawDylib { .. } => foreign_items .iter() .map(|&child_item| { self.build_dll_import( diff --git a/compiler/rustc_session/src/config/native_libs.rs b/compiler/rustc_session/src/config/native_libs.rs index 50a0593f88712..71d3e222c8a15 100644 --- a/compiler/rustc_session/src/config/native_libs.rs +++ b/compiler/rustc_session/src/config/native_libs.rs @@ -135,7 +135,8 @@ fn parse_and_apply_modifier(cx: &ParseNativeLibCx<'_>, modifier: &str, native_li ), ("as-needed", NativeLibKind::Dylib { as_needed }) - | ("as-needed", NativeLibKind::Framework { as_needed }) => { + | ("as-needed", NativeLibKind::Framework { as_needed }) + | ("as-needed", NativeLibKind::RawDylib { as_needed }) => { cx.on_unstable_value( "linking modifier `as-needed` is unstable", ", the `-Z unstable-options` flag must also be passed to use it", diff --git a/tests/ui/linkage-attr/raw-dylib/elf/as_needed.rs b/tests/ui/linkage-attr/raw-dylib/elf/as_needed.rs new file mode 100644 index 0000000000000..48ca39300f41c --- /dev/null +++ b/tests/ui/linkage-attr/raw-dylib/elf/as_needed.rs @@ -0,0 +1,43 @@ +//@ only-elf +//@ needs-dynamic-linking + +//@ only-gnu +//@ only-x86_64 +//@ revisions: as_needed no_as_needed no_modifier merge_1 merge_2 merge_3 merge_4 + +//@ [as_needed] run-pass +//@ [no_as_needed] run-fail +//@ [no_modifier] run-pass +//@ [merge_1] run-fail +//@ [merge_2] run-fail +//@ [merge_3] run-fail +//@ [merge_4] run-pass + +#![allow(incomplete_features)] +#![feature(raw_dylib_elf)] +#![feature(native_link_modifiers_as_needed)] + +#[cfg_attr( + as_needed, + link(name = "taiqannf1y28z2rw", kind = "raw-dylib", modifiers = "+as-needed") +)] +#[cfg_attr( + no_as_needed, + link(name = "taiqannf1y28z2rw", kind = "raw-dylib", modifiers = "-as-needed") +)] +#[cfg_attr(no_modifier, link(name = "taiqannf1y28z2rw", kind = "raw-dylib"))] +unsafe extern "C" {} + +#[cfg_attr(merge_1, link(name = "k9nm7qxoa79bg7e6", kind = "raw-dylib", modifiers = "+as-needed"))] +#[cfg_attr(merge_2, link(name = "k9nm7qxoa79bg7e6", kind = "raw-dylib", modifiers = "-as-needed"))] +#[cfg_attr(merge_3, link(name = "k9nm7qxoa79bg7e6", kind = "raw-dylib", modifiers = "-as-needed"))] +#[cfg_attr(merge_4, link(name = "k9nm7qxoa79bg7e6", kind = "raw-dylib", modifiers = "+as-needed"))] +unsafe extern "C" {} + +#[cfg_attr(merge_1, link(name = "k9nm7qxoa79bg7e6", kind = "raw-dylib", modifiers = "-as-needed"))] +#[cfg_attr(merge_2, link(name = "k9nm7qxoa79bg7e6", kind = "raw-dylib", modifiers = "+as-needed"))] +#[cfg_attr(merge_3, link(name = "k9nm7qxoa79bg7e6", kind = "raw-dylib", modifiers = "-as-needed"))] +#[cfg_attr(merge_4, link(name = "k9nm7qxoa79bg7e6", kind = "raw-dylib", modifiers = "+as-needed"))] +unsafe extern "C" {} + +fn main() {} From eeb7cb1b8a42630458e6c6cdabf0c4264579aec7 Mon Sep 17 00:00:00 2001 From: Martin Nordholts Date: Mon, 22 Sep 2025 07:39:48 +0200 Subject: [PATCH 4/4] compiler: Hint at multiple crate versions if trait impl is for wrong ADT If a user does e.g. impl From for foo::Foo and get a compilation error about that `From` is not implemented for `Foo`, check if multiple versions of the crate with `Foo` is present in the dependency graph. If so, give a hint about it. I encountered this case in the wild and didn't realize I had multiple versions of a crate in my dependency graph. So I was a bit confused at first. This fix will make life easier for others. --- .../traits/fulfillment_errors.rs | 85 +++++++++++++++++-- .../run-make/duplicate-dependency/main.stderr | 6 ++ tests/run-make/duplicate-dependency/rmake.rs | 4 +- 3 files changed, 88 insertions(+), 7 deletions(-) diff --git a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs index 149f5e638b1ad..7728f657bedad 100644 --- a/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs +++ b/compiler/rustc_trait_selection/src/error_reporting/traits/fulfillment_errors.rs @@ -467,7 +467,8 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { span, leaf_trait_predicate, ); - self.note_version_mismatch(&mut err, leaf_trait_predicate); + self.note_trait_version_mismatch(&mut err, leaf_trait_predicate); + self.note_adt_version_mismatch(&mut err, leaf_trait_predicate); self.suggest_remove_await(&obligation, &mut err); self.suggest_derive(&obligation, &mut err, leaf_trait_predicate); @@ -2406,7 +2407,7 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { /// If the `Self` type of the unsatisfied trait `trait_ref` implements a trait /// with the same path as `trait_ref`, a help message about /// a probable version mismatch is added to `err` - fn note_version_mismatch( + fn note_trait_version_mismatch( &self, err: &mut Diag<'_>, trait_pred: ty::PolyTraitPredicate<'tcx>, @@ -2446,15 +2447,87 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { impl_spans, format!("trait impl{} with same name found", pluralize!(trait_impls.len())), ); - let trait_crate = self.tcx.crate_name(trait_with_same_path.krate); - let crate_msg = - format!("perhaps two different versions of crate `{trait_crate}` are being used?"); - err.note(crate_msg); + self.note_two_crate_versions(trait_with_same_path, err); suggested = true; } suggested } + fn note_two_crate_versions(&self, did: DefId, err: &mut Diag<'_>) { + let crate_name = self.tcx.crate_name(did.krate); + let crate_msg = + format!("perhaps two different versions of crate `{crate_name}` are being used?"); + err.note(crate_msg); + } + + fn note_adt_version_mismatch( + &self, + err: &mut Diag<'_>, + trait_pred: ty::PolyTraitPredicate<'tcx>, + ) { + let ty::Adt(impl_self_def, _) = trait_pred.self_ty().skip_binder().peel_refs().kind() + else { + return; + }; + + let impl_self_did = impl_self_def.did(); + + // We only want to warn about different versions of a dependency. + // If no dependency is involved, bail. + if impl_self_did.krate == LOCAL_CRATE { + return; + } + + let impl_self_path = self.comparable_path(impl_self_did); + let impl_self_crate_name = self.tcx.crate_name(impl_self_did.krate); + let similar_items: UnordSet<_> = self + .tcx + .visible_parent_map(()) + .items() + .filter_map(|(&item, _)| { + // If we found ourselves, ignore. + if impl_self_did == item { + return None; + } + // We only want to warn about different versions of a dependency. + // Ignore items from our own crate. + if item.krate == LOCAL_CRATE { + return None; + } + // We want to warn about different versions of a dependency. + // So make sure the crate names are the same. + if impl_self_crate_name != self.tcx.crate_name(item.krate) { + return None; + } + // Filter out e.g. constructors that often have the same path + // str as the relevant ADT. + if !self.tcx.def_kind(item).is_adt() { + return None; + } + let path = self.comparable_path(item); + // We don't know if our item or the one we found is the re-exported one. + // Check both cases. + let is_similar = path.ends_with(&impl_self_path) || impl_self_path.ends_with(&path); + is_similar.then_some((item, path)) + }) + .collect(); + + let mut similar_items = + similar_items.into_items().into_sorted_stable_ord_by_key(|(_, path)| path); + similar_items.dedup(); + + for (similar_item, _) in similar_items { + err.span_help(self.tcx.def_span(similar_item), "item with same name found"); + self.note_two_crate_versions(similar_item, err); + } + } + + /// Add a `::` prefix when comparing paths so that paths with just one item + /// like "Foo" does not equal the end of "OtherFoo". + fn comparable_path(&self, did: DefId) -> String { + format!("::{}", self.tcx.def_path_str(did)) + } + /// Creates a `PredicateObligation` with `new_self_ty` replacing the existing type in the /// `trait_ref`. /// diff --git a/tests/run-make/duplicate-dependency/main.stderr b/tests/run-make/duplicate-dependency/main.stderr index 2c912f872fe35..36d54988788f4 100644 --- a/tests/run-make/duplicate-dependency/main.stderr +++ b/tests/run-make/duplicate-dependency/main.stderr @@ -6,6 +6,12 @@ LL | re_export_foo::into_foo(Bar); | | | required by a bound introduced by this call | +help: item with same name found + --> $DIR/foo-v1.rs:1:1 + | +LL | pub struct Foo; + | ^^^^^^^^^^^^^^ + = note: perhaps two different versions of crate `foo` are being used? = note: required for `Bar` to implement `Into` note: required by a bound in `into_foo` --> $DIR/re-export-foo.rs:3:25 diff --git a/tests/run-make/duplicate-dependency/rmake.rs b/tests/run-make/duplicate-dependency/rmake.rs index 13ab4caaba56f..762d97e4311f4 100644 --- a/tests/run-make/duplicate-dependency/rmake.rs +++ b/tests/run-make/duplicate-dependency/rmake.rs @@ -1,3 +1,5 @@ +//@ needs-target-std + use run_make_support::{Rustc, cwd, diff, rust_lib_name, rustc}; fn rustc_with_common_args() -> Rustc { @@ -39,5 +41,5 @@ fn main() { .run_fail() .stderr_utf8(); - diff().expected_file("main.stderr").actual_text("(rustc)", &stderr).run(); + diff().expected_file("main.stderr").normalize(r"\\", "/").actual_text("(rustc)", &stderr).run(); }