From b2e7ae1f65fd5e1579692a9a7a8c95fb95aa1b04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 7 Aug 2024 15:08:43 +0000 Subject: [PATCH 01/16] Detect multiple crate versions on method not found When a type comes indirectly from one crate version but the imported trait comes from a separate crate version, the called method won't be found. We now show additional context: ``` error[E0599]: no method named `foo` found for struct `dep_2_reexport::Type` in the current scope --> multiple-dep-versions.rs:8:10 | 8 | Type.foo(); | ^^^ method not found in `Type` | note: you have multiple different versions of crate `dependency` in your dependency graph --> multiple-dep-versions.rs:4:32 | 4 | use dependency::{do_something, Trait}; | ^^^^^ `dependency` imported here doesn't correspond to the right crate version | ::: ~/rust/build/x86_64-unknown-linux-gnu/test/run-make/crate-loading/rmake_out/multiple-dep-versions-1.rs:4:1 | 4 | pub trait Trait { | --------------- this is the trait that was imported | ::: ~/rust/build/x86_64-unknown-linux-gnu/test/run-make/crate-loading/rmake_out/multiple-dep-versions-2.rs:4:1 | 4 | pub trait Trait { | --------------- this is the trait that is needed 5 | fn foo(&self); | --- the method is available for `dep_2_reexport::Type` here ``` --- .../rustc_hir_typeck/src/method/suggest.rs | 69 +++++++++++++++++-- .../crate-loading/multiple-dep-versions-1.rs | 10 ++- .../crate-loading/multiple-dep-versions-2.rs | 10 ++- .../crate-loading/multiple-dep-versions-3.rs | 5 ++ .../crate-loading/multiple-dep-versions.rs | 5 +- tests/run-make/crate-loading/rmake.rs | 14 +++- 6 files changed, 99 insertions(+), 14 deletions(-) create mode 100644 tests/run-make/crate-loading/multiple-dep-versions-3.rs diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 61287d98676b2..d4fcbcad7ae07 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -3448,6 +3448,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { trait_missing_method: bool, ) { let mut alt_rcvr_sugg = false; + let mut suggest = true; if let (SelfSource::MethodCall(rcvr), false) = (source, unsatisfied_bounds) { debug!( "suggest_traits_to_import: span={:?}, item_name={:?}, rcvr_ty={:?}, rcvr={:?}", @@ -3491,10 +3492,18 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let did = Some(pick.item.container_id(self.tcx)); let skip = skippable.contains(&did); if pick.autoderefs == 0 && !skip { - err.span_label( - pick.item.ident(self.tcx).span, - format!("the method is available for `{rcvr_ty}` here"), + suggest = self.detect_and_explain_multiple_crate_versions( + err, + &pick.item, + rcvr.hir_id.owner, + *rcvr_ty, ); + if suggest { + err.span_label( + pick.item.ident(self.tcx).span, + format!("the method is available for `{rcvr_ty}` here"), + ); + } } break; } @@ -3675,7 +3684,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } } - if self.suggest_valid_traits(err, item_name, valid_out_of_scope_traits, true) { + if suggest && self.suggest_valid_traits(err, item_name, valid_out_of_scope_traits, true) { return; } @@ -4040,6 +4049,58 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } + fn detect_and_explain_multiple_crate_versions( + &self, + err: &mut Diag<'_>, + item: &ty::AssocItem, + owner: hir::OwnerId, + rcvr_ty: Ty<'_>, + ) -> bool { + let pick_name = self.tcx.crate_name(item.def_id.krate); + if let Some(map) = self.tcx.in_scope_traits_map(owner) { + for trait_candidate in map.to_sorted_stable_ord().into_iter().flat_map(|v| v.1.iter()) { + let name = self.tcx.crate_name(trait_candidate.def_id.krate); + if trait_candidate.def_id.krate != item.def_id.krate && name == pick_name { + let msg = format!( + "you have multiple different versions of crate `{name}` in your \ + dependency graph", + ); + let tdid = self.tcx.parent(item.def_id); + if self.tcx.item_name(trait_candidate.def_id) == self.tcx.item_name(tdid) + && let Some(def_id) = trait_candidate.import_ids.get(0) + { + let span = self.tcx.def_span(*def_id); + let mut multi_span: MultiSpan = span.into(); + multi_span.push_span_label( + span, + format!( + "`{name}` imported here doesn't correspond to the right crate \ + version", + ), + ); + multi_span.push_span_label( + self.tcx.def_span(trait_candidate.def_id), + format!("this is the trait that was imported"), + ); + multi_span.push_span_label( + self.tcx.def_span(tdid), + format!("this is the trait that is needed"), + ); + multi_span.push_span_label( + item.ident(self.tcx).span, + format!("the method is available for `{rcvr_ty}` here"), + ); + err.span_note(multi_span, msg); + return false; + } else { + err.note(msg); + } + } + } + } + true + } + /// issue #102320, for `unwrap_or` with closure as argument, suggest `unwrap_or_else` /// FIXME: currently not working for suggesting `map_or_else`, see #102408 pub(crate) fn suggest_else_fn_with_closure( diff --git a/tests/run-make/crate-loading/multiple-dep-versions-1.rs b/tests/run-make/crate-loading/multiple-dep-versions-1.rs index 2d35163382916..94f30ca326fdb 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions-1.rs +++ b/tests/run-make/crate-loading/multiple-dep-versions-1.rs @@ -1,6 +1,10 @@ #![crate_name = "dependency"] #![crate_type = "rlib"] -pub struct Type; -pub trait Trait {} -impl Trait for Type {} +pub struct Type(pub i32); +pub trait Trait { + fn foo(&self); +} +impl Trait for Type { + fn foo(&self) {} +} pub fn do_something(_: X) {} diff --git a/tests/run-make/crate-loading/multiple-dep-versions-2.rs b/tests/run-make/crate-loading/multiple-dep-versions-2.rs index a5df3dc61eda5..0a4626be5605b 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions-2.rs +++ b/tests/run-make/crate-loading/multiple-dep-versions-2.rs @@ -1,6 +1,10 @@ #![crate_name = "dependency"] #![crate_type = "rlib"] -pub struct Type(pub i32); -pub trait Trait {} -impl Trait for Type {} +pub struct Type; +pub trait Trait { + fn foo(&self); +} +impl Trait for Type { + fn foo(&self) {} +} pub fn do_something(_: X) {} diff --git a/tests/run-make/crate-loading/multiple-dep-versions-3.rs b/tests/run-make/crate-loading/multiple-dep-versions-3.rs new file mode 100644 index 0000000000000..07d888e9f1034 --- /dev/null +++ b/tests/run-make/crate-loading/multiple-dep-versions-3.rs @@ -0,0 +1,5 @@ +#![crate_name = "foo"] +#![crate_type = "rlib"] + +extern crate dependency; +pub use dependency::Type; diff --git a/tests/run-make/crate-loading/multiple-dep-versions.rs b/tests/run-make/crate-loading/multiple-dep-versions.rs index 5a6cb03aaa4a7..113a6b76d7d82 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions.rs +++ b/tests/run-make/crate-loading/multiple-dep-versions.rs @@ -1,8 +1,9 @@ extern crate dep_2_reexport; extern crate dependency; -use dep_2_reexport::do_something; -use dependency::Type; +use dep_2_reexport::Type; +use dependency::{do_something, Trait}; fn main() { do_something(Type); + Type.foo(); } diff --git a/tests/run-make/crate-loading/rmake.rs b/tests/run-make/crate-loading/rmake.rs index d7abd5872c94a..5da706624aef7 100644 --- a/tests/run-make/crate-loading/rmake.rs +++ b/tests/run-make/crate-loading/rmake.rs @@ -7,11 +7,15 @@ use run_make_support::{rust_lib_name, rustc}; fn main() { rustc().input("multiple-dep-versions-1.rs").run(); rustc().input("multiple-dep-versions-2.rs").extra_filename("2").metadata("2").run(); + rustc() + .input("multiple-dep-versions-3.rs") + .extern_("dependency", rust_lib_name("dependency2")) + .run(); rustc() .input("multiple-dep-versions.rs") .extern_("dependency", rust_lib_name("dependency")) - .extern_("dep_2_reexport", rust_lib_name("dependency2")) + .extern_("dep_2_reexport", rust_lib_name("foo")) .run_fail() .assert_stderr_contains( "you have multiple different versions of crate `dependency` in your dependency graph", @@ -22,5 +26,11 @@ fn main() { ) .assert_stderr_contains("this type doesn't implement the required trait") .assert_stderr_contains("this type implements the required trait") - .assert_stderr_contains("this is the required trait"); + .assert_stderr_contains("this is the required trait") + .assert_stderr_contains( + "`dependency` imported here doesn't correspond to the right crate version", + ) + .assert_stderr_contains("this is the trait that was imported") + .assert_stderr_contains("this is the trait that is needed") + .assert_stderr_contains("the method is available for `dep_2_reexport::Type` here"); } From 5c427b4600708dce24066ade292f8d8b592b1cc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 7 Aug 2024 16:18:06 +0000 Subject: [PATCH 02/16] reword message --- compiler/rustc_hir_typeck/src/method/suggest.rs | 2 +- .../src/error_reporting/traits/fulfillment_errors.rs | 4 ++-- tests/run-make/crate-loading/rmake.rs | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index d4fcbcad7ae07..6afbde867398a 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -4062,7 +4062,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let name = self.tcx.crate_name(trait_candidate.def_id.krate); if trait_candidate.def_id.krate != item.def_id.krate && name == pick_name { let msg = format!( - "you have multiple different versions of crate `{name}` in your \ + "there are multiple different versions of crate `{name}` in the \ dependency graph", ); let tdid = self.tcx.parent(item.def_id); 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 95d4509c100a2..5b879cf67ce0d 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 @@ -1697,11 +1697,11 @@ impl<'a, 'tcx> TypeErrCtxt<'a, 'tcx> { err.highlighted_span_help( span, vec![ - StringPart::normal("you have ".to_string()), + StringPart::normal("there are ".to_string()), StringPart::highlighted("multiple different versions".to_string()), StringPart::normal(" of crate `".to_string()), StringPart::highlighted(format!("{name}")), - StringPart::normal("` in your dependency graph".to_string()), + StringPart::normal("` the your dependency graph".to_string()), ], ); let candidates = if impl_candidates.is_empty() { diff --git a/tests/run-make/crate-loading/rmake.rs b/tests/run-make/crate-loading/rmake.rs index 5da706624aef7..1b9c13c1f6a10 100644 --- a/tests/run-make/crate-loading/rmake.rs +++ b/tests/run-make/crate-loading/rmake.rs @@ -18,7 +18,7 @@ fn main() { .extern_("dep_2_reexport", rust_lib_name("foo")) .run_fail() .assert_stderr_contains( - "you have multiple different versions of crate `dependency` in your dependency graph", + "there are multiple different versions of crate `dependency` in the dependency graph", ) .assert_stderr_contains( "two types coming from two different versions of the same crate are different types \ From eeb72835d291a9e5fc08731ed0e26cc707ced720 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 7 Aug 2024 21:09:17 +0000 Subject: [PATCH 03/16] Account for fully-qualified path case of conflicting crate versions When encountering the following, mention the precense of conflicting crates: ``` error[E0599]: no function or associated item named `get_decoded` found for struct `HpkeConfig` in the current scope --> src/main.rs:7:17 | 7 | HpkeConfig::get_decoded(&foo); | ^^^^^^^^^^^ function or associated item not found in `HpkeConfig` | note: if you're trying to build a new `HpkeConfig`, consider using `HpkeConfig::new` which returns `HpkeConfig` --> ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/janus_messages-0.3.1/src/lib.rs:908:5 | 908 | / pub fn new( 909 | | id: HpkeConfigId, 910 | | kem_id: HpkeKemId, 911 | | kdf_id: HpkeKdfId, 912 | | aead_id: HpkeAeadId, 913 | | public_key: HpkePublicKey, 914 | | ) -> HpkeConfig { | |___________________^ note: there are multiple different versions of crate `prio` in the dependency graph --> src/main.rs:1:5 | 1 | use prio::codec::Decode; | ^^^^^^^^^^^^^^^^^^^ `prio` imported here doesn't correspond to the right crate version | ::: ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/prio-0.9.1/src/codec.rs:35:1 | 35 | pub trait Decode: Sized { | ----------------------- this is the trait that was imported | ::: ~/.cargo/registry/src/index.crates.io-6f17d22bba15001f/prio-0.10.3/src/codec.rs:35:1 | 35 | pub trait Decode: Sized { | ----------------------- this is the trait that is needed ... 43 | fn get_decoded(bytes: &[u8]) -> Result { | -------------------------------------------------------- the method is available for `HpkeConfig` here help: there is an associated function `decode` with a similar name | 7 | HpkeConfig::decode(&foo); | ~~~~~~ ``` --- .../rustc_hir_typeck/src/method/suggest.rs | 52 ++++++++++++++----- 1 file changed, 40 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 6afbde867398a..4ab7e2ee17772 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -3494,7 +3494,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if pick.autoderefs == 0 && !skip { suggest = self.detect_and_explain_multiple_crate_versions( err, - &pick.item, + pick.item.def_id, + pick.item.ident(self.tcx).span, rcvr.hir_id.owner, *rcvr_ty, ); @@ -3684,6 +3685,30 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } } + + if let SelfSource::QPath(ty) = source + && !valid_out_of_scope_traits.is_empty() + && let hir::TyKind::Path(path) = ty.kind + && let hir::QPath::Resolved(_, path) = path + && let Some(def_id) = path.res.opt_def_id() + && let Some(assoc) = self + .tcx + .associated_items(valid_out_of_scope_traits[0]) + .filter_by_name_unhygienic(item_name.name) + .next() + { + // See if the `Type::function(val)` where `function` wasn't found corresponds to a + // `Trait` that is imported directly, but `Type` came from a different version of the + // same crate. + let rcvr_ty = self.tcx.type_of(def_id).instantiate_identity(); + suggest = self.detect_and_explain_multiple_crate_versions( + err, + assoc.def_id, + self.tcx.def_span(assoc.def_id), + ty.hir_id.owner, + rcvr_ty, + ); + } if suggest && self.suggest_valid_traits(err, item_name, valid_out_of_scope_traits, true) { return; } @@ -4052,21 +4077,24 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { fn detect_and_explain_multiple_crate_versions( &self, err: &mut Diag<'_>, - item: &ty::AssocItem, + item_def_id: DefId, + item_span: Span, owner: hir::OwnerId, rcvr_ty: Ty<'_>, ) -> bool { - let pick_name = self.tcx.crate_name(item.def_id.krate); + let pick_name = self.tcx.crate_name(item_def_id.krate); + let trait_did = self.tcx.parent(item_def_id); + let trait_name = self.tcx.item_name(trait_did); if let Some(map) = self.tcx.in_scope_traits_map(owner) { for trait_candidate in map.to_sorted_stable_ord().into_iter().flat_map(|v| v.1.iter()) { - let name = self.tcx.crate_name(trait_candidate.def_id.krate); - if trait_candidate.def_id.krate != item.def_id.krate && name == pick_name { + let crate_name = self.tcx.crate_name(trait_candidate.def_id.krate); + if trait_candidate.def_id.krate != item_def_id.krate && crate_name == pick_name { let msg = format!( - "there are multiple different versions of crate `{name}` in the \ + "there are multiple different versions of crate `{crate_name}` in the \ dependency graph", ); - let tdid = self.tcx.parent(item.def_id); - if self.tcx.item_name(trait_candidate.def_id) == self.tcx.item_name(tdid) + let candidate_name = self.tcx.item_name(trait_candidate.def_id); + if candidate_name == trait_name && let Some(def_id) = trait_candidate.import_ids.get(0) { let span = self.tcx.def_span(*def_id); @@ -4074,8 +4102,8 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { multi_span.push_span_label( span, format!( - "`{name}` imported here doesn't correspond to the right crate \ - version", + "`{crate_name}` imported here doesn't correspond to the right \ + crate version", ), ); multi_span.push_span_label( @@ -4083,11 +4111,11 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { format!("this is the trait that was imported"), ); multi_span.push_span_label( - self.tcx.def_span(tdid), + self.tcx.def_span(trait_did), format!("this is the trait that is needed"), ); multi_span.push_span_label( - item.ident(self.tcx).span, + item_span, format!("the method is available for `{rcvr_ty}` here"), ); err.span_note(multi_span, msg); From 4e985534e850822c6e1069b6bb2459b31ea5a886 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Wed, 7 Aug 2024 21:24:49 +0000 Subject: [PATCH 04/16] Ignore auto-deref for multiple crate version note As per the case presented in #128569, we should be showing the extra info even if auto-deref is involved. --- .../rustc_hir_typeck/src/method/suggest.rs | 31 ++++++++++--------- 1 file changed, 17 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 4ab7e2ee17772..cf9f496ed56ff 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -3448,7 +3448,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { trait_missing_method: bool, ) { let mut alt_rcvr_sugg = false; - let mut suggest = true; + let mut trait_in_other_version_found = false; if let (SelfSource::MethodCall(rcvr), false) = (source, unsatisfied_bounds) { debug!( "suggest_traits_to_import: span={:?}, item_name={:?}, rcvr_ty={:?}, rcvr={:?}", @@ -3490,21 +3490,22 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // self types and rely on the suggestion to `use` the trait from // `suggest_valid_traits`. let did = Some(pick.item.container_id(self.tcx)); - let skip = skippable.contains(&did); - if pick.autoderefs == 0 && !skip { - suggest = self.detect_and_explain_multiple_crate_versions( + if skippable.contains(&did) { + continue; + } + trait_in_other_version_found = self + .detect_and_explain_multiple_crate_versions( err, pick.item.def_id, pick.item.ident(self.tcx).span, rcvr.hir_id.owner, *rcvr_ty, ); - if suggest { - err.span_label( - pick.item.ident(self.tcx).span, - format!("the method is available for `{rcvr_ty}` here"), - ); - } + if pick.autoderefs == 0 && !trait_in_other_version_found { + err.span_label( + pick.item.ident(self.tcx).span, + format!("the method is available for `{rcvr_ty}` here"), + ); } break; } @@ -3701,7 +3702,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { // `Trait` that is imported directly, but `Type` came from a different version of the // same crate. let rcvr_ty = self.tcx.type_of(def_id).instantiate_identity(); - suggest = self.detect_and_explain_multiple_crate_versions( + trait_in_other_version_found = self.detect_and_explain_multiple_crate_versions( err, assoc.def_id, self.tcx.def_span(assoc.def_id), @@ -3709,7 +3710,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { rcvr_ty, ); } - if suggest && self.suggest_valid_traits(err, item_name, valid_out_of_scope_traits, true) { + if !trait_in_other_version_found + && self.suggest_valid_traits(err, item_name, valid_out_of_scope_traits, true) + { return; } @@ -4119,14 +4122,14 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { format!("the method is available for `{rcvr_ty}` here"), ); err.span_note(multi_span, msg); - return false; } else { err.note(msg); } + return true; } } } - true + false } /// issue #102320, for `unwrap_or` with closure as argument, suggest `unwrap_or_else` From 61058937e5a9d58ce1f9c5e0ac6beef22c54cadf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Sat, 10 Aug 2024 00:32:10 +0000 Subject: [PATCH 05/16] Rework suggestion method Make checking slightly cheaper (by restricting to the right item only). Add tests. --- .../rustc_hir_typeck/src/method/suggest.rs | 96 +++++++++---------- .../crate-loading/multiple-dep-versions-1.rs | 2 + .../crate-loading/multiple-dep-versions-2.rs | 2 + .../crate-loading/multiple-dep-versions.rs | 1 + tests/run-make/crate-loading/rmake.rs | 84 ++++++++++++++-- 5 files changed, 126 insertions(+), 59 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index cf9f496ed56ff..31366a3076050 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -3497,8 +3497,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { .detect_and_explain_multiple_crate_versions( err, pick.item.def_id, - pick.item.ident(self.tcx).span, - rcvr.hir_id.owner, + rcvr.hir_id, *rcvr_ty, ); if pick.autoderefs == 0 && !trait_in_other_version_found { @@ -3705,8 +3704,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { trait_in_other_version_found = self.detect_and_explain_multiple_crate_versions( err, assoc.def_id, - self.tcx.def_span(assoc.def_id), - ty.hir_id.owner, + ty.hir_id, rcvr_ty, ); } @@ -4081,55 +4079,55 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { &self, err: &mut Diag<'_>, item_def_id: DefId, - item_span: Span, - owner: hir::OwnerId, + hir_id: hir::HirId, rcvr_ty: Ty<'_>, ) -> bool { - let pick_name = self.tcx.crate_name(item_def_id.krate); - let trait_did = self.tcx.parent(item_def_id); - let trait_name = self.tcx.item_name(trait_did); - if let Some(map) = self.tcx.in_scope_traits_map(owner) { - for trait_candidate in map.to_sorted_stable_ord().into_iter().flat_map(|v| v.1.iter()) { - let crate_name = self.tcx.crate_name(trait_candidate.def_id.krate); - if trait_candidate.def_id.krate != item_def_id.krate && crate_name == pick_name { - let msg = format!( - "there are multiple different versions of crate `{crate_name}` in the \ - dependency graph", - ); - let candidate_name = self.tcx.item_name(trait_candidate.def_id); - if candidate_name == trait_name - && let Some(def_id) = trait_candidate.import_ids.get(0) - { - let span = self.tcx.def_span(*def_id); - let mut multi_span: MultiSpan = span.into(); - multi_span.push_span_label( - span, - format!( - "`{crate_name}` imported here doesn't correspond to the right \ - crate version", - ), - ); - multi_span.push_span_label( - self.tcx.def_span(trait_candidate.def_id), - format!("this is the trait that was imported"), - ); - multi_span.push_span_label( - self.tcx.def_span(trait_did), - format!("this is the trait that is needed"), - ); - multi_span.push_span_label( - item_span, - format!("the method is available for `{rcvr_ty}` here"), - ); - err.span_note(multi_span, msg); - } else { - err.note(msg); - } - return true; - } + let hir_id = self.tcx.parent_hir_id(hir_id); + let Some(traits) = self.tcx.in_scope_traits(hir_id) else { return false }; + if traits.is_empty() { + return false; + } + let trait_def_id = self.tcx.parent(item_def_id); + let krate = self.tcx.crate_name(trait_def_id.krate); + let name = self.tcx.item_name(trait_def_id); + let candidates: Vec<_> = traits + .iter() + .filter(|c| { + c.def_id.krate != trait_def_id.krate + && self.tcx.crate_name(c.def_id.krate) == krate + && self.tcx.item_name(c.def_id) == name + }) + .map(|c| (c.def_id, c.import_ids.get(0).cloned())) + .collect(); + if candidates.is_empty() { + return false; + } + let item_span = self.tcx.def_span(item_def_id); + let msg = format!( + "there are multiple different versions of crate `{krate}` in the dependency graph", + ); + let trait_span = self.tcx.def_span(trait_def_id); + let mut multi_span: MultiSpan = trait_span.into(); + multi_span.push_span_label(trait_span, format!("this is the trait that is needed")); + multi_span + .push_span_label(item_span, format!("the method is available for `{rcvr_ty}` here")); + for (def_id, import_def_id) in candidates { + if let Some(import_def_id) = import_def_id { + multi_span.push_span_label( + self.tcx.def_span(import_def_id), + format!( + "`{name}` imported here doesn't correspond to the right version of crate \ + `{krate}`", + ), + ); } + multi_span.push_span_label( + self.tcx.def_span(def_id), + format!("this is the trait that was imported"), + ); } - false + err.span_note(multi_span, msg); + true } /// issue #102320, for `unwrap_or` with closure as argument, suggest `unwrap_or_else` diff --git a/tests/run-make/crate-loading/multiple-dep-versions-1.rs b/tests/run-make/crate-loading/multiple-dep-versions-1.rs index 94f30ca326fdb..d81462504dd57 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions-1.rs +++ b/tests/run-make/crate-loading/multiple-dep-versions-1.rs @@ -3,8 +3,10 @@ pub struct Type(pub i32); pub trait Trait { fn foo(&self); + fn bar(); } impl Trait for Type { fn foo(&self) {} + fn bar() {} } pub fn do_something(_: X) {} diff --git a/tests/run-make/crate-loading/multiple-dep-versions-2.rs b/tests/run-make/crate-loading/multiple-dep-versions-2.rs index 0a4626be5605b..0a566fe2c6051 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions-2.rs +++ b/tests/run-make/crate-loading/multiple-dep-versions-2.rs @@ -3,8 +3,10 @@ pub struct Type; pub trait Trait { fn foo(&self); + fn bar(); } impl Trait for Type { fn foo(&self) {} + fn bar() {} } pub fn do_something(_: X) {} diff --git a/tests/run-make/crate-loading/multiple-dep-versions.rs b/tests/run-make/crate-loading/multiple-dep-versions.rs index 113a6b76d7d82..8ef042bf41842 100644 --- a/tests/run-make/crate-loading/multiple-dep-versions.rs +++ b/tests/run-make/crate-loading/multiple-dep-versions.rs @@ -6,4 +6,5 @@ use dependency::{do_something, Trait}; fn main() { do_something(Type); Type.foo(); + Type::bar(); } diff --git a/tests/run-make/crate-loading/rmake.rs b/tests/run-make/crate-loading/rmake.rs index 1b9c13c1f6a10..65cbc8b0b1b96 100644 --- a/tests/run-make/crate-loading/rmake.rs +++ b/tests/run-make/crate-loading/rmake.rs @@ -1,6 +1,7 @@ //@ only-linux //@ ignore-wasm32 //@ ignore-wasm64 +// ignore-tidy-linelength use run_make_support::{rust_lib_name, rustc}; @@ -18,19 +19,82 @@ fn main() { .extern_("dep_2_reexport", rust_lib_name("foo")) .run_fail() .assert_stderr_contains( - "there are multiple different versions of crate `dependency` in the dependency graph", + r#"error[E0277]: the trait bound `dep_2_reexport::Type: Trait` is not satisfied + --> multiple-dep-versions.rs:7:18 + | +7 | do_something(Type); + | ------------ ^^^^ the trait `Trait` is not implemented for `dep_2_reexport::Type` + | | + | required by a bound introduced by this call + | +help: there are multiple different versions of crate `dependency` the your dependency graph + --> multiple-dep-versions.rs:1:1 + | +1 | extern crate dep_2_reexport; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ one version of crate `dependency` is used here, as a dependency of crate `foo` +2 | extern crate dependency; + | ^^^^^^^^^^^^^^^^^^^^^^^^ one version of crate `dependency` is used here, as a direct dependency of the current crate"#, ) .assert_stderr_contains( - "two types coming from two different versions of the same crate are different types \ - even if they look the same", + r#" +3 | pub struct Type(pub i32); + | ^^^^^^^^^^^^^^^ this type implements the required trait +4 | pub trait Trait { + | --------------- this is the required trait"#, ) - .assert_stderr_contains("this type doesn't implement the required trait") - .assert_stderr_contains("this type implements the required trait") - .assert_stderr_contains("this is the required trait") .assert_stderr_contains( - "`dependency` imported here doesn't correspond to the right crate version", + r#" +3 | pub struct Type; + | ^^^^^^^^^^^^^^^ this type doesn't implement the required trait"#, ) - .assert_stderr_contains("this is the trait that was imported") - .assert_stderr_contains("this is the trait that is needed") - .assert_stderr_contains("the method is available for `dep_2_reexport::Type` here"); + .assert_stderr_contains( + r#" +error[E0599]: no method named `foo` found for struct `dep_2_reexport::Type` in the current scope + --> multiple-dep-versions.rs:8:10 + | +8 | Type.foo(); + | ^^^ method not found in `Type` + | +note: there are multiple different versions of crate `dependency` in the dependency graph"#, + ) + .assert_stderr_contains( + r#" +4 | pub trait Trait { + | ^^^^^^^^^^^^^^^ this is the trait that is needed +5 | fn foo(&self); + | -------------- the method is available for `dep_2_reexport::Type` here + | + ::: multiple-dep-versions.rs:4:32 + | +4 | use dependency::{do_something, Trait}; + | ----- `Trait` imported here doesn't correspond to the right version of crate `dependency`"#, + ) + .assert_stderr_contains( + r#" +4 | pub trait Trait { + | --------------- this is the trait that was imported"#, + ) + .assert_stderr_contains( + r#" +error[E0599]: no function or associated item named `bar` found for struct `dep_2_reexport::Type` in the current scope + --> multiple-dep-versions.rs:9:11 + | +9 | Type::bar(); + | ^^^ function or associated item not found in `Type` + | +note: there are multiple different versions of crate `dependency` in the dependency graph"#, + ) + .assert_stderr_contains( + r#" +4 | pub trait Trait { + | ^^^^^^^^^^^^^^^ this is the trait that is needed +5 | fn foo(&self); +6 | fn bar(); + | --------- the method is available for `dep_2_reexport::Type` here + | + ::: multiple-dep-versions.rs:4:32 + | +4 | use dependency::{do_something, Trait}; + | ----- `Trait` imported here doesn't correspond to the right version of crate `dependency`"#, + ); } From 110b19b2b6a9896d4d73b8bd8b735fa9c2503d1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Esteban=20K=C3=BCber?= Date: Mon, 12 Aug 2024 19:45:20 +0000 Subject: [PATCH 06/16] Properly differentiate between methods and assoc fns --- compiler/rustc_hir_typeck/src/method/suggest.rs | 3 ++- tests/run-make/crate-loading/rmake.rs | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index 31366a3076050..ab13b31439c04 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -4109,8 +4109,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { let trait_span = self.tcx.def_span(trait_def_id); let mut multi_span: MultiSpan = trait_span.into(); multi_span.push_span_label(trait_span, format!("this is the trait that is needed")); + let descr = self.tcx.associated_item(item_def_id).descr(); multi_span - .push_span_label(item_span, format!("the method is available for `{rcvr_ty}` here")); + .push_span_label(item_span, format!("the {descr} is available for `{rcvr_ty}` here")); for (def_id, import_def_id) in candidates { if let Some(import_def_id) = import_def_id { multi_span.push_span_label( diff --git a/tests/run-make/crate-loading/rmake.rs b/tests/run-make/crate-loading/rmake.rs index 65cbc8b0b1b96..13585edf6ccbe 100644 --- a/tests/run-make/crate-loading/rmake.rs +++ b/tests/run-make/crate-loading/rmake.rs @@ -90,7 +90,7 @@ note: there are multiple different versions of crate `dependency` in the depende | ^^^^^^^^^^^^^^^ this is the trait that is needed 5 | fn foo(&self); 6 | fn bar(); - | --------- the method is available for `dep_2_reexport::Type` here + | --------- the associated function is available for `dep_2_reexport::Type` here | ::: multiple-dep-versions.rs:4:32 | From 35785ef39fa01250f6c4db6bb08e239f55c308ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E8=AE=B8=E6=9D=B0=E5=8F=8B=20Jieyou=20Xu=20=28Joe=29?= Date: Thu, 15 Aug 2024 05:13:05 +0000 Subject: [PATCH 07/16] tests: re-enable `dump-ice-to-disk` for Windows This test was previously flakey on `i686-mingw`, but since some modifications I could no longer make it fail on `i686-mingw`. See for multiple try job runs. --- tests/run-make/dump-ice-to-disk/rmake.rs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/tests/run-make/dump-ice-to-disk/rmake.rs b/tests/run-make/dump-ice-to-disk/rmake.rs index 48b4071e06541..08767246b90d0 100644 --- a/tests/run-make/dump-ice-to-disk/rmake.rs +++ b/tests/run-make/dump-ice-to-disk/rmake.rs @@ -14,11 +14,14 @@ //! that `RUSTC_ICE_PATH` takes precedence and no ICE dump is emitted under `METRICS_PATH`. //! //! See . - -//@ ignore-windows -// FIXME(#128911): @jieyouxu: This test is sometimes for whatever forsaken reason flakey in -// `i686-mingw`, and I cannot reproduce it locally. The error messages upon assertion failure in -// this test is intentionally extremely verbose to aid debugging that issue. +//! +//! # Test history +//! +//! - The previous rmake.rs iteration of this test was flakey for unknown reason on `i686-mingw` +//! *specifically*, so assertion failures in this test was made extremely verbose to help +//! diagnose why the ICE messages was different *specifically* on `i686-mingw`. +//! - An attempt is made to re-enable this test on `i686-mingw` (by removing `ignore-windows`). If +//! this test is still flakey, please restore the `ignore-windows` directive. use std::cell::OnceCell; use std::path::{Path, PathBuf}; From f21d6353dc5129800d184729ffaf01838f46793e Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Fri, 16 Aug 2024 19:32:22 +0000 Subject: [PATCH 08/16] Always use ar_archive_writer for import libs --- Cargo.lock | 4 +- .../rustc_codegen_cranelift/src/archive.rs | 83 +--------------- compiler/rustc_codegen_cranelift/src/lib.rs | 1 - compiler/rustc_codegen_llvm/messages.ftl | 3 - .../rustc_codegen_llvm/src/back/archive.rs | 94 +------------------ compiler/rustc_codegen_llvm/src/errors.rs | 7 -- compiler/rustc_codegen_ssa/Cargo.toml | 2 +- compiler/rustc_codegen_ssa/messages.ftl | 3 + .../rustc_codegen_ssa/src/back/archive.rs | 86 ++++++++++++++++- compiler/rustc_codegen_ssa/src/errors.rs | 7 ++ 10 files changed, 100 insertions(+), 190 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1aa0b2ea4a398..ae5d3e12e3af9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -205,9 +205,9 @@ dependencies = [ [[package]] name = "ar_archive_writer" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2bcb7cf51decfbbfc7ef476e28b0775b13e5eb1190f8b7df145cd53d4f4374" +checksum = "de11a9d32db3327f981143bdf699ade4d637c6887b13b97e6e91a9154666963c" dependencies = [ "object 0.36.2", ] diff --git a/compiler/rustc_codegen_cranelift/src/archive.rs b/compiler/rustc_codegen_cranelift/src/archive.rs index 5eedab4f2cbaa..c7725e49c9442 100644 --- a/compiler/rustc_codegen_cranelift/src/archive.rs +++ b/compiler/rustc_codegen_cranelift/src/archive.rs @@ -1,13 +1,6 @@ -use std::borrow::Borrow; -use std::fs; -use std::path::Path; - -use ar_archive_writer::{COFFShortExport, MachineTypes}; use rustc_codegen_ssa::back::archive::{ - create_mingw_dll_import_lib, ArArchiveBuilder, ArchiveBuilder, ArchiveBuilderBuilder, - DEFAULT_OBJECT_READER, + ArArchiveBuilder, ArchiveBuilder, ArchiveBuilderBuilder, DEFAULT_OBJECT_READER, }; -use rustc_codegen_ssa::common::is_mingw_gnu_toolchain; use rustc_session::Session; pub(crate) struct ArArchiveBuilderBuilder; @@ -16,78 +9,4 @@ impl ArchiveBuilderBuilder for ArArchiveBuilderBuilder { fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box { Box::new(ArArchiveBuilder::new(sess, &DEFAULT_OBJECT_READER)) } - - fn create_dll_import_lib( - &self, - sess: &Session, - lib_name: &str, - import_name_and_ordinal_vector: Vec<(String, Option)>, - output_path: &Path, - ) { - if is_mingw_gnu_toolchain(&sess.target) { - // The binutils linker used on -windows-gnu targets cannot read the import - // libraries generated by LLVM: in our attempts, the linker produced an .EXE - // that loaded but crashed with an AV upon calling one of the imported - // functions. Therefore, use binutils to create the import library instead, - // by writing a .DEF file to the temp dir and calling binutils's dlltool. - create_mingw_dll_import_lib( - sess, - lib_name, - import_name_and_ordinal_vector, - output_path, - ); - } else { - let mut file = - match fs::OpenOptions::new().write(true).create_new(true).open(&output_path) { - Ok(file) => file, - Err(error) => { - sess.dcx().fatal(format!( - "failed to create import library file `{path}`: {error}", - path = output_path.display(), - )); - } - }; - - let machine = match sess.target.arch.borrow() { - "x86" => MachineTypes::I386, - "x86_64" => MachineTypes::AMD64, - "arm" => MachineTypes::ARMNT, - "aarch64" => MachineTypes::ARM64, - _ => { - sess.dcx().fatal(format!( - "unsupported target architecture `{arch}`", - arch = sess.target.arch, - )); - } - }; - - let exports = import_name_and_ordinal_vector - .iter() - .map(|(name, ordinal)| COFFShortExport { - name: name.to_string(), - ext_name: None, - symbol_name: None, - alias_target: None, - ordinal: ordinal.unwrap_or(0), - noname: ordinal.is_some(), - data: false, - private: false, - constant: false, - }) - .collect::>(); - - if let Err(error) = ar_archive_writer::write_import_library( - &mut file, - lib_name, - &exports, - machine, - !sess.target.is_like_msvc, - ) { - sess.dcx().fatal(format!( - "failed to create import library `{path}`: `{error}`", - path = output_path.display(), - )); - } - } - } } diff --git a/compiler/rustc_codegen_cranelift/src/lib.rs b/compiler/rustc_codegen_cranelift/src/lib.rs index 21930fa2ddb49..f737af25b62ee 100644 --- a/compiler/rustc_codegen_cranelift/src/lib.rs +++ b/compiler/rustc_codegen_cranelift/src/lib.rs @@ -12,7 +12,6 @@ #![warn(unused_lifetimes)] // tidy-alphabetical-end -extern crate ar_archive_writer; extern crate jobserver; #[macro_use] extern crate rustc_middle; diff --git a/compiler/rustc_codegen_llvm/messages.ftl b/compiler/rustc_codegen_llvm/messages.ftl index 267da9325c386..df2198df14b6a 100644 --- a/compiler/rustc_codegen_llvm/messages.ftl +++ b/compiler/rustc_codegen_llvm/messages.ftl @@ -5,9 +5,6 @@ codegen_llvm_dynamic_linking_with_lto = .note = only 'staticlib', 'bin', and 'cdylib' outputs are supported with LTO -codegen_llvm_error_creating_import_library = - Error creating import library for {$lib_name}: {$error} - codegen_llvm_fixed_x18_invalid_arch = the `-Zfixed-x18` flag is not supported on the `{$arch}` architecture codegen_llvm_from_llvm_diag = {$message} diff --git a/compiler/rustc_codegen_llvm/src/back/archive.rs b/compiler/rustc_codegen_llvm/src/back/archive.rs index 2120fc1815cd8..c0ec4f86b4dbb 100644 --- a/compiler/rustc_codegen_llvm/src/back/archive.rs +++ b/compiler/rustc_codegen_llvm/src/back/archive.rs @@ -5,17 +5,13 @@ use std::path::{Path, PathBuf}; use std::{io, mem, ptr, str}; use rustc_codegen_ssa::back::archive::{ - create_mingw_dll_import_lib, try_extract_macho_fat_archive, ArArchiveBuilder, - ArchiveBuildFailure, ArchiveBuilder, ArchiveBuilderBuilder, ObjectReader, UnknownArchiveKind, - DEFAULT_OBJECT_READER, + try_extract_macho_fat_archive, ArArchiveBuilder, ArchiveBuildFailure, ArchiveBuilder, + ArchiveBuilderBuilder, ObjectReader, UnknownArchiveKind, DEFAULT_OBJECT_READER, }; -use rustc_codegen_ssa::common; use rustc_session::Session; -use tracing::trace; -use crate::errors::ErrorCreatingImportLibrary; use crate::llvm::archive_ro::{ArchiveRO, Child}; -use crate::llvm::{self, ArchiveKind, LLVMMachineType, LLVMRustCOFFShortExport}; +use crate::llvm::{self, ArchiveKind}; /// Helper for adding many files to an archive. #[must_use = "must call build() to finish building the archive"] @@ -44,18 +40,6 @@ fn is_relevant_child(c: &Child<'_>) -> bool { } } -/// Map machine type strings to values of LLVM's MachineTypes enum. -fn llvm_machine_type(cpu: &str) -> LLVMMachineType { - match cpu { - "x86_64" => LLVMMachineType::AMD64, - "x86" => LLVMMachineType::I386, - "aarch64" => LLVMMachineType::ARM64, - "arm64ec" => LLVMMachineType::ARM64EC, - "arm" => LLVMMachineType::ARM, - _ => panic!("unsupported cpu type {cpu}"), - } -} - impl<'a> ArchiveBuilder for LlvmArchiveBuilder<'a> { fn add_archive( &mut self, @@ -116,78 +100,6 @@ impl ArchiveBuilderBuilder for LlvmArchiveBuilderBuilder { Box::new(ArArchiveBuilder::new(sess, &LLVM_OBJECT_READER)) } } - - fn create_dll_import_lib( - &self, - sess: &Session, - lib_name: &str, - import_name_and_ordinal_vector: Vec<(String, Option)>, - output_path: &Path, - ) { - if common::is_mingw_gnu_toolchain(&sess.target) { - // The binutils linker used on -windows-gnu targets cannot read the import - // libraries generated by LLVM: in our attempts, the linker produced an .EXE - // that loaded but crashed with an AV upon calling one of the imported - // functions. Therefore, use binutils to create the import library instead, - // by writing a .DEF file to the temp dir and calling binutils's dlltool. - create_mingw_dll_import_lib( - sess, - lib_name, - import_name_and_ordinal_vector, - output_path, - ); - } else { - // we've checked for \0 characters in the library name already - let dll_name_z = CString::new(lib_name).unwrap(); - - let output_path_z = rustc_fs_util::path_to_c_string(&output_path); - - trace!("invoking LLVMRustWriteImportLibrary"); - trace!(" dll_name {:#?}", dll_name_z); - trace!(" output_path {}", output_path.display()); - trace!( - " import names: {}", - import_name_and_ordinal_vector - .iter() - .map(|(name, _ordinal)| name.clone()) - .collect::>() - .join(", "), - ); - - // All import names are Rust identifiers and therefore cannot contain \0 characters. - // FIXME: when support for #[link_name] is implemented, ensure that the import names - // still don't contain any \0 characters. Also need to check that the names don't - // contain substrings like " @" or "NONAME" that are keywords or otherwise reserved - // in definition files. - let cstring_import_name_and_ordinal_vector: Vec<(CString, Option)> = - import_name_and_ordinal_vector - .into_iter() - .map(|(name, ordinal)| (CString::new(name).unwrap(), ordinal)) - .collect(); - - let ffi_exports: Vec = cstring_import_name_and_ordinal_vector - .iter() - .map(|(name_z, ordinal)| LLVMRustCOFFShortExport::new(name_z.as_ptr(), *ordinal)) - .collect(); - let result = unsafe { - crate::llvm::LLVMRustWriteImportLibrary( - dll_name_z.as_ptr(), - output_path_z.as_ptr(), - ffi_exports.as_ptr(), - ffi_exports.len(), - llvm_machine_type(&sess.target.arch) as u16, - !sess.target.is_like_msvc, - ) - }; - - if result == crate::llvm::LLVMRustResult::Failure { - sess.dcx().emit_fatal(ErrorCreatingImportLibrary { - lib_name, - error: llvm::last_error().unwrap_or("unknown LLVM error".to_string()), - }); - } - } - } } // The object crate doesn't know how to get symbols for LLVM bitcode and COFF bigobj files. diff --git a/compiler/rustc_codegen_llvm/src/errors.rs b/compiler/rustc_codegen_llvm/src/errors.rs index 7e53d32ce8cd3..e0ec9cdca5606 100644 --- a/compiler/rustc_codegen_llvm/src/errors.rs +++ b/compiler/rustc_codegen_llvm/src/errors.rs @@ -39,13 +39,6 @@ pub(crate) enum PossibleFeature<'a> { None, } -#[derive(Diagnostic)] -#[diag(codegen_llvm_error_creating_import_library)] -pub(crate) struct ErrorCreatingImportLibrary<'a> { - pub lib_name: &'a str, - pub error: String, -} - #[derive(Diagnostic)] #[diag(codegen_llvm_symbol_already_defined)] pub(crate) struct SymbolAlreadyDefined<'a> { diff --git a/compiler/rustc_codegen_ssa/Cargo.toml b/compiler/rustc_codegen_ssa/Cargo.toml index 6e1c323cbd063..0af34a1b9fa62 100644 --- a/compiler/rustc_codegen_ssa/Cargo.toml +++ b/compiler/rustc_codegen_ssa/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] # tidy-alphabetical-start -ar_archive_writer = "0.3.3" +ar_archive_writer = "0.4.0" arrayvec = { version = "0.7", default-features = false } bitflags = "2.4.1" cc = "1.0.90" diff --git a/compiler/rustc_codegen_ssa/messages.ftl b/compiler/rustc_codegen_ssa/messages.ftl index 80f25d42a085e..8a6a2acd87d44 100644 --- a/compiler/rustc_codegen_ssa/messages.ftl +++ b/compiler/rustc_codegen_ssa/messages.ftl @@ -32,6 +32,9 @@ codegen_ssa_dlltool_fail_import_library = codegen_ssa_error_calling_dlltool = Error calling dlltool '{$dlltool_path}': {$error} +codegen_ssa_error_creating_import_library = + Error creating import library for {$lib_name}: {$error} + codegen_ssa_error_creating_remark_dir = failed to create remark directory: {$error} codegen_ssa_error_writing_def_file = diff --git a/compiler/rustc_codegen_ssa/src/back/archive.rs b/compiler/rustc_codegen_ssa/src/back/archive.rs index 8eb44d1201640..87956e9f26527 100644 --- a/compiler/rustc_codegen_ssa/src/back/archive.rs +++ b/compiler/rustc_codegen_ssa/src/back/archive.rs @@ -5,7 +5,9 @@ use std::fs::{self, File}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; -use ar_archive_writer::{write_archive_to_stream, ArchiveKind, NewArchiveMember}; +use ar_archive_writer::{ + write_archive_to_stream, ArchiveKind, COFFShortExport, MachineTypes, NewArchiveMember, +}; pub use ar_archive_writer::{ObjectReader, DEFAULT_OBJECT_READER}; use object::read::archive::ArchiveFile; use object::read::macho::FatArch; @@ -14,11 +16,15 @@ use rustc_data_structures::memmap::Mmap; use rustc_session::Session; use rustc_span::symbol::Symbol; use tempfile::Builder as TempFileBuilder; +use tracing::trace; use super::metadata::search_for_section; +use crate::common; // Re-exporting for rustc_codegen_llvm::back::archive pub use crate::errors::{ArchiveBuildFailure, ExtractBundledLibsError, UnknownArchiveKind}; -use crate::errors::{DlltoolFailImportLibrary, ErrorCallingDllTool, ErrorWritingDEFFile}; +use crate::errors::{ + DlltoolFailImportLibrary, ErrorCallingDllTool, ErrorCreatingImportLibrary, ErrorWritingDEFFile, +}; pub trait ArchiveBuilderBuilder { fn new_archive_builder<'a>(&self, sess: &'a Session) -> Box; @@ -34,7 +40,81 @@ pub trait ArchiveBuilderBuilder { lib_name: &str, import_name_and_ordinal_vector: Vec<(String, Option)>, output_path: &Path, - ); + ) { + if common::is_mingw_gnu_toolchain(&sess.target) { + // The binutils linker used on -windows-gnu targets cannot read the import + // libraries generated by LLVM: in our attempts, the linker produced an .EXE + // that loaded but crashed with an AV upon calling one of the imported + // functions. Therefore, use binutils to create the import library instead, + // by writing a .DEF file to the temp dir and calling binutils's dlltool. + create_mingw_dll_import_lib( + sess, + lib_name, + import_name_and_ordinal_vector, + output_path, + ); + } else { + trace!("creating import library"); + trace!(" dll_name {:#?}", lib_name); + trace!(" output_path {}", output_path.display()); + trace!( + " import names: {}", + import_name_and_ordinal_vector + .iter() + .map(|(name, _ordinal)| name.clone()) + .collect::>() + .join(", "), + ); + + // All import names are Rust identifiers and therefore cannot contain \0 characters. + // FIXME: when support for #[link_name] is implemented, ensure that the import names + // still don't contain any \0 characters. Also need to check that the names don't + // contain substrings like " @" or "NONAME" that are keywords or otherwise reserved + // in definition files. + + let mut file = match fs::File::create_new(&output_path) { + Ok(file) => file, + Err(error) => sess + .dcx() + .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() }), + }; + + let exports = import_name_and_ordinal_vector + .iter() + .map(|(name, ordinal)| COFFShortExport { + name: name.to_string(), + ext_name: None, + symbol_name: None, + alias_target: None, + ordinal: ordinal.unwrap_or(0), + noname: ordinal.is_some(), + data: false, + private: false, + constant: false, + }) + .collect::>(); + let machine = match &*sess.target.arch { + "x86_64" => MachineTypes::AMD64, + "x86" => MachineTypes::I386, + "aarch64" => MachineTypes::ARM64, + "arm64ec" => MachineTypes::ARM64EC, + "arm" => MachineTypes::ARMNT, + cpu => panic!("unsupported cpu type {cpu}"), + }; + + if let Err(error) = ar_archive_writer::write_import_library( + &mut file, + lib_name, + &exports, + machine, + !sess.target.is_like_msvc, + /*comdat=*/ true, + ) { + sess.dcx() + .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() }); + } + } + } fn extract_bundled_libs<'a>( &'a self, diff --git a/compiler/rustc_codegen_ssa/src/errors.rs b/compiler/rustc_codegen_ssa/src/errors.rs index 94bf0ab34e21b..573a8cf7cbe42 100644 --- a/compiler/rustc_codegen_ssa/src/errors.rs +++ b/compiler/rustc_codegen_ssa/src/errors.rs @@ -1060,3 +1060,10 @@ pub struct CompilerBuiltinsCannotCall { pub caller: String, pub callee: String, } + +#[derive(Diagnostic)] +#[diag(codegen_ssa_error_creating_import_library)] +pub(crate) struct ErrorCreatingImportLibrary<'a> { + pub lib_name: &'a str, + pub error: String, +} From 29017e45a1c85afe457765a5d4c77e4fcdebb4f6 Mon Sep 17 00:00:00 2001 From: Josh Stone Date: Fri, 16 Aug 2024 12:42:02 -0700 Subject: [PATCH 09/16] mir/pretty: use `Option` instead of `Either` `Either` is wasteful for a one-or-none iterator, especially since `Once` is already an `option::IntoIter` internally. We don't really need any of the iterator mechanisms in this case, just a single conditional insert. --- compiler/rustc_middle/src/mir/pretty.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index f2d8781413096..5dd0e69cf1fe6 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -1418,21 +1418,19 @@ pub fn write_allocations<'tcx>( alloc.inner().provenance().ptrs().values().map(|p| p.alloc_id()) } - fn alloc_ids_from_const_val(val: ConstValue<'_>) -> impl Iterator + '_ { + fn alloc_id_from_const_val(val: ConstValue<'_>) -> Option { match val { - ConstValue::Scalar(interpret::Scalar::Ptr(ptr, _)) => { - Either::Left(std::iter::once(ptr.provenance.alloc_id())) - } - ConstValue::Scalar(interpret::Scalar::Int { .. }) => Either::Right(std::iter::empty()), - ConstValue::ZeroSized => Either::Right(std::iter::empty()), + ConstValue::Scalar(interpret::Scalar::Ptr(ptr, _)) => Some(ptr.provenance.alloc_id()), + ConstValue::Scalar(interpret::Scalar::Int { .. }) => None, + ConstValue::ZeroSized => None, ConstValue::Slice { .. } => { // `u8`/`str` slices, shouldn't contain pointers that we want to print. - Either::Right(std::iter::empty()) + None } ConstValue::Indirect { alloc_id, .. } => { // FIXME: we don't actually want to print all of these, since some are printed nicely directly as values inline in MIR. // Really we'd want `pretty_print_const_value` to decide which allocations to print, instead of having a separate visitor. - Either::Left(std::iter::once(alloc_id)) + Some(alloc_id) } } } @@ -1443,7 +1441,9 @@ pub fn write_allocations<'tcx>( match c.const_ { Const::Ty(_, _) | Const::Unevaluated(..) => {} Const::Val(val, _) => { - self.0.extend(alloc_ids_from_const_val(val)); + if let Some(id) = alloc_id_from_const_val(val) { + self.0.insert(id); + } } } } From c396e3625acb2e9bc86897aeb202d24d624dca5a Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Fri, 16 Aug 2024 19:44:19 +0000 Subject: [PATCH 10/16] Explain why we set the comdat option --- compiler/rustc_codegen_ssa/src/back/archive.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/compiler/rustc_codegen_ssa/src/back/archive.rs b/compiler/rustc_codegen_ssa/src/back/archive.rs index 87956e9f26527..e838c82d569f9 100644 --- a/compiler/rustc_codegen_ssa/src/back/archive.rs +++ b/compiler/rustc_codegen_ssa/src/back/archive.rs @@ -108,7 +108,11 @@ pub trait ArchiveBuilderBuilder { &exports, machine, !sess.target.is_like_msvc, - /*comdat=*/ true, + // Tell the import library writer to make `.idata$3` a COMDAT section. + // This prevents duplicate symbol errors when using /WHOLEARCHIVE + // to link a staticlib with the MSVC linker. + // See #129020 + true, ) { sess.dcx() .emit_fatal(ErrorCreatingImportLibrary { lib_name, error: error.to_string() }); From ed6315b3fef4ebd9aee053d407eb746c3b1d58bf Mon Sep 17 00:00:00 2001 From: Boxy Date: Fri, 16 Aug 2024 20:53:02 +0100 Subject: [PATCH 11/16] Rewrite `get_fn_id_for_return_block` --- compiler/rustc_middle/src/hir/map/mod.rs | 64 +++++----- tests/crashes/128810.rs | 25 ---- ..._body_owner_parent_found_in_diagnostics.rs | 34 ++++++ ...y_owner_parent_found_in_diagnostics.stderr | 113 ++++++++++++++++++ tests/ui/typeck/const-in-fn-call-generics.rs | 16 +++ .../typeck/const-in-fn-call-generics.stderr | 9 ++ 6 files changed, 199 insertions(+), 62 deletions(-) delete mode 100644 tests/crashes/128810.rs create mode 100644 tests/ui/delegation/correct_body_owner_parent_found_in_diagnostics.rs create mode 100644 tests/ui/delegation/correct_body_owner_parent_found_in_diagnostics.stderr create mode 100644 tests/ui/typeck/const-in-fn-call-generics.rs create mode 100644 tests/ui/typeck/const-in-fn-call-generics.stderr diff --git a/compiler/rustc_middle/src/hir/map/mod.rs b/compiler/rustc_middle/src/hir/map/mod.rs index edab6b5ebde84..da08027d66b19 100644 --- a/compiler/rustc_middle/src/hir/map/mod.rs +++ b/compiler/rustc_middle/src/hir/map/mod.rs @@ -554,53 +554,43 @@ impl<'hir> Map<'hir> { /// } /// ``` pub fn get_fn_id_for_return_block(self, id: HirId) -> Option { - let mut iter = self.parent_iter(id).peekable(); - let mut ignore_tail = false; - if let Node::Expr(Expr { kind: ExprKind::Ret(_), .. }) = self.tcx.hir_node(id) { - // When dealing with `return` statements, we don't care about climbing only tail - // expressions. - ignore_tail = true; - } + let enclosing_body_owner = self.tcx.local_def_id_to_hir_id(self.enclosing_body_owner(id)); + + // Return `None` if the `id` expression is not the returned value of the enclosing body + let mut iter = [id].into_iter().chain(self.parent_id_iter(id)).peekable(); + while let Some(cur_id) = iter.next() { + if enclosing_body_owner == cur_id { + break; + } + + // A return statement is always the value returned from the enclosing body regardless of + // what the parent expressions are. + if let Node::Expr(Expr { kind: ExprKind::Ret(_), .. }) = self.tcx.hir_node(cur_id) { + break; + } - let mut prev_hir_id = None; - while let Some((hir_id, node)) = iter.next() { - if let (Some((_, next_node)), false) = (iter.peek(), ignore_tail) { - match next_node { - Node::Block(Block { expr: None, .. }) => return None, - // The current node is not the tail expression of its parent. - Node::Block(Block { expr: Some(e), .. }) if hir_id != e.hir_id => return None, + // If the current expression's value doesnt get used as the parent expressions value then return `None` + if let Some(&parent_id) = iter.peek() { + match self.tcx.hir_node(parent_id) { + // The current node is not the tail expression of the block expression parent expr. + Node::Block(Block { expr: Some(e), .. }) if cur_id != e.hir_id => return None, Node::Block(Block { expr: Some(e), .. }) if matches!(e.kind, ExprKind::If(_, _, None)) => { return None; } + + // The current expression's value does not pass up through these parent expressions + Node::Block(Block { expr: None, .. }) + | Node::Expr(Expr { kind: ExprKind::Loop(..), .. }) + | Node::LetStmt(..) => return None, + _ => {} } } - match node { - Node::Item(_) - | Node::ForeignItem(_) - | Node::TraitItem(_) - | Node::Expr(Expr { kind: ExprKind::Closure(_), .. }) - | Node::ImplItem(_) - // The input node `id` must be enclosed in the method's body as opposed - // to some other place such as its return type (fixes #114918). - // We verify that indirectly by checking that the previous node is the - // current node's body - if node.body_id().map(|b| b.hir_id) == prev_hir_id => { - return Some(hir_id) - } - // Ignore `return`s on the first iteration - Node::Expr(Expr { kind: ExprKind::Loop(..) | ExprKind::Ret(..), .. }) - | Node::LetStmt(_) => { - return None; - } - _ => {} - } - - prev_hir_id = Some(hir_id); } - None + + Some(enclosing_body_owner) } /// Retrieves the `OwnerId` for `id`'s parent item, or `id` itself if no diff --git a/tests/crashes/128810.rs b/tests/crashes/128810.rs deleted file mode 100644 index 68214ff010c99..0000000000000 --- a/tests/crashes/128810.rs +++ /dev/null @@ -1,25 +0,0 @@ -//@ known-bug: rust-lang/rust#128810 - -#![feature(fn_delegation)] - -use std::marker::PhantomData; - -pub struct InvariantRef<'a, T: ?Sized>(&'a T, PhantomData<&'a mut &'a T>); - -impl<'a> InvariantRef<'a, ()> { - pub const NEW: Self = InvariantRef::new(&()); -} - -trait Trait { - fn foo(&self) -> u8 { 0 } - fn bar(&self) -> u8 { 1 } - fn meh(&self) -> u8 { 2 } -} - -struct Z(u8); - -impl Trait for Z { - reuse ::{foo, bar, meh} { &const { InvariantRef::<'a>::NEW } } -} - -fn main() { } diff --git a/tests/ui/delegation/correct_body_owner_parent_found_in_diagnostics.rs b/tests/ui/delegation/correct_body_owner_parent_found_in_diagnostics.rs new file mode 100644 index 0000000000000..0a7ec5ab5c1be --- /dev/null +++ b/tests/ui/delegation/correct_body_owner_parent_found_in_diagnostics.rs @@ -0,0 +1,34 @@ +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +use std::marker::PhantomData; + +pub struct InvariantRef<'a, T: ?Sized>(&'a T, PhantomData<&'a mut &'a T>); + +impl<'a> InvariantRef<'a, ()> { + pub const NEW: Self = InvariantRef::new(&()); + //~^ ERROR: no function or associated item named `new` found +} + +trait Trait { + fn foo(&self) -> u8 { 0 } + fn bar(&self) -> u8 { 1 } + fn meh(&self) -> u8 { 2 } +} + +struct Z(u8); + +impl Trait for Z { + reuse ::{foo, bar, meh} { &const { InvariantRef::<'a>::NEW } } + //~^ ERROR: use of undeclared lifetime name `'a` + //~| ERROR: use of undeclared lifetime name `'a` + //~| ERROR: use of undeclared lifetime name `'a` + //~| ERROR: the trait bound `u8: Trait` is not satisfied + //~| ERROR: the trait bound `u8: Trait` is not satisfied + //~| ERROR: the trait bound `u8: Trait` is not satisfied + //~| ERROR: mismatched types + //~| ERROR: mismatched types + //~| ERROR: mismatched types +} + +fn main() { } diff --git a/tests/ui/delegation/correct_body_owner_parent_found_in_diagnostics.stderr b/tests/ui/delegation/correct_body_owner_parent_found_in_diagnostics.stderr new file mode 100644 index 0000000000000..2ce3b388073c4 --- /dev/null +++ b/tests/ui/delegation/correct_body_owner_parent_found_in_diagnostics.stderr @@ -0,0 +1,113 @@ +error[E0261]: use of undeclared lifetime name `'a` + --> $DIR/correct_body_owner_parent_found_in_diagnostics.rs:22:68 + | +LL | reuse ::{foo, bar, meh} { &const { InvariantRef::<'a>::NEW } } + | ^^ undeclared lifetime + | +help: consider introducing lifetime `'a` here + | +LL | reuse ::{foo'a, , bar, meh} { &const { InvariantRef::<'a>::NEW } } + | +++ +help: consider introducing lifetime `'a` here + | +LL | impl<'a> Trait for Z { + | ++++ + +error[E0261]: use of undeclared lifetime name `'a` + --> $DIR/correct_body_owner_parent_found_in_diagnostics.rs:22:68 + | +LL | reuse ::{foo, bar, meh} { &const { InvariantRef::<'a>::NEW } } + | ^^ undeclared lifetime + | +help: consider introducing lifetime `'a` here + | +LL | reuse ::{foo, bar'a, , meh} { &const { InvariantRef::<'a>::NEW } } + | +++ +help: consider introducing lifetime `'a` here + | +LL | impl<'a> Trait for Z { + | ++++ + +error[E0261]: use of undeclared lifetime name `'a` + --> $DIR/correct_body_owner_parent_found_in_diagnostics.rs:22:68 + | +LL | reuse ::{foo, bar, meh} { &const { InvariantRef::<'a>::NEW } } + | ^^ undeclared lifetime + | +help: consider introducing lifetime `'a` here + | +LL | reuse ::{foo, bar, meh'a, } { &const { InvariantRef::<'a>::NEW } } + | +++ +help: consider introducing lifetime `'a` here + | +LL | impl<'a> Trait for Z { + | ++++ + +error[E0599]: no function or associated item named `new` found for struct `InvariantRef` in the current scope + --> $DIR/correct_body_owner_parent_found_in_diagnostics.rs:9:41 + | +LL | pub struct InvariantRef<'a, T: ?Sized>(&'a T, PhantomData<&'a mut &'a T>); + | -------------------------------------- function or associated item `new` not found for this struct +... +LL | pub const NEW: Self = InvariantRef::new(&()); + | ^^^ function or associated item not found in `InvariantRef<'_, _>` + +error[E0308]: mismatched types + --> $DIR/correct_body_owner_parent_found_in_diagnostics.rs:22:53 + | +LL | reuse ::{foo, bar, meh} { &const { InvariantRef::<'a>::NEW } } + | ^^^^^^^^^^^^^^^^^^^^^^^ expected `u8`, found `InvariantRef<'_, ()>` + | + = note: expected type `u8` + found struct `InvariantRef<'_, ()>` + +error[E0277]: the trait bound `u8: Trait` is not satisfied + --> $DIR/correct_body_owner_parent_found_in_diagnostics.rs:22:12 + | +LL | reuse ::{foo, bar, meh} { &const { InvariantRef::<'a>::NEW } } + | ^^ the trait `Trait` is not implemented for `u8` + | + = help: the trait `Trait` is implemented for `Z` + +error[E0308]: mismatched types + --> $DIR/correct_body_owner_parent_found_in_diagnostics.rs:22:53 + | +LL | reuse ::{foo, bar, meh} { &const { InvariantRef::<'a>::NEW } } + | ^^^^^^^^^^^^^^^^^^^^^^^ expected `u8`, found `InvariantRef<'_, ()>` + | + = note: expected type `u8` + found struct `InvariantRef<'_, ()>` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0277]: the trait bound `u8: Trait` is not satisfied + --> $DIR/correct_body_owner_parent_found_in_diagnostics.rs:22:12 + | +LL | reuse ::{foo, bar, meh} { &const { InvariantRef::<'a>::NEW } } + | ^^ the trait `Trait` is not implemented for `u8` + | + = help: the trait `Trait` is implemented for `Z` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0308]: mismatched types + --> $DIR/correct_body_owner_parent_found_in_diagnostics.rs:22:53 + | +LL | reuse ::{foo, bar, meh} { &const { InvariantRef::<'a>::NEW } } + | ^^^^^^^^^^^^^^^^^^^^^^^ expected `u8`, found `InvariantRef<'_, ()>` + | + = note: expected type `u8` + found struct `InvariantRef<'_, ()>` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error[E0277]: the trait bound `u8: Trait` is not satisfied + --> $DIR/correct_body_owner_parent_found_in_diagnostics.rs:22:12 + | +LL | reuse ::{foo, bar, meh} { &const { InvariantRef::<'a>::NEW } } + | ^^ the trait `Trait` is not implemented for `u8` + | + = help: the trait `Trait` is implemented for `Z` + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: aborting due to 10 previous errors + +Some errors have detailed explanations: E0261, E0277, E0308, E0599. +For more information about an error, try `rustc --explain E0261`. diff --git a/tests/ui/typeck/const-in-fn-call-generics.rs b/tests/ui/typeck/const-in-fn-call-generics.rs new file mode 100644 index 0000000000000..675dbcc305475 --- /dev/null +++ b/tests/ui/typeck/const-in-fn-call-generics.rs @@ -0,0 +1,16 @@ +fn generic() {} + +trait Collate { + type Pass; + fn collate(self) -> Self::Pass; +} + +impl Collate for i32 { + type Pass = (); + fn collate(self) -> Self::Pass { + generic::<{ true }>() + //~^ ERROR: mismatched types + } +} + +fn main() {} diff --git a/tests/ui/typeck/const-in-fn-call-generics.stderr b/tests/ui/typeck/const-in-fn-call-generics.stderr new file mode 100644 index 0000000000000..12dd454188cac --- /dev/null +++ b/tests/ui/typeck/const-in-fn-call-generics.stderr @@ -0,0 +1,9 @@ +error[E0308]: mismatched types + --> $DIR/const-in-fn-call-generics.rs:11:21 + | +LL | generic::<{ true }>() + | ^^^^ expected `u32`, found `bool` + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0308`. From c9451ac8c4d2699f7ed64c7e9bff043047ee2c4e Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Fri, 16 Aug 2024 20:01:56 +0000 Subject: [PATCH 12/16] Add test for MSVC WHOLEARCHIVE --- tests/run-make/msvc-wholearchive/c.c | 1 + tests/run-make/msvc-wholearchive/dll.def | 4 ++++ tests/run-make/msvc-wholearchive/rmake.rs | 22 ++++++++++++++++++++++ tests/run-make/msvc-wholearchive/static.rs | 9 +++++++++ 4 files changed, 36 insertions(+) create mode 100644 tests/run-make/msvc-wholearchive/c.c create mode 100644 tests/run-make/msvc-wholearchive/dll.def create mode 100644 tests/run-make/msvc-wholearchive/rmake.rs create mode 100644 tests/run-make/msvc-wholearchive/static.rs diff --git a/tests/run-make/msvc-wholearchive/c.c b/tests/run-make/msvc-wholearchive/c.c new file mode 100644 index 0000000000000..d6847845c6846 --- /dev/null +++ b/tests/run-make/msvc-wholearchive/c.c @@ -0,0 +1 @@ +// This page is intentionally left blank diff --git a/tests/run-make/msvc-wholearchive/dll.def b/tests/run-make/msvc-wholearchive/dll.def new file mode 100644 index 0000000000000..d55819e0d5e9f --- /dev/null +++ b/tests/run-make/msvc-wholearchive/dll.def @@ -0,0 +1,4 @@ +LIBRARY dll +EXPORTS + hello + number diff --git a/tests/run-make/msvc-wholearchive/rmake.rs b/tests/run-make/msvc-wholearchive/rmake.rs new file mode 100644 index 0000000000000..cd5c3b19115cc --- /dev/null +++ b/tests/run-make/msvc-wholearchive/rmake.rs @@ -0,0 +1,22 @@ +//@ only-msvc +// Reason: this is testing the MSVC linker + +// This is a regression test for #129020 +// It ensures we can link a rust staticlib into DLL using the MSVC linker + +use run_make_support::run::cmd; +use run_make_support::rustc; + +fn main() { + // Build the staticlib + rustc().crate_type("staticlib").input("static.rs").output("static.lib").run(); + // Create an empty obj file (using the C compiler) + // Then use it to link in the staticlib using `/WHOLEARCHIVE` and produce a DLL. + // We call link.exe directly because this test is only for MSVC and not their LLVM equivalents + cmd("cl.exe").args(&["c.c", "-nologo", "-c", "-MT"]).run(); + cmd("link.exe") + .args(&["c", "/WHOLEARCHIVE:./static.lib", "/dll", "/def:dll.def", "/out:dll.dll"]) + // Import libs + .args(&["libcmt.lib", "kernel32.lib", "ws2_32.lib", "ntdll.lib", "userenv.lib"]) + .run(); +} diff --git a/tests/run-make/msvc-wholearchive/static.rs b/tests/run-make/msvc-wholearchive/static.rs new file mode 100644 index 0000000000000..881c88565737a --- /dev/null +++ b/tests/run-make/msvc-wholearchive/static.rs @@ -0,0 +1,9 @@ +#[no_mangle] +pub extern "C" fn hello() { + println!("Hello world!"); +} + +#[no_mangle] +pub extern "C" fn number() -> u32 { + 42 +} From 9bc7cea412b31be0faf1fc1e65f39b9b952a43e6 Mon Sep 17 00:00:00 2001 From: beetrees Date: Fri, 16 Aug 2024 23:42:10 +0100 Subject: [PATCH 13/16] Fix `is_val_statically_known` for floats --- compiler/rustc_codegen_llvm/src/context.rs | 2 + compiler/rustc_codegen_llvm/src/intrinsic.rs | 22 ++++-- tests/codegen/is_val_statically_known.rs | 81 +++++++++++++++++++- 3 files changed, 96 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_codegen_llvm/src/context.rs b/compiler/rustc_codegen_llvm/src/context.rs index dd3f39eceadb6..1fd9f9e811671 100644 --- a/compiler/rustc_codegen_llvm/src/context.rs +++ b/compiler/rustc_codegen_llvm/src/context.rs @@ -1000,8 +1000,10 @@ impl<'ll> CodegenCx<'ll, '_> { ifn!("llvm.is.constant.i64", fn(t_i64) -> i1); ifn!("llvm.is.constant.i128", fn(t_i128) -> i1); ifn!("llvm.is.constant.isize", fn(t_isize) -> i1); + ifn!("llvm.is.constant.f16", fn(t_f16) -> i1); ifn!("llvm.is.constant.f32", fn(t_f32) -> i1); ifn!("llvm.is.constant.f64", fn(t_f64) -> i1); + ifn!("llvm.is.constant.f128", fn(t_f128) -> i1); ifn!("llvm.is.constant.ptr", fn(ptr) -> i1); ifn!("llvm.expect.i1", fn(i1, i1) -> i1); diff --git a/compiler/rustc_codegen_llvm/src/intrinsic.rs b/compiler/rustc_codegen_llvm/src/intrinsic.rs index f5558723d11bf..abfe38d4c0cc6 100644 --- a/compiler/rustc_codegen_llvm/src/intrinsic.rs +++ b/compiler/rustc_codegen_llvm/src/intrinsic.rs @@ -192,14 +192,22 @@ impl<'ll, 'tcx> IntrinsicCallMethods<'tcx> for Builder<'_, 'll, 'tcx> { } sym::is_val_statically_known => { let intrinsic_type = args[0].layout.immediate_llvm_type(self.cx); - match self.type_kind(intrinsic_type) { - TypeKind::Pointer | TypeKind::Integer | TypeKind::Float | TypeKind::Double => { - self.call_intrinsic( - &format!("llvm.is.constant.{:?}", intrinsic_type), - &[args[0].immediate()], - ) + let kind = self.type_kind(intrinsic_type); + let intrinsic_name = match kind { + TypeKind::Pointer | TypeKind::Integer => { + Some(format!("llvm.is.constant.{intrinsic_type:?}")) } - _ => self.const_bool(false), + // LLVM float types' intrinsic names differ from their type names. + TypeKind::Half => Some(format!("llvm.is.constant.f16")), + TypeKind::Float => Some(format!("llvm.is.constant.f32")), + TypeKind::Double => Some(format!("llvm.is.constant.f64")), + TypeKind::FP128 => Some(format!("llvm.is.constant.f128")), + _ => None, + }; + if let Some(intrinsic_name) = intrinsic_name { + self.call_intrinsic(&intrinsic_name, &[args[0].immediate()]) + } else { + self.const_bool(false) } } sym::unlikely => self diff --git a/tests/codegen/is_val_statically_known.rs b/tests/codegen/is_val_statically_known.rs index 6af4f353a481c..fe432d3bcc4de 100644 --- a/tests/codegen/is_val_statically_known.rs +++ b/tests/codegen/is_val_statically_known.rs @@ -1,6 +1,7 @@ //@ compile-flags: --crate-type=lib -Zmerge-functions=disabled -O #![feature(core_intrinsics)] +#![feature(f16, f128)] use std::intrinsics::is_val_statically_known; @@ -49,7 +50,7 @@ pub fn _bool_false(b: bool) -> i32 { #[inline] pub fn _iref(a: &u8) -> i32 { - if unsafe { is_val_statically_known(a) } { 5 } else { 4 } + if is_val_statically_known(a) { 5 } else { 4 } } // CHECK-LABEL: @_iref_borrow( @@ -68,7 +69,7 @@ pub fn _iref_arg(a: &u8) -> i32 { #[inline] pub fn _slice_ref(a: &[u8]) -> i32 { - if unsafe { is_val_statically_known(a) } { 7 } else { 6 } + if is_val_statically_known(a) { 7 } else { 6 } } // CHECK-LABEL: @_slice_ref_borrow( @@ -84,3 +85,79 @@ pub fn _slice_ref_arg(a: &[u8]) -> i32 { // CHECK: ret i32 6 _slice_ref(a) } + +#[inline] +pub fn _f16(a: f16) -> i32 { + if is_val_statically_known(a) { 1 } else { 0 } +} + +// CHECK-LABEL: @_f16_true( +#[no_mangle] +pub fn _f16_true() -> i32 { + // CHECK: ret i32 1 + _f16(1.0) +} + +// CHECK-LABEL: @_f16_false( +#[no_mangle] +pub fn _f16_false(a: f16) -> i32 { + // CHECK: ret i32 0 + _f16(a) +} + +#[inline] +pub fn _f32(a: f32) -> i32 { + if is_val_statically_known(a) { 1 } else { 0 } +} + +// CHECK-LABEL: @_f32_true( +#[no_mangle] +pub fn _f32_true() -> i32 { + // CHECK: ret i32 1 + _f32(1.0) +} + +// CHECK-LABEL: @_f32_false( +#[no_mangle] +pub fn _f32_false(a: f32) -> i32 { + // CHECK: ret i32 0 + _f32(a) +} + +#[inline] +pub fn _f64(a: f64) -> i32 { + if is_val_statically_known(a) { 1 } else { 0 } +} + +// CHECK-LABEL: @_f64_true( +#[no_mangle] +pub fn _f64_true() -> i32 { + // CHECK: ret i32 1 + _f64(1.0) +} + +// CHECK-LABEL: @_f64_false( +#[no_mangle] +pub fn _f64_false(a: f64) -> i32 { + // CHECK: ret i32 0 + _f64(a) +} + +#[inline] +pub fn _f128(a: f128) -> i32 { + if is_val_statically_known(a) { 1 } else { 0 } +} + +// CHECK-LABEL: @_f128_true( +#[no_mangle] +pub fn _f128_true() -> i32 { + // CHECK: ret i32 1 + _f128(1.0) +} + +// CHECK-LABEL: @_f128_false( +#[no_mangle] +pub fn _f128_false(a: f128) -> i32 { + // CHECK: ret i32 0 + _f128(a) +} From 70320c1936e2cdf0dd4f79c6539b32dd928d4cc2 Mon Sep 17 00:00:00 2001 From: Ben Kimock Date: Sun, 11 Aug 2024 14:25:00 -0400 Subject: [PATCH 14/16] Enable more debuginfo tests on Windows --- tests/debuginfo/drop-locations.rs | 1 - tests/debuginfo/embedded-visualizer.rs | 2 +- tests/debuginfo/empty-string.rs | 2 +- tests/debuginfo/issue-12886.rs | 3 +-- tests/debuginfo/macro-stepping.rs | 1 - tests/debuginfo/numeric-types.rs | 2 +- tests/debuginfo/pretty-huge-vec.rs | 2 +- tests/debuginfo/pretty-slices.rs | 2 +- tests/debuginfo/pretty-std-collections.rs | 1 - tests/debuginfo/pretty-uninitialized-vec.rs | 2 +- tests/debuginfo/thread-names.rs | 2 +- 11 files changed, 8 insertions(+), 12 deletions(-) diff --git a/tests/debuginfo/drop-locations.rs b/tests/debuginfo/drop-locations.rs index 0777313d19804..2db087dda7967 100644 --- a/tests/debuginfo/drop-locations.rs +++ b/tests/debuginfo/drop-locations.rs @@ -1,4 +1,3 @@ -//@ ignore-windows //@ ignore-android //@ min-lldb-version: 310 //@ ignore-test: #128971 diff --git a/tests/debuginfo/embedded-visualizer.rs b/tests/debuginfo/embedded-visualizer.rs index 69afd273f7735..959dbba7fd73c 100644 --- a/tests/debuginfo/embedded-visualizer.rs +++ b/tests/debuginfo/embedded-visualizer.rs @@ -1,7 +1,7 @@ //@ compile-flags:-g //@ min-gdb-version: 8.1 //@ ignore-lldb -//@ ignore-windows-gnu // emit_debug_gdb_scripts is disabled on Windows +//@ ignore-windows-gnu: #128981 // === CDB TESTS ================================================================================== diff --git a/tests/debuginfo/empty-string.rs b/tests/debuginfo/empty-string.rs index 35b68ed91c0fc..eac3448bfd510 100644 --- a/tests/debuginfo/empty-string.rs +++ b/tests/debuginfo/empty-string.rs @@ -1,4 +1,4 @@ -//@ ignore-windows failing on win32 bot +//@ ignore-windows-gnu: #128981 //@ ignore-android: FIXME(#10381) //@ compile-flags:-g //@ min-gdb-version: 8.1 diff --git a/tests/debuginfo/issue-12886.rs b/tests/debuginfo/issue-12886.rs index c6cf0dd4e05e3..48250e885374d 100644 --- a/tests/debuginfo/issue-12886.rs +++ b/tests/debuginfo/issue-12886.rs @@ -1,4 +1,3 @@ -//@ ignore-windows failing on 64-bit bots FIXME #17638 //@ ignore-lldb //@ ignore-aarch64 @@ -6,7 +5,7 @@ // gdb-command:run // gdb-command:next -// gdb-check:[...]24[...]let s = Some(5).unwrap(); // #break +// gdb-check:[...]23[...]let s = Some(5).unwrap(); // #break // gdb-command:continue #![feature(omit_gdb_pretty_printer_section)] diff --git a/tests/debuginfo/macro-stepping.rs b/tests/debuginfo/macro-stepping.rs index 3edac3c7832f9..35bb6de4fef38 100644 --- a/tests/debuginfo/macro-stepping.rs +++ b/tests/debuginfo/macro-stepping.rs @@ -1,4 +1,3 @@ -//@ ignore-windows //@ ignore-android //@ ignore-aarch64 //@ min-lldb-version: 1800 diff --git a/tests/debuginfo/numeric-types.rs b/tests/debuginfo/numeric-types.rs index 98bc31e88557a..6f45b69e5e34c 100644 --- a/tests/debuginfo/numeric-types.rs +++ b/tests/debuginfo/numeric-types.rs @@ -1,7 +1,7 @@ //@ compile-flags:-g //@ min-gdb-version: 8.1 -//@ ignore-windows-gnu // emit_debug_gdb_scripts is disabled on Windows +//@ ignore-windows-gnu: #128981 // Tests the visualizations for `NonZero`, `Wrapping` and // `Atomic{Bool,I8,I16,I32,I64,Isize,U8,U16,U32,U64,Usize}` located in `libcore.natvis`. diff --git a/tests/debuginfo/pretty-huge-vec.rs b/tests/debuginfo/pretty-huge-vec.rs index dcf3521175d4c..b88e5db1acb8c 100644 --- a/tests/debuginfo/pretty-huge-vec.rs +++ b/tests/debuginfo/pretty-huge-vec.rs @@ -1,4 +1,4 @@ -//@ ignore-windows failing on win32 bot +//@ ignore-windows-gnu: #128981 //@ ignore-android: FIXME(#10381) //@ compile-flags:-g //@ min-gdb-version: 8.1 diff --git a/tests/debuginfo/pretty-slices.rs b/tests/debuginfo/pretty-slices.rs index 9defa344be03e..44335f5c83b3f 100644 --- a/tests/debuginfo/pretty-slices.rs +++ b/tests/debuginfo/pretty-slices.rs @@ -1,5 +1,5 @@ //@ ignore-android: FIXME(#10381) -//@ ignore-windows +//@ ignore-windows-gnu: #128981 //@ compile-flags:-g // gdb-command: run diff --git a/tests/debuginfo/pretty-std-collections.rs b/tests/debuginfo/pretty-std-collections.rs index 3d5a30d19a9ce..3ec00886e2f40 100644 --- a/tests/debuginfo/pretty-std-collections.rs +++ b/tests/debuginfo/pretty-std-collections.rs @@ -1,4 +1,3 @@ -//@ ignore-windows failing on win32 bot //@ ignore-android: FIXME(#10381) //@ ignore-windows-gnu: #128981 //@ compile-flags:-g diff --git a/tests/debuginfo/pretty-uninitialized-vec.rs b/tests/debuginfo/pretty-uninitialized-vec.rs index 5206ff23fd5ba..ddb751161d8ce 100644 --- a/tests/debuginfo/pretty-uninitialized-vec.rs +++ b/tests/debuginfo/pretty-uninitialized-vec.rs @@ -1,4 +1,4 @@ -//@ ignore-windows failing on win32 bot +//@ ignore-windows-gnu: #128981 //@ ignore-android: FIXME(#10381) //@ compile-flags:-g //@ min-gdb-version: 8.1 diff --git a/tests/debuginfo/thread-names.rs b/tests/debuginfo/thread-names.rs index 6b3b0c7f4b231..265b9271cf306 100644 --- a/tests/debuginfo/thread-names.rs +++ b/tests/debuginfo/thread-names.rs @@ -4,7 +4,7 @@ //@[macos] only-macos //@[win] only-windows //@ ignore-sgx -//@ ignore-windows-gnu +//@ ignore-windows-gnu: gdb on windows-gnu does not print thread names // === GDB TESTS ================================================================================== // From 3c8dad152bc1e3baca25e5b7b6b7ea6eccef060a Mon Sep 17 00:00:00 2001 From: Shina <53410646+s7tya@users.noreply.github.com> Date: Sat, 17 Aug 2024 15:03:20 +0900 Subject: [PATCH 15/16] Emit an error for invalid use of the linkage attribute --- compiler/rustc_passes/messages.ftl | 4 ++ compiler/rustc_passes/src/check_attr.rs | 15 ++++++- compiler/rustc_passes/src/errors.rs | 9 ++++ tests/ui/attributes/linkage.rs | 42 +++++++++++++++++++ tests/ui/attributes/linkage.stderr | 55 +++++++++++++++++++++++++ 5 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 tests/ui/attributes/linkage.rs create mode 100644 tests/ui/attributes/linkage.stderr diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index 59c9d1e49f5b3..86ce552ad16d9 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -427,6 +427,10 @@ passes_link_section = .warn = {-passes_previously_accepted} .label = not a function or static +passes_linkage = + attribute should be applied to a function or static + .label = not a function definition or static + passes_macro_export = `#[macro_export]` only has an effect on macro definitions diff --git a/compiler/rustc_passes/src/check_attr.rs b/compiler/rustc_passes/src/check_attr.rs index c8d4c190113b2..e16509261404f 100644 --- a/compiler/rustc_passes/src/check_attr.rs +++ b/compiler/rustc_passes/src/check_attr.rs @@ -243,6 +243,7 @@ impl<'tcx> CheckAttrVisitor<'tcx> { [sym::coroutine, ..] => { self.check_coroutine(attr, target); } + [sym::linkage, ..] => self.check_linkage(attr, span, target), [ // ok sym::allow @@ -256,7 +257,6 @@ impl<'tcx> CheckAttrVisitor<'tcx> { | sym::cfi_encoding // FIXME(cfi_encoding) | sym::may_dangle // FIXME(dropck_eyepatch) | sym::pointee // FIXME(derive_smart_pointer) - | sym::linkage // FIXME(linkage) | sym::omit_gdb_pretty_printer_section // FIXME(omit_gdb_pretty_printer_section) | sym::used // handled elsewhere to restrict to static items | sym::repr // handled elsewhere to restrict to type decls items @@ -2349,6 +2349,19 @@ impl<'tcx> CheckAttrVisitor<'tcx> { } } } + + fn check_linkage(&self, attr: &Attribute, span: Span, target: Target) { + match target { + Target::Fn + | Target::Method(..) + | Target::Static + | Target::ForeignStatic + | Target::ForeignFn => {} + _ => { + self.dcx().emit_err(errors::Linkage { attr_span: attr.span, span }); + } + } + } } impl<'tcx> Visitor<'tcx> for CheckAttrVisitor<'tcx> { diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 36dfc40e7628b..3a043e0e3c196 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -643,6 +643,15 @@ pub struct CoroutineOnNonClosure { pub span: Span, } +#[derive(Diagnostic)] +#[diag(passes_linkage)] +pub struct Linkage { + #[primary_span] + pub attr_span: Span, + #[label] + pub span: Span, +} + #[derive(Diagnostic)] #[diag(passes_empty_confusables)] pub(crate) struct EmptyConfusables { diff --git a/tests/ui/attributes/linkage.rs b/tests/ui/attributes/linkage.rs new file mode 100644 index 0000000000000..0d5ce699fa808 --- /dev/null +++ b/tests/ui/attributes/linkage.rs @@ -0,0 +1,42 @@ +#![feature(linkage)] +#![feature(stmt_expr_attributes)] +#![deny(unused_attributes)] +#![allow(dead_code)] + +#[linkage = "weak"] //~ ERROR attribute should be applied to a function or static +type InvalidTy = (); + +#[linkage = "weak"] //~ ERROR attribute should be applied to a function or static +mod invalid_module {} + +#[linkage = "weak"] //~ ERROR attribute should be applied to a function or static +struct F; + +#[linkage = "weak"] //~ ERROR attribute should be applied to a function or static +impl F { + #[linkage = "weak"] + fn valid(&self) {} +} + +#[linkage = "weak"] +fn f() { + #[linkage = "weak"] + { + 1 + }; + //~^^^^ ERROR attribute should be applied to a function or static +} + +extern "C" { + #[linkage = "weak"] + static A: *const (); + + #[linkage = "weak"] + fn bar(); +} + +fn main() { + let _ = #[linkage = "weak"] + (|| 1); + //~^^ ERROR attribute should be applied to a function or static +} diff --git a/tests/ui/attributes/linkage.stderr b/tests/ui/attributes/linkage.stderr new file mode 100644 index 0000000000000..d5595529f400a --- /dev/null +++ b/tests/ui/attributes/linkage.stderr @@ -0,0 +1,55 @@ +error: attribute should be applied to a function or static + --> $DIR/linkage.rs:6:1 + | +LL | #[linkage = "weak"] + | ^^^^^^^^^^^^^^^^^^^ +LL | type InvalidTy = (); + | -------------------- not a function definition or static + +error: attribute should be applied to a function or static + --> $DIR/linkage.rs:9:1 + | +LL | #[linkage = "weak"] + | ^^^^^^^^^^^^^^^^^^^ +LL | mod invalid_module {} + | --------------------- not a function definition or static + +error: attribute should be applied to a function or static + --> $DIR/linkage.rs:12:1 + | +LL | #[linkage = "weak"] + | ^^^^^^^^^^^^^^^^^^^ +LL | struct F; + | --------- not a function definition or static + +error: attribute should be applied to a function or static + --> $DIR/linkage.rs:15:1 + | +LL | #[linkage = "weak"] + | ^^^^^^^^^^^^^^^^^^^ +LL | / impl F { +LL | | #[linkage = "weak"] +LL | | fn valid(&self) {} +LL | | } + | |_- not a function definition or static + +error: attribute should be applied to a function or static + --> $DIR/linkage.rs:23:5 + | +LL | #[linkage = "weak"] + | ^^^^^^^^^^^^^^^^^^^ +LL | / { +LL | | 1 +LL | | }; + | |_____- not a function definition or static + +error: attribute should be applied to a function or static + --> $DIR/linkage.rs:39:13 + | +LL | let _ = #[linkage = "weak"] + | ^^^^^^^^^^^^^^^^^^^ +LL | (|| 1); + | ------ not a function definition or static + +error: aborting due to 6 previous errors + From 3116db669c4e80ffdae5a34a8dd9bcfba4b1a9be Mon Sep 17 00:00:00 2001 From: Zalathar Date: Sat, 17 Aug 2024 17:56:00 +1000 Subject: [PATCH 16/16] Port `run-make/libtest-json/validate_json.py` to Rust This is a trivial Python script that simply tries to parse each line of stdin (i.e. the test process output) as JSON, to verify that the overall output is JSON Lines. We can perform the same check directly in `rmake.rs` using `serde_json`. --- tests/run-make/libtest-json/rmake.rs | 15 +++++++++++++-- tests/run-make/libtest-json/validate_json.py | 8 -------- 2 files changed, 13 insertions(+), 10 deletions(-) delete mode 100755 tests/run-make/libtest-json/validate_json.py diff --git a/tests/run-make/libtest-json/rmake.rs b/tests/run-make/libtest-json/rmake.rs index acbd88dc46cb9..c31f4a79b641f 100644 --- a/tests/run-make/libtest-json/rmake.rs +++ b/tests/run-make/libtest-json/rmake.rs @@ -3,7 +3,7 @@ //@ ignore-cross-compile //@ needs-unwind (test file contains #[should_panic] test) -use run_make_support::{cmd, diff, python_command, rustc}; +use run_make_support::{cmd, diff, rustc, serde_json}; fn main() { rustc().arg("--test").input("f.rs").run(); @@ -21,7 +21,18 @@ fn run_tests(extra_args: &[&str], expected_file: &str) { .run_fail(); let test_stdout = &cmd_out.stdout_utf8(); - python_command().arg("validate_json.py").stdin(test_stdout).run(); + // Verify that the test process output is JSON Lines, i.e. each line is valid JSON. + for (line, n) in test_stdout.lines().zip(1..) { + if let Err(e) = serde_json::from_str::(line) { + panic!( + "could not parse JSON on line {n}: {e}\n\ + \n\ + === STDOUT ===\n\ + {test_stdout}\ + ==============" + ); + } + } diff() .expected_file(expected_file) diff --git a/tests/run-make/libtest-json/validate_json.py b/tests/run-make/libtest-json/validate_json.py deleted file mode 100755 index 657f732f2bffd..0000000000000 --- a/tests/run-make/libtest-json/validate_json.py +++ /dev/null @@ -1,8 +0,0 @@ -#!/usr/bin/env python - -import sys -import json - -# Try to decode line in order to ensure it is a valid JSON document -for line in sys.stdin: - json.loads(line)