From 012a458dcabc6438ee070c074435860690475370 Mon Sep 17 00:00:00 2001 From: Gurinder Singh Date: Mon, 13 May 2024 17:42:44 +0530 Subject: [PATCH 01/33] Suggest removing unused tuple fields if they are the last fields --- compiler/rustc_passes/messages.ftl | 9 +++ compiler/rustc_passes/src/dead.rs | 58 ++++++++++++++----- compiler/rustc_passes/src/errors.rs | 19 ++++-- tests/ui/lint/dead-code/tuple-struct-field.rs | 31 ++++++---- .../lint/dead-code/tuple-struct-field.stderr | 37 +++++++----- 5 files changed, 109 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_passes/messages.ftl b/compiler/rustc_passes/messages.ftl index a545c170297de..70d314913d2a3 100644 --- a/compiler/rustc_passes/messages.ftl +++ b/compiler/rustc_passes/messages.ftl @@ -597,6 +597,15 @@ passes_pass_by_value = passes_proc_macro_bad_sig = {$kind} has incorrect signature +passes_remove_fields = + consider removing { $num -> + [one] this + *[other] these + } { $num -> + [one] field + *[other] fields + } + passes_repr_conflicting = conflicting representation hints diff --git a/compiler/rustc_passes/src/dead.rs b/compiler/rustc_passes/src/dead.rs index 51f288b3c9595..f977f90e8acec 100644 --- a/compiler/rustc_passes/src/dead.rs +++ b/compiler/rustc_passes/src/dead.rs @@ -23,8 +23,7 @@ use rustc_target::abi::FieldIdx; use std::mem; use crate::errors::{ - ChangeFieldsToBeOfUnitType, IgnoredDerivedImpls, MultipleDeadCodes, ParentInfo, - UselessAssignment, + ChangeFields, IgnoredDerivedImpls, MultipleDeadCodes, ParentInfo, UselessAssignment, }; // Any local node that may call something in its body block should be @@ -993,17 +992,50 @@ impl<'tcx> DeadVisitor<'tcx> { }; let diag = match report_on { - ReportOn::TupleField => MultipleDeadCodes::UnusedTupleStructFields { - multiple, - num, - descr, - participle, - name_list, - change_fields_suggestion: ChangeFieldsToBeOfUnitType { num, spans: spans.clone() }, - parent_info, - ignored_derived_impls, - }, - + ReportOn::TupleField => { + let tuple_fields = if let Some(parent_id) = parent_item + && let node = tcx.hir_node_by_def_id(parent_id) + && let hir::Node::Item(hir::Item { + kind: hir::ItemKind::Struct(hir::VariantData::Tuple(fields, _, _), _), + .. + }) = node + { + *fields + } else { + &[] + }; + + let trailing_tuple_fields = if tuple_fields.len() >= dead_codes.len() { + LocalDefIdSet::from_iter( + tuple_fields + .iter() + .skip(tuple_fields.len() - dead_codes.len()) + .map(|f| f.def_id), + ) + } else { + LocalDefIdSet::default() + }; + + let fields_suggestion = + // Suggest removal if all tuple fields are at the end. + // Otherwise suggest removal or changing to unit type + if dead_codes.iter().all(|dc| trailing_tuple_fields.contains(&dc.def_id)) { + ChangeFields::Remove { num } + } else { + ChangeFields::ChangeToUnitTypeOrRemove { num, spans: spans.clone() } + }; + + MultipleDeadCodes::UnusedTupleStructFields { + multiple, + num, + descr, + participle, + name_list, + change_fields_suggestion: fields_suggestion, + parent_info, + ignored_derived_impls, + } + } ReportOn::NamedField => MultipleDeadCodes::DeadCodes { multiple, num, diff --git a/compiler/rustc_passes/src/errors.rs b/compiler/rustc_passes/src/errors.rs index 03a607348e88c..a590ef8f3a1e2 100644 --- a/compiler/rustc_passes/src/errors.rs +++ b/compiler/rustc_passes/src/errors.rs @@ -1589,7 +1589,7 @@ pub enum MultipleDeadCodes<'tcx> { participle: &'tcx str, name_list: DiagSymbolList, #[subdiagnostic] - change_fields_suggestion: ChangeFieldsToBeOfUnitType, + change_fields_suggestion: ChangeFields, #[subdiagnostic] parent_info: Option>, #[subdiagnostic] @@ -1616,11 +1616,18 @@ pub struct IgnoredDerivedImpls { } #[derive(Subdiagnostic)] -#[multipart_suggestion(passes_change_fields_to_be_of_unit_type, applicability = "has-placeholders")] -pub struct ChangeFieldsToBeOfUnitType { - pub num: usize, - #[suggestion_part(code = "()")] - pub spans: Vec, +pub enum ChangeFields { + #[multipart_suggestion( + passes_change_fields_to_be_of_unit_type, + applicability = "has-placeholders" + )] + ChangeToUnitTypeOrRemove { + num: usize, + #[suggestion_part(code = "()")] + spans: Vec, + }, + #[help(passes_remove_fields)] + Remove { num: usize }, } #[derive(Diagnostic)] diff --git a/tests/ui/lint/dead-code/tuple-struct-field.rs b/tests/ui/lint/dead-code/tuple-struct-field.rs index d13fe029289ad..ff3da4105000d 100644 --- a/tests/ui/lint/dead-code/tuple-struct-field.rs +++ b/tests/ui/lint/dead-code/tuple-struct-field.rs @@ -5,15 +5,20 @@ use std::marker::PhantomData; const LEN: usize = 4; -struct SingleUnused(i32, [u8; LEN], String); -//~^ ERROR: field `1` is never read +struct UnusedAtTheEnd(i32, f32, [u8; LEN], String, u8); +//~^ ERROR:fields `1`, `2`, `3`, and `4` are never read +//~| NOTE: fields in this struct +//~| HELP: consider removing these fields + +struct UnusedJustOneField(i32); +//~^ ERROR: field `0` is never read //~| NOTE: field in this struct -//~| HELP: consider changing the field to be of unit type +//~| HELP: consider removing this field -struct MultipleUnused(i32, f32, String, u8); -//~^ ERROR: fields `0`, `1`, `2`, and `3` are never read +struct UnusedInTheMiddle(i32, f32, String, u8, u32); +//~^ ERROR: fields `1`, `2`, and `4` are never read //~| NOTE: fields in this struct -//~| HELP: consider changing the fields to be of unit type +//~| HELP: consider changing the fields to be of unit type to suppress this warning while preserving the field numbering, or remove the fields struct GoodUnit(()); @@ -23,15 +28,19 @@ struct Void; struct GoodVoid(Void); fn main() { - let w = SingleUnused(42, [0, 1, 2, 3], "abc".to_string()); - let _ = w.0; - let _ = w.2; + let u1 = UnusedAtTheEnd(42, 3.14, [0, 1, 2, 3], "def".to_string(), 4u8); + let _ = u1.0; + + let _ = UnusedJustOneField(42); + + let u2 = UnusedInTheMiddle(42, 3.14, "def".to_string(), 4u8, 5); + let _ = u2.0; + let _ = u2.3; - let m = MultipleUnused(42, 3.14, "def".to_string(), 4u8); let gu = GoodUnit(()); let gp = GoodPhantom(PhantomData); let gv = GoodVoid(Void); - let _ = (gu, gp, gv, m); + let _ = (gu, gp, gv); } diff --git a/tests/ui/lint/dead-code/tuple-struct-field.stderr b/tests/ui/lint/dead-code/tuple-struct-field.stderr index 0154d5489f9fb..434554d7ae5ee 100644 --- a/tests/ui/lint/dead-code/tuple-struct-field.stderr +++ b/tests/ui/lint/dead-code/tuple-struct-field.stderr @@ -1,33 +1,40 @@ -error: field `1` is never read - --> $DIR/tuple-struct-field.rs:8:26 +error: fields `1`, `2`, `3`, and `4` are never read + --> $DIR/tuple-struct-field.rs:8:28 | -LL | struct SingleUnused(i32, [u8; LEN], String); - | ------------ ^^^^^^^^^ +LL | struct UnusedAtTheEnd(i32, f32, [u8; LEN], String, u8); + | -------------- ^^^ ^^^^^^^^^ ^^^^^^ ^^ | | - | field in this struct + | fields in this struct | + = help: consider removing these fields note: the lint level is defined here --> $DIR/tuple-struct-field.rs:1:9 | LL | #![deny(dead_code)] | ^^^^^^^^^ -help: consider changing the field to be of unit type to suppress this warning while preserving the field numbering, or remove the field + +error: field `0` is never read + --> $DIR/tuple-struct-field.rs:13:27 + | +LL | struct UnusedJustOneField(i32); + | ------------------ ^^^ + | | + | field in this struct | -LL | struct SingleUnused(i32, (), String); - | ~~ + = help: consider removing this field -error: fields `0`, `1`, `2`, and `3` are never read - --> $DIR/tuple-struct-field.rs:13:23 +error: fields `1`, `2`, and `4` are never read + --> $DIR/tuple-struct-field.rs:18:31 | -LL | struct MultipleUnused(i32, f32, String, u8); - | -------------- ^^^ ^^^ ^^^^^^ ^^ +LL | struct UnusedInTheMiddle(i32, f32, String, u8, u32); + | ----------------- ^^^ ^^^^^^ ^^^ | | | fields in this struct | help: consider changing the fields to be of unit type to suppress this warning while preserving the field numbering, or remove the fields | -LL | struct MultipleUnused((), (), (), ()); - | ~~ ~~ ~~ ~~ +LL | struct UnusedInTheMiddle(i32, (), (), u8, ()); + | ~~ ~~ ~~ -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors From 1e9cd07c9cab4f2ce167519693546f0dd1d8700f Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Mon, 27 May 2024 11:49:05 +0000 Subject: [PATCH 02/33] Allow constraining opaque types during subtyping in the trait system --- compiler/rustc_infer/src/infer/mod.rs | 4 +- .../impl-trait/lazy_subtyping_of_opaques.rs | 67 +++++-------------- .../lazy_subtyping_of_opaques.stderr | 21 ------ .../lazy_subtyping_of_opaques.rs | 3 +- .../lazy_subtyping_of_opaques.stderr | 31 +++++---- 5 files changed, 38 insertions(+), 88 deletions(-) delete mode 100644 tests/ui/impl-trait/lazy_subtyping_of_opaques.stderr diff --git a/compiler/rustc_infer/src/infer/mod.rs b/compiler/rustc_infer/src/infer/mod.rs index 6f603d9b612eb..ba7e4f04c2134 100644 --- a/compiler/rustc_infer/src/infer/mod.rs +++ b/compiler/rustc_infer/src/infer/mod.rs @@ -962,9 +962,9 @@ impl<'tcx> InferCtxt<'tcx> { self.enter_forall(predicate, |ty::SubtypePredicate { a_is_expected, a, b }| { if a_is_expected { - Ok(self.at(cause, param_env).sub(DefineOpaqueTypes::No, a, b)) + Ok(self.at(cause, param_env).sub(DefineOpaqueTypes::Yes, a, b)) } else { - Ok(self.at(cause, param_env).sup(DefineOpaqueTypes::No, b, a)) + Ok(self.at(cause, param_env).sup(DefineOpaqueTypes::Yes, b, a)) } }) } diff --git a/tests/ui/impl-trait/lazy_subtyping_of_opaques.rs b/tests/ui/impl-trait/lazy_subtyping_of_opaques.rs index 65331894725a8..8fd1f35645a8a 100644 --- a/tests/ui/impl-trait/lazy_subtyping_of_opaques.rs +++ b/tests/ui/impl-trait/lazy_subtyping_of_opaques.rs @@ -2,58 +2,23 @@ //! No hidden types are being constrained in the subtyping predicate, but type and //! lifetime variables get subtyped in the generic parameter list of the opaque. -use std::iter; - -mod either { - pub enum Either { - Left(L), - Right(R), - } - - impl> Iterator for Either { - type Item = L::Item; - fn next(&mut self) -> Option { - todo!() - } - } - pub use self::Either::{Left, Right}; -} - -pub enum BabeConsensusLogRef<'a> { - NextEpochData(BabeNextEpochRef<'a>), - NextConfigData, -} - -impl<'a> BabeConsensusLogRef<'a> { - pub fn scale_encoding( - &self, - ) -> impl Iterator + Clone + 'a> + Clone + 'a { - //~^ ERROR is not satisfied - //~| ERROR is not satisfied - //~| ERROR is not satisfied - match self { - BabeConsensusLogRef::NextEpochData(digest) => either::Left(either::Left( - digest.scale_encoding().map(either::Left).map(either::Left), - )), - BabeConsensusLogRef::NextConfigData => either::Right( - // The Opaque type from ``scale_encoding` gets used opaquely here, while the `R` - // generic parameter of `Either` contains type variables that get subtyped and the - // opaque type contains lifetime variables that get subtyped. - iter::once(either::Right(either::Left([1]))) - .chain(std::iter::once([1]).map(either::Right).map(either::Right)), - ), - } - } -} - -pub struct BabeNextEpochRef<'a>(&'a ()); - -impl<'a> BabeNextEpochRef<'a> { - pub fn scale_encoding( - &self, - ) -> impl Iterator + Clone + 'a> + Clone + 'a { - std::iter::once([1]) +//@ check-pass + +fn foo() -> impl Default + Copy { + if false { + let x = Default::default(); + // add `Subtype(?x, ?y)` obligation + let y = x; + + // Make a tuple `(?x, ?y)` and equate it with `(impl Default, u32)`. + // For us to try and prove a `Subtype(impl Default, u32)` obligation, + // we have to instantiate both `?x` and `?y` without any + // `select_where_possible` calls inbetween. + let mut tup = &mut (x, y); + let assign_tup = &mut (foo(), 1u32); + tup = assign_tup; } + 1u32 } fn main() {} diff --git a/tests/ui/impl-trait/lazy_subtyping_of_opaques.stderr b/tests/ui/impl-trait/lazy_subtyping_of_opaques.stderr deleted file mode 100644 index 2f8c957c2c7dc..0000000000000 --- a/tests/ui/impl-trait/lazy_subtyping_of_opaques.stderr +++ /dev/null @@ -1,21 +0,0 @@ -error[E0277]: the trait bound `Either + Clone + '_> + Clone + '_, fn(impl AsRef<[u8]> + Clone + '_) -> Either + Clone + '_, _> {Either:: + Clone + '_, _>::Left}>, fn(Either + Clone + '_, _>) -> Either + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>> {Either:: + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>>::Left}>, _>, std::iter::Chain + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>>>, Map, fn([{integer}; 1]) -> Either<[{integer}; 1], [{integer}; 1]> {Either::<[{integer}; 1], [{integer}; 1]>::Right}>, fn(Either<[{integer}; 1], [{integer}; 1]>) -> Either + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>> {Either:: + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>>::Right}>>>: Clone` is not satisfied - --> $DIR/lazy_subtyping_of_opaques.rs:30:10 - | -LL | ) -> impl Iterator + Clone + 'a> + Clone + 'a { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `Either + Clone + '_> + Clone + '_, fn(impl AsRef<[u8]> + Clone + '_) -> Either + Clone + '_, _> {Either:: + Clone + '_, _>::Left}>, fn(Either + Clone + '_, _>) -> Either + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>> {Either:: + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>>::Left}>, _>, std::iter::Chain + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>>>, Map, fn([{integer}; 1]) -> Either<[{integer}; 1], [{integer}; 1]> {Either::<[{integer}; 1], [{integer}; 1]>::Right}>, fn(Either<[{integer}; 1], [{integer}; 1]>) -> Either + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>> {Either:: + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>>::Right}>>>` - -error[E0277]: the trait bound `Either + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>>: AsRef<[u8]>` is not satisfied - --> $DIR/lazy_subtyping_of_opaques.rs:30:31 - | -LL | ) -> impl Iterator + Clone + 'a> + Clone + 'a { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `AsRef<[u8]>` is not implemented for `Either + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>>` - -error[E0277]: the trait bound `Either + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>>: Clone` is not satisfied - --> $DIR/lazy_subtyping_of_opaques.rs:30:31 - | -LL | ) -> impl Iterator + Clone + 'a> + Clone + 'a { - | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `Clone` is not implemented for `Either + Clone + '_, _>, Either<[{integer}; 1], [{integer}; 1]>>` - -error: aborting due to 3 previous errors - -For more information about this error, try `rustc --explain E0277`. diff --git a/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.rs b/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.rs index 72a90287e374f..1bc352041a57d 100644 --- a/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.rs +++ b/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.rs @@ -7,9 +7,10 @@ type Tait = impl FnOnce() -> (); fn reify_as_tait() -> Thunk { + //~^ ERROR: expected a `FnOnce()` closure, found `()` Thunk::new(|cont| cont) //~^ ERROR: mismatched types - //~| ERROR: mismatched types + //~| ERROR: expected a `FnOnce()` closure, found `()` } struct Thunk(F); diff --git a/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.stderr b/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.stderr index 5a35dc27446cb..7d95d707ca7f5 100644 --- a/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.stderr +++ b/tests/ui/type-alias-impl-trait/lazy_subtyping_of_opaques.stderr @@ -1,26 +1,31 @@ -error[E0308]: mismatched types - --> $DIR/lazy_subtyping_of_opaques.rs:10:23 +error[E0277]: expected a `FnOnce()` closure, found `()` + --> $DIR/lazy_subtyping_of_opaques.rs:11:23 | -LL | type Tait = impl FnOnce() -> (); - | ------------------- the found opaque type -... LL | Thunk::new(|cont| cont) - | ^^^^ expected `()`, found opaque type + | ^^^^ expected an `FnOnce()` closure, found `()` | - = note: expected unit type `()` - found opaque type `Tait` + = help: the trait `FnOnce<()>` is not implemented for `()` + = note: wrap the `()` in a closure with no arguments: `|| { /* code */ }` -error[E0308]: mismatched types - --> $DIR/lazy_subtyping_of_opaques.rs:10:5 +error[E0277]: expected a `FnOnce()` closure, found `()` + --> $DIR/lazy_subtyping_of_opaques.rs:9:23 | LL | fn reify_as_tait() -> Thunk { - | ----------- expected `Thunk<_>` because of return type + | ^^^^^^^^^^^ expected an `FnOnce()` closure, found `()` + | + = help: the trait `FnOnce<()>` is not implemented for `()` + = note: wrap the `()` in a closure with no arguments: `|| { /* code */ }` + +error[E0308]: mismatched types + --> $DIR/lazy_subtyping_of_opaques.rs:11:5 + | LL | Thunk::new(|cont| cont) | ^^^^^^^^^^^^^^^^^^^^^^^ expected `Thunk<_>`, found `()` | = note: expected struct `Thunk<_>` found unit type `()` -error: aborting due to 2 previous errors +error: aborting due to 3 previous errors -For more information about this error, try `rustc --explain E0308`. +Some errors have detailed explanations: E0277, E0308. +For more information about an error, try `rustc --explain E0277`. From 586821eacd7389a4a30b60633ceb53596d06e238 Mon Sep 17 00:00:00 2001 From: bohan Date: Sat, 1 Jun 2024 17:43:48 +0800 Subject: [PATCH 03/33] tip for inaccessible traits --- .../rustc_hir_typeck/src/method/suggest.rs | 185 +++++++++++++----- tests/ui/traits/item-privacy.stderr | 15 +- 2 files changed, 143 insertions(+), 57 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/method/suggest.rs b/compiler/rustc_hir_typeck/src/method/suggest.rs index daaaf630f2c45..e2b60bd52b267 100644 --- a/compiler/rustc_hir_typeck/src/method/suggest.rs +++ b/compiler/rustc_hir_typeck/src/method/suggest.rs @@ -284,14 +284,57 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } if !candidates.is_empty() { let help = format!( - "{an}other candidate{s} {were} found in the following trait{s}, perhaps \ - add a `use` for {one_of_them}:", + "{an}other candidate{s} {were} found in the following trait{s}", an = if candidates.len() == 1 { "an" } else { "" }, s = pluralize!(candidates.len()), were = pluralize!("was", candidates.len()), - one_of_them = if candidates.len() == 1 { "it" } else { "one_of_them" }, ); - self.suggest_use_candidates(&mut err, help, candidates); + self.suggest_use_candidates( + candidates, + |accessible_sugg, inaccessible_sugg, span| { + let suggest_for_access = + |err: &mut Diag<'_>, mut msg: String, sugg: Vec<_>| { + msg += &format!( + ", perhaps add a `use` for {one_of_them}:", + one_of_them = + if sugg.len() == 1 { "it" } else { "one_of_them" }, + ); + err.span_suggestions( + span, + msg, + sugg, + Applicability::MaybeIncorrect, + ); + }; + let suggest_for_privacy = + |err: &mut Diag<'_>, mut msg: String, sugg: Vec| { + if sugg.len() == 1 { + let msg = format!("\ + trait `{}` provides `{item_name}` is implemented but not reachable", + sugg[0].trim() + ); + err.help(msg); + } else { + msg += &format!(" but {} not reachable", pluralize!("is", sugg.len())); + err.span_suggestions( + span, + msg, + sugg, + Applicability::MaybeIncorrect, + ); + } + }; + if accessible_sugg.is_empty() { + // `inaccessible_sugg` must not be empty + suggest_for_privacy(&mut err, help, inaccessible_sugg); + } else if inaccessible_sugg.is_empty() { + suggest_for_access(&mut err, help, accessible_sugg); + } else { + suggest_for_access(&mut err, help.clone(), accessible_sugg); + suggest_for_privacy(&mut err, help, inaccessible_sugg); + } + }, + ); } if let ty::Ref(region, t_type, mutability) = rcvr_ty.kind() { if needs_mut { @@ -3069,49 +3112,69 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } } - fn suggest_use_candidates(&self, err: &mut Diag<'_>, msg: String, candidates: Vec) { + fn suggest_use_candidates(&self, candidates: Vec, handle_candidates: F) + where + F: FnOnce(Vec, Vec, Span), + { let parent_map = self.tcx.visible_parent_map(()); - // Separate out candidates that must be imported with a glob, because they are named `_` - // and cannot be referred with their identifier. - let (candidates, globs): (Vec<_>, Vec<_>) = candidates.into_iter().partition(|trait_did| { - if let Some(parent_did) = parent_map.get(trait_did) { - // If the item is re-exported as `_`, we should suggest a glob-import instead. - if *parent_did != self.tcx.parent(*trait_did) - && self - .tcx - .module_children(*parent_did) - .iter() - .filter(|child| child.res.opt_def_id() == Some(*trait_did)) - .all(|child| child.ident.name == kw::Underscore) - { - return false; - } - } + let scope = self.tcx.parent_module_from_def_id(self.body_id); + let (accessible_candidates, inaccessible_candidates): (Vec<_>, Vec<_>) = + candidates.into_iter().partition(|id| { + let vis = self.tcx.visibility(*id); + vis.is_accessible_from(scope, self.tcx) + }); - true - }); + let sugg = |candidates: Vec<_>, visible| { + // Separate out candidates that must be imported with a glob, because they are named `_` + // and cannot be referred with their identifier. + let (candidates, globs): (Vec<_>, Vec<_>) = + candidates.into_iter().partition(|trait_did| { + if let Some(parent_did) = parent_map.get(trait_did) { + // If the item is re-exported as `_`, we should suggest a glob-import instead. + if *parent_did != self.tcx.parent(*trait_did) + && self + .tcx + .module_children(*parent_did) + .iter() + .filter(|child| child.res.opt_def_id() == Some(*trait_did)) + .all(|child| child.ident.name == kw::Underscore) + { + return false; + } + } - let module_did = self.tcx.parent_module_from_def_id(self.body_id); - let (module, _, _) = self.tcx.hir().get_module(module_did); - let span = module.spans.inject_use_span; + true + }); - let path_strings = candidates.iter().map(|trait_did| { - format!("use {};\n", with_crate_prefix!(self.tcx.def_path_str(*trait_did)),) - }); + let prefix = if visible { "use " } else { "" }; + let postfix = if visible { ";" } else { "" }; + let path_strings = candidates.iter().map(|trait_did| { + format!( + "{prefix}{}{postfix}\n", + with_crate_prefix!(self.tcx.def_path_str(*trait_did)), + ) + }); - let glob_path_strings = globs.iter().map(|trait_did| { - let parent_did = parent_map.get(trait_did).unwrap(); - format!( - "use {}::*; // trait {}\n", - with_crate_prefix!(self.tcx.def_path_str(*parent_did)), - self.tcx.item_name(*trait_did), - ) - }); - let mut sugg: Vec<_> = path_strings.chain(glob_path_strings).collect(); - sugg.sort(); + let glob_path_strings = globs.iter().map(|trait_did| { + let parent_did = parent_map.get(trait_did).unwrap(); + format!( + "{prefix}{}::*{postfix} // trait {}\n", + with_crate_prefix!(self.tcx.def_path_str(*parent_did)), + self.tcx.item_name(*trait_did), + ) + }); + let mut sugg: Vec<_> = path_strings.chain(glob_path_strings).collect(); + sugg.sort(); + sugg + }; - err.span_suggestions(span, msg, sugg, Applicability::MaybeIncorrect); + let accessible_sugg = sugg(accessible_candidates, true); + let inaccessible_sugg = sugg(inaccessible_candidates, false); + + let (module, _, _) = self.tcx.hir().get_module(scope); + let span = module.spans.inject_use_span; + handle_candidates(accessible_sugg, inaccessible_sugg, span); } fn suggest_valid_traits( @@ -3135,9 +3198,9 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { if explain { err.help("items from traits can only be used if the trait is in scope"); } + let msg = format!( - "{this_trait_is} implemented but not in scope; perhaps you want to import \ - {one_of_them}", + "{this_trait_is} implemented but not in scope", this_trait_is = if candidates.len() == 1 { format!( "trait `{}` which provides `{item_name}` is", @@ -3145,11 +3208,43 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { ) } else { format!("the following traits which provide `{item_name}` are") - }, - one_of_them = if candidates.len() == 1 { "it" } else { "one of them" }, + } ); - self.suggest_use_candidates(err, msg, candidates); + self.suggest_use_candidates(candidates, |accessible_sugg, inaccessible_sugg, span| { + let suggest_for_access = |err: &mut Diag<'_>, mut msg: String, sugg: Vec<_>| { + msg += &format!( + "; perhaps you want to import {one_of}", + one_of = if sugg.len() == 1 { "it" } else { "one of them" }, + ); + err.span_suggestions(span, msg, sugg, Applicability::MaybeIncorrect); + }; + let suggest_for_privacy = |err: &mut Diag<'_>, sugg: Vec| { + let msg = format!( + "{this_trait_is} implemented but not reachable", + this_trait_is = if sugg.len() == 1 { + format!("trait `{}` which provides `{item_name}` is", sugg[0].trim()) + } else { + format!("the following traits which provide `{item_name}` are") + } + ); + if sugg.len() == 1 { + err.help(msg); + } else { + err.span_suggestions(span, msg, sugg, Applicability::MaybeIncorrect); + } + }; + if accessible_sugg.is_empty() { + // `inaccessible_sugg` must not be empty + suggest_for_privacy(err, inaccessible_sugg); + } else if inaccessible_sugg.is_empty() { + suggest_for_access(err, msg, accessible_sugg); + } else { + suggest_for_access(err, msg, accessible_sugg); + suggest_for_privacy(err, inaccessible_sugg); + } + }); + if let Some(did) = edition_fix { err.note(format!( "'{}' is included in the prelude starting in Edition 2021", diff --git a/tests/ui/traits/item-privacy.stderr b/tests/ui/traits/item-privacy.stderr index d08bb4745bf57..fca7473f3bc30 100644 --- a/tests/ui/traits/item-privacy.stderr +++ b/tests/ui/traits/item-privacy.stderr @@ -8,10 +8,7 @@ LL | S.a(); | ^ | = help: items from traits can only be used if the trait is implemented and in scope -help: trait `A` which provides `a` is implemented but not in scope; perhaps you want to import it - | -LL + use method::A; - | + = help: trait `method::A` which provides `a` is implemented but not reachable help: there is a method `b` with a similar name | LL | S.b(); @@ -58,15 +55,12 @@ LL | S::a(&S); | ^ function or associated item not found in `S` | = help: items from traits can only be used if the trait is implemented and in scope + = help: trait `method::A` which provides `a` is implemented but not reachable help: there is an associated constant `B` with a similar name --> $DIR/item-privacy.rs:29:9 | LL | const B: u8 = 0; | ^^^^^^^^^^^ -help: trait `A` which provides `a` is implemented but not in scope; perhaps you want to import it - | -LL + use method::A; - | error[E0599]: no function or associated item named `b` found for struct `S` in the current scope --> $DIR/item-privacy.rs:80:8 @@ -107,10 +101,7 @@ LL | S::A; | ^ associated item not found in `S` | = help: items from traits can only be used if the trait is implemented and in scope -help: trait `A` which provides `A` is implemented but not in scope; perhaps you want to import it - | -LL + use assoc_const::A; - | + = help: trait `assoc_const::A` which provides `A` is implemented but not reachable help: there is an associated constant `B` with a similar name | LL | S::B; From 05b7b46e65207bdc33bd86c4ffda3516059e4de9 Mon Sep 17 00:00:00 2001 From: Lin Yihai Date: Mon, 13 May 2024 22:42:18 +0800 Subject: [PATCH 04/33] fix: break inside async closure has incorrect span for enclosing closure --- compiler/rustc_ast_lowering/src/item.rs | 10 +++++++++- .../async-closures/wrong-fn-kind.stderr | 9 +++++---- .../break-inside-coroutine-issue-124495.rs | 1 + .../break-inside-coroutine-issue-124495.stderr | 14 +++++++------- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/compiler/rustc_ast_lowering/src/item.rs b/compiler/rustc_ast_lowering/src/item.rs index a15449409df3a..44e07c299a5fa 100644 --- a/compiler/rustc_ast_lowering/src/item.rs +++ b/compiler/rustc_ast_lowering/src/item.rs @@ -1311,6 +1311,14 @@ impl<'hir> LoweringContext<'_, 'hir> { CoroutineKind::AsyncGen { .. } => hir::CoroutineDesugaring::AsyncGen, }; let closure_id = coroutine_kind.closure_id(); + + let span = if let FnRetTy::Default(span) = decl.output + && matches!(coroutine_source, rustc_hir::CoroutineSource::Closure) + { + body_span.with_lo(span.lo()) + } else { + body_span + }; let coroutine_expr = self.make_desugared_coroutine_expr( // The default capture mode here is by-ref. Later on during upvar analysis, // we will force the captured arguments to by-move, but for async closures, @@ -1319,7 +1327,7 @@ impl<'hir> LoweringContext<'_, 'hir> { CaptureBy::Ref, closure_id, None, - body_span, + span, desugaring_kind, coroutine_source, mkbody, diff --git a/tests/ui/async-await/async-closures/wrong-fn-kind.stderr b/tests/ui/async-await/async-closures/wrong-fn-kind.stderr index e56389b320273..6f07a6feed35f 100644 --- a/tests/ui/async-await/async-closures/wrong-fn-kind.stderr +++ b/tests/ui/async-await/async-closures/wrong-fn-kind.stderr @@ -20,15 +20,16 @@ LL | fn needs_async_fn(_: impl async Fn()) {} | ^^^^^^^^^^ required by this bound in `needs_async_fn` error[E0596]: cannot borrow `x` as mutable, as it is a captured variable in a `Fn` closure - --> $DIR/wrong-fn-kind.rs:9:29 + --> $DIR/wrong-fn-kind.rs:9:20 | LL | fn needs_async_fn(_: impl async Fn()) {} | --------------- change this to accept `FnMut` instead of `Fn` ... LL | needs_async_fn(async || { - | _____--------------_--------_^ - | | | | - | | | in this closure + | -------------- ^------- + | | | + | _____|______________in this closure + | | | | | expects `Fn` instead of `FnMut` LL | | LL | | x += 1; diff --git a/tests/ui/coroutine/break-inside-coroutine-issue-124495.rs b/tests/ui/coroutine/break-inside-coroutine-issue-124495.rs index 5d93db56722bf..97c3d06c023e6 100644 --- a/tests/ui/coroutine/break-inside-coroutine-issue-124495.rs +++ b/tests/ui/coroutine/break-inside-coroutine-issue-124495.rs @@ -18,6 +18,7 @@ async gen fn async_gen_fn() { fn main() { let _ = async { break; }; //~ ERROR `break` inside `async` block + let _ = async || { break; }; //~ ERROR `break` inside `async` closure let _ = gen { break; }; //~ ERROR `break` inside `gen` block diff --git a/tests/ui/coroutine/break-inside-coroutine-issue-124495.stderr b/tests/ui/coroutine/break-inside-coroutine-issue-124495.stderr index a7f37fad35ea8..f030961b7edde 100644 --- a/tests/ui/coroutine/break-inside-coroutine-issue-124495.stderr +++ b/tests/ui/coroutine/break-inside-coroutine-issue-124495.stderr @@ -38,16 +38,16 @@ LL | let _ = async { break; }; | enclosing `async` block error[E0267]: `break` inside `async` closure - --> $DIR/break-inside-coroutine-issue-124495.rs:21:24 + --> $DIR/break-inside-coroutine-issue-124495.rs:22:24 | LL | let _ = async || { break; }; - | --^^^^^--- - | | | - | | cannot `break` inside `async` closure - | enclosing `async` closure + | -----------^^^^^--- + | | | + | | cannot `break` inside `async` closure + | enclosing `async` closure error[E0267]: `break` inside `gen` block - --> $DIR/break-inside-coroutine-issue-124495.rs:23:19 + --> $DIR/break-inside-coroutine-issue-124495.rs:24:19 | LL | let _ = gen { break; }; | ------^^^^^--- @@ -56,7 +56,7 @@ LL | let _ = gen { break; }; | enclosing `gen` block error[E0267]: `break` inside `async gen` block - --> $DIR/break-inside-coroutine-issue-124495.rs:25:25 + --> $DIR/break-inside-coroutine-issue-124495.rs:26:25 | LL | let _ = async gen { break; }; | ------------^^^^^--- From 9b05e154f39f1a413ebc09c8f1dd751f991b3bba Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Sat, 8 Jun 2024 12:04:38 +0200 Subject: [PATCH 05/33] StorageLive: refresh storage (instead of UB) when local is already live --- compiler/rustc_const_eval/messages.ftl | 2 -- .../rustc_const_eval/src/interpret/eval_context.rs | 8 +++----- compiler/rustc_middle/src/mir/syntax.rs | 11 +++++++---- 3 files changed, 10 insertions(+), 11 deletions(-) diff --git a/compiler/rustc_const_eval/messages.ftl b/compiler/rustc_const_eval/messages.ftl index 2dbeb7d5e0ca4..14c036b8df9b5 100644 --- a/compiler/rustc_const_eval/messages.ftl +++ b/compiler/rustc_const_eval/messages.ftl @@ -73,8 +73,6 @@ const_eval_division_by_zero = dividing by zero const_eval_division_overflow = overflow in signed division (dividing MIN by -1) -const_eval_double_storage_live = - StorageLive on a local that was already live const_eval_dyn_call_not_a_method = `dyn` call trying to call something that is not a method diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 7c2100fcbe38a..c1daa2fac338e 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -1103,11 +1103,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { Operand::Immediate(Immediate::Uninit) }); - // StorageLive expects the local to be dead, and marks it live. + // If the local is already live, deallocate its old memory. let old = mem::replace(&mut self.frame_mut().locals[local].value, local_val); - if !matches!(old, LocalValue::Dead) { - throw_ub_custom!(fluent::const_eval_double_storage_live); - } + self.deallocate_local(old)?; Ok(()) } @@ -1121,7 +1119,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { assert!(local != mir::RETURN_PLACE, "Cannot make return place dead"); trace!("{:?} is now dead", local); - // It is entirely okay for this local to be already dead (at least that's how we currently generate MIR) + // If the local is already dead, this is a NOP. let old = mem::replace(&mut self.frame_mut().locals[local].value, LocalValue::Dead); self.deallocate_local(old)?; Ok(()) diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index ebe77a1abfd8b..9855c72197edc 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -361,16 +361,19 @@ pub enum StatementKind<'tcx> { /// At any point during the execution of a function, each local is either allocated or /// unallocated. Except as noted below, all locals except function parameters are initially /// unallocated. `StorageLive` statements cause memory to be allocated for the local while - /// `StorageDead` statements cause the memory to be freed. Using a local in any way (not only - /// reading/writing from it) while it is unallocated is UB. + /// `StorageDead` statements cause the memory to be freed. In other words, + /// `StorageLive`/`StorageDead` act like the heap operations `allocate`/`deallocate`, but for + /// stack-allocated local variables. Using a local in any way (not only reading/writing from it) + /// while it is unallocated is UB. /// /// Some locals have no `StorageLive` or `StorageDead` statements within the entire MIR body. /// These locals are implicitly allocated for the full duration of the function. There is a /// convenience method at `rustc_mir_dataflow::storage::always_storage_live_locals` for /// computing these locals. /// - /// If the local is already allocated, calling `StorageLive` again is UB. However, for an - /// unallocated local an additional `StorageDead` all is simply a nop. + /// If the local is already allocated, calling `StorageLive` again will implicitly free the + /// local and then allocate fresh uninitilized memory. If a local is already deallocated, + /// calling `StorageDead` again is a NOP. StorageLive(Local), /// See `StorageLive` above. From 408c8eb983b2c460e56e1e71db26d6b7e76aa695 Mon Sep 17 00:00:00 2001 From: Ralf Jung Date: Fri, 14 Jun 2024 12:14:32 +0200 Subject: [PATCH 06/33] add Miri tests --- .../miri/tests/fail/storage-live-dead-var.rs | 14 ++++++++++++++ .../tests/fail/storage-live-dead-var.stderr | 15 +++++++++++++++ .../miri/tests/fail/storage-live-resets-var.rs | 17 +++++++++++++++++ .../tests/fail/storage-live-resets-var.stderr | 15 +++++++++++++++ 4 files changed, 61 insertions(+) create mode 100644 src/tools/miri/tests/fail/storage-live-dead-var.rs create mode 100644 src/tools/miri/tests/fail/storage-live-dead-var.stderr create mode 100644 src/tools/miri/tests/fail/storage-live-resets-var.rs create mode 100644 src/tools/miri/tests/fail/storage-live-resets-var.stderr diff --git a/src/tools/miri/tests/fail/storage-live-dead-var.rs b/src/tools/miri/tests/fail/storage-live-dead-var.rs new file mode 100644 index 0000000000000..83ab98d79d1a1 --- /dev/null +++ b/src/tools/miri/tests/fail/storage-live-dead-var.rs @@ -0,0 +1,14 @@ +#![feature(core_intrinsics, custom_mir)] +use std::intrinsics::mir::*; + +#[custom_mir(dialect = "runtime")] +fn main() { + mir! { + let val: i32; + { + val = 42; //~ERROR: accessing a dead local variable + StorageLive(val); // too late... (but needs to be here to make `val` not implicitly live) + Return() + } + } +} diff --git a/src/tools/miri/tests/fail/storage-live-dead-var.stderr b/src/tools/miri/tests/fail/storage-live-dead-var.stderr new file mode 100644 index 0000000000000..ccc77b1c978dc --- /dev/null +++ b/src/tools/miri/tests/fail/storage-live-dead-var.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: accessing a dead local variable + --> $DIR/storage-live-dead-var.rs:LL:CC + | +LL | val = 42; + | ^^^^^^^^ accessing a dead local variable + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/storage-live-dead-var.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/storage-live-resets-var.rs b/src/tools/miri/tests/fail/storage-live-resets-var.rs new file mode 100644 index 0000000000000..bfdd9e78943e9 --- /dev/null +++ b/src/tools/miri/tests/fail/storage-live-resets-var.rs @@ -0,0 +1,17 @@ +#![feature(core_intrinsics, custom_mir)] +use std::intrinsics::mir::*; + +#[custom_mir(dialect = "runtime")] +fn main() { + mir! { + let val: i32; + let _val2: i32; + { + StorageLive(val); + val = 42; + StorageLive(val); // reset val to `uninit` + _val2 = val; //~ERROR: uninitialized + Return() + } + } +} diff --git a/src/tools/miri/tests/fail/storage-live-resets-var.stderr b/src/tools/miri/tests/fail/storage-live-resets-var.stderr new file mode 100644 index 0000000000000..07d39cc9d6b3f --- /dev/null +++ b/src/tools/miri/tests/fail/storage-live-resets-var.stderr @@ -0,0 +1,15 @@ +error: Undefined Behavior: constructing invalid value: encountered uninitialized memory, but expected an integer + --> $DIR/storage-live-resets-var.rs:LL:CC + | +LL | _val2 = val; + | ^^^^^^^^^^^ constructing invalid value: encountered uninitialized memory, but expected an integer + | + = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior + = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information + = note: BACKTRACE: + = note: inside `main` at $DIR/storage-live-resets-var.rs:LL:CC + +note: some details are omitted, run with `MIRIFLAGS=-Zmiri-backtrace=full` for a verbose backtrace + +error: aborting due to 1 previous error + From 22d0b1ee18520ba32d3bc41ad3e85d91a5c73c24 Mon Sep 17 00:00:00 2001 From: Vadim Petrochenkov Date: Fri, 15 Mar 2024 14:21:03 +0300 Subject: [PATCH 07/33] delegation: Implement glob delegation --- compiler/rustc_ast/src/ast.rs | 9 +- compiler/rustc_ast/src/mut_visit.rs | 38 +++-- compiler/rustc_ast/src/visit.rs | 38 +++-- .../rustc_ast_pretty/src/pprust/state/item.rs | 51 +++--- compiler/rustc_expand/messages.ftl | 7 +- compiler/rustc_expand/src/base.rs | 47 +++++- compiler/rustc_expand/src/errors.rs | 12 +- compiler/rustc_expand/src/expand.rs | 152 +++++++++++++----- compiler/rustc_parse/src/errors.rs | 3 +- compiler/rustc_parse/src/parser/item.rs | 24 ++- .../rustc_resolve/src/build_reduced_graph.rs | 28 +++- compiler/rustc_resolve/src/def_collector.rs | 10 +- compiler/rustc_resolve/src/late.rs | 24 +-- compiler/rustc_resolve/src/lib.rs | 14 +- compiler/rustc_resolve/src/macros.rs | 152 +++++++++++++++--- tests/ui/delegation/body-identity-glob.rs | 32 ++++ tests/ui/delegation/empty-glob.rs | 11 ++ tests/ui/delegation/empty-glob.stderr | 8 + tests/ui/delegation/glob-bad-path.rs | 12 ++ tests/ui/delegation/glob-bad-path.stderr | 15 ++ tests/ui/delegation/glob-glob-conflict.rs | 33 ++++ tests/ui/delegation/glob-glob-conflict.stderr | 25 +++ tests/ui/delegation/glob-glob.rs | 36 +++++ tests/ui/delegation/glob-non-fn.rs | 38 +++++ tests/ui/delegation/glob-non-fn.stderr | 62 +++++++ tests/ui/delegation/glob-non-impl.rs | 20 +++ tests/ui/delegation/glob-non-impl.stderr | 26 +++ tests/ui/delegation/glob-override.rs | 37 +++++ tests/ui/delegation/glob.rs | 35 ++++ tests/ui/delegation/macro-inside-glob.rs | 26 +++ tests/ui/delegation/macro-inside-list.rs | 4 +- 31 files changed, 893 insertions(+), 136 deletions(-) create mode 100644 tests/ui/delegation/body-identity-glob.rs create mode 100644 tests/ui/delegation/empty-glob.rs create mode 100644 tests/ui/delegation/empty-glob.stderr create mode 100644 tests/ui/delegation/glob-bad-path.rs create mode 100644 tests/ui/delegation/glob-bad-path.stderr create mode 100644 tests/ui/delegation/glob-glob-conflict.rs create mode 100644 tests/ui/delegation/glob-glob-conflict.stderr create mode 100644 tests/ui/delegation/glob-glob.rs create mode 100644 tests/ui/delegation/glob-non-fn.rs create mode 100644 tests/ui/delegation/glob-non-fn.stderr create mode 100644 tests/ui/delegation/glob-non-impl.rs create mode 100644 tests/ui/delegation/glob-non-impl.stderr create mode 100644 tests/ui/delegation/glob-override.rs create mode 100644 tests/ui/delegation/glob.rs create mode 100644 tests/ui/delegation/macro-inside-glob.rs diff --git a/compiler/rustc_ast/src/ast.rs b/compiler/rustc_ast/src/ast.rs index 9cb193b4a6781..b9b8d0f67ee70 100644 --- a/compiler/rustc_ast/src/ast.rs +++ b/compiler/rustc_ast/src/ast.rs @@ -3158,13 +3158,16 @@ pub struct Delegation { pub path: Path, pub rename: Option, pub body: Option>, + /// The item was expanded from a glob delegation item. + pub from_glob: bool, } #[derive(Clone, Encodable, Decodable, Debug)] pub struct DelegationMac { pub qself: Option>, pub prefix: Path, - pub suffixes: ThinVec<(Ident, Option)>, + // Some for list delegation, and None for glob delegation. + pub suffixes: Option)>>, pub body: Option>, } @@ -3291,7 +3294,7 @@ pub enum ItemKind { /// /// E.g. `reuse ::name { target_expr_template }`. Delegation(Box), - /// A list delegation item (`reuse prefix::{a, b, c}`). + /// A list or glob delegation item (`reuse prefix::{a, b, c}`, `reuse prefix::*`). /// Treated similarly to a macro call and expanded early. DelegationMac(Box), } @@ -3372,7 +3375,7 @@ pub enum AssocItemKind { MacCall(P), /// An associated delegation item. Delegation(Box), - /// An associated delegation item list. + /// An associated list or glob delegation item. DelegationMac(Box), } diff --git a/compiler/rustc_ast/src/mut_visit.rs b/compiler/rustc_ast/src/mut_visit.rs index cc33ce2cb5629..8497be53c068f 100644 --- a/compiler/rustc_ast/src/mut_visit.rs +++ b/compiler/rustc_ast/src/mut_visit.rs @@ -1162,7 +1162,14 @@ impl NoopVisitItemKind for ItemKind { } ItemKind::MacCall(m) => vis.visit_mac_call(m), ItemKind::MacroDef(def) => vis.visit_macro_def(def), - ItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => { + ItemKind::Delegation(box Delegation { + id, + qself, + path, + rename, + body, + from_glob: _, + }) => { vis.visit_id(id); vis.visit_qself(qself); vis.visit_path(path); @@ -1176,10 +1183,12 @@ impl NoopVisitItemKind for ItemKind { ItemKind::DelegationMac(box DelegationMac { qself, prefix, suffixes, body }) => { vis.visit_qself(qself); vis.visit_path(prefix); - for (ident, rename) in suffixes { - vis.visit_ident(ident); - if let Some(rename) = rename { - vis.visit_ident(rename); + if let Some(suffixes) = suffixes { + for (ident, rename) in suffixes { + vis.visit_ident(ident); + if let Some(rename) = rename { + vis.visit_ident(rename); + } } } if let Some(body) = body { @@ -1218,7 +1227,14 @@ impl NoopVisitItemKind for AssocItemKind { visit_opt(ty, |ty| visitor.visit_ty(ty)); } AssocItemKind::MacCall(mac) => visitor.visit_mac_call(mac), - AssocItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => { + AssocItemKind::Delegation(box Delegation { + id, + qself, + path, + rename, + body, + from_glob: _, + }) => { visitor.visit_id(id); visitor.visit_qself(qself); visitor.visit_path(path); @@ -1232,10 +1248,12 @@ impl NoopVisitItemKind for AssocItemKind { AssocItemKind::DelegationMac(box DelegationMac { qself, prefix, suffixes, body }) => { visitor.visit_qself(qself); visitor.visit_path(prefix); - for (ident, rename) in suffixes { - visitor.visit_ident(ident); - if let Some(rename) = rename { - visitor.visit_ident(rename); + if let Some(suffixes) = suffixes { + for (ident, rename) in suffixes { + visitor.visit_ident(ident); + if let Some(rename) = rename { + visitor.visit_ident(rename); + } } } if let Some(body) = body { diff --git a/compiler/rustc_ast/src/visit.rs b/compiler/rustc_ast/src/visit.rs index fa97c8db3267b..30ab852c4aa92 100644 --- a/compiler/rustc_ast/src/visit.rs +++ b/compiler/rustc_ast/src/visit.rs @@ -398,7 +398,14 @@ impl WalkItemKind for ItemKind { } ItemKind::MacCall(mac) => try_visit!(visitor.visit_mac_call(mac)), ItemKind::MacroDef(ts) => try_visit!(visitor.visit_mac_def(ts, item.id)), - ItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => { + ItemKind::Delegation(box Delegation { + id, + qself, + path, + rename, + body, + from_glob: _, + }) => { if let Some(qself) = qself { try_visit!(visitor.visit_ty(&qself.ty)); } @@ -411,10 +418,12 @@ impl WalkItemKind for ItemKind { try_visit!(visitor.visit_ty(&qself.ty)); } try_visit!(visitor.visit_path(prefix, item.id)); - for (ident, rename) in suffixes { - visitor.visit_ident(*ident); - if let Some(rename) = rename { - visitor.visit_ident(*rename); + if let Some(suffixes) = suffixes { + for (ident, rename) in suffixes { + visitor.visit_ident(*ident); + if let Some(rename) = rename { + visitor.visit_ident(*rename); + } } } visit_opt!(visitor, visit_block, body); @@ -828,7 +837,14 @@ impl WalkItemKind for AssocItemKind { AssocItemKind::MacCall(mac) => { try_visit!(visitor.visit_mac_call(mac)); } - AssocItemKind::Delegation(box Delegation { id, qself, path, rename, body }) => { + AssocItemKind::Delegation(box Delegation { + id, + qself, + path, + rename, + body, + from_glob: _, + }) => { if let Some(qself) = qself { try_visit!(visitor.visit_ty(&qself.ty)); } @@ -841,10 +857,12 @@ impl WalkItemKind for AssocItemKind { try_visit!(visitor.visit_ty(&qself.ty)); } try_visit!(visitor.visit_path(prefix, item.id)); - for (ident, rename) in suffixes { - visitor.visit_ident(*ident); - if let Some(rename) = rename { - visitor.visit_ident(*rename); + if let Some(suffixes) = suffixes { + for (ident, rename) in suffixes { + visitor.visit_ident(*ident); + if let Some(rename) = rename { + visitor.visit_ident(*rename); + } } } visit_opt!(visitor, visit_block, body); diff --git a/compiler/rustc_ast_pretty/src/pprust/state/item.rs b/compiler/rustc_ast_pretty/src/pprust/state/item.rs index 474741fb06777..49ac5ece337f0 100644 --- a/compiler/rustc_ast_pretty/src/pprust/state/item.rs +++ b/compiler/rustc_ast_pretty/src/pprust/state/item.rs @@ -9,6 +9,12 @@ use rustc_ast::ptr::P; use rustc_ast::ModKind; use rustc_span::symbol::Ident; +enum DelegationKind<'a> { + Single, + List(&'a [(Ident, Option)]), + Glob, +} + fn visibility_qualified(vis: &ast::Visibility, s: &str) -> String { format!("{}{}", State::to_string(|s| s.print_visibility(vis)), s) } @@ -387,7 +393,7 @@ impl<'a> State<'a> { &item.vis, &deleg.qself, &deleg.path, - None, + DelegationKind::Single, &deleg.body, ), ast::ItemKind::DelegationMac(deleg) => self.print_delegation( @@ -395,7 +401,7 @@ impl<'a> State<'a> { &item.vis, &deleg.qself, &deleg.prefix, - Some(&deleg.suffixes), + deleg.suffixes.as_ref().map_or(DelegationKind::Glob, |s| DelegationKind::List(s)), &deleg.body, ), } @@ -579,7 +585,7 @@ impl<'a> State<'a> { vis, &deleg.qself, &deleg.path, - None, + DelegationKind::Single, &deleg.body, ), ast::AssocItemKind::DelegationMac(deleg) => self.print_delegation( @@ -587,20 +593,20 @@ impl<'a> State<'a> { vis, &deleg.qself, &deleg.prefix, - Some(&deleg.suffixes), + deleg.suffixes.as_ref().map_or(DelegationKind::Glob, |s| DelegationKind::List(s)), &deleg.body, ), } self.ann.post(self, AnnNode::SubItem(id)) } - pub(crate) fn print_delegation( + fn print_delegation( &mut self, attrs: &[ast::Attribute], vis: &ast::Visibility, qself: &Option>, path: &ast::Path, - suffixes: Option<&[(Ident, Option)]>, + kind: DelegationKind<'_>, body: &Option>, ) { if body.is_some() { @@ -614,21 +620,28 @@ impl<'a> State<'a> { } else { self.print_path(path, false, 0); } - if let Some(suffixes) = suffixes { - self.word("::"); - self.word("{"); - for (i, (ident, rename)) in suffixes.iter().enumerate() { - self.print_ident(*ident); - if let Some(rename) = rename { - self.nbsp(); - self.word_nbsp("as"); - self.print_ident(*rename); - } - if i != suffixes.len() - 1 { - self.word_space(","); + match kind { + DelegationKind::Single => {} + DelegationKind::List(suffixes) => { + self.word("::"); + self.word("{"); + for (i, (ident, rename)) in suffixes.iter().enumerate() { + self.print_ident(*ident); + if let Some(rename) = rename { + self.nbsp(); + self.word_nbsp("as"); + self.print_ident(*rename); + } + if i != suffixes.len() - 1 { + self.word_space(","); + } } + self.word("}"); + } + DelegationKind::Glob => { + self.word("::"); + self.word("*"); } - self.word("}"); } if let Some(body) = body { self.nbsp(); diff --git a/compiler/rustc_expand/messages.ftl b/compiler/rustc_expand/messages.ftl index 2e150f7bb279b..6113580491ef9 100644 --- a/compiler/rustc_expand/messages.ftl +++ b/compiler/rustc_expand/messages.ftl @@ -35,8 +35,8 @@ expand_duplicate_matcher_binding = duplicate matcher binding .label = duplicate binding .label2 = previous binding -expand_empty_delegation_list = - empty list delegation is not supported +expand_empty_delegation_mac = + empty {$kind} delegation is not supported expand_expected_paren_or_brace = expected `(` or `{"{"}`, found `{$token}` @@ -58,6 +58,9 @@ expand_feature_removed = .label = feature has been removed .reason = {$reason} +expand_glob_delegation_outside_impls = + glob delegation is only supported in impls + expand_helper_attribute_name_invalid = `{$name}` cannot be a name of derive helper attribute diff --git a/compiler/rustc_expand/src/base.rs b/compiler/rustc_expand/src/base.rs index b3f6a35f3a4b3..aab493b9db5a2 100644 --- a/compiler/rustc_expand/src/base.rs +++ b/compiler/rustc_expand/src/base.rs @@ -357,6 +357,10 @@ where } } +pub trait GlobDelegationExpander { + fn expand(&self, ecx: &mut ExtCtxt<'_>) -> ExpandResult)>, ()>; +} + // Use a macro because forwarding to a simple function has type system issues macro_rules! make_stmts_default { ($me:expr) => { @@ -714,6 +718,9 @@ pub enum SyntaxExtensionKind { /// The produced AST fragment is appended to the input AST fragment. Box, ), + + /// A glob delegation. + GlobDelegation(Box), } /// A struct representing a macro definition in "lowered" form ready for expansion. @@ -748,7 +755,9 @@ impl SyntaxExtension { /// Returns which kind of macro calls this syntax extension. pub fn macro_kind(&self) -> MacroKind { match self.kind { - SyntaxExtensionKind::Bang(..) | SyntaxExtensionKind::LegacyBang(..) => MacroKind::Bang, + SyntaxExtensionKind::Bang(..) + | SyntaxExtensionKind::LegacyBang(..) + | SyntaxExtensionKind::GlobDelegation(..) => MacroKind::Bang, SyntaxExtensionKind::Attr(..) | SyntaxExtensionKind::LegacyAttr(..) | SyntaxExtensionKind::NonMacroAttr => MacroKind::Attr, @@ -922,6 +931,32 @@ impl SyntaxExtension { SyntaxExtension::default(SyntaxExtensionKind::NonMacroAttr, edition) } + pub fn glob_delegation( + trait_def_id: DefId, + impl_def_id: LocalDefId, + edition: Edition, + ) -> SyntaxExtension { + struct GlobDelegationExpanderImpl { + trait_def_id: DefId, + impl_def_id: LocalDefId, + } + impl GlobDelegationExpander for GlobDelegationExpanderImpl { + fn expand( + &self, + ecx: &mut ExtCtxt<'_>, + ) -> ExpandResult)>, ()> { + match ecx.resolver.glob_delegation_suffixes(self.trait_def_id, self.impl_def_id) { + Ok(suffixes) => ExpandResult::Ready(suffixes), + Err(Indeterminate) if ecx.force_mode => ExpandResult::Ready(Vec::new()), + Err(Indeterminate) => ExpandResult::Retry(()), + } + } + } + + let expander = GlobDelegationExpanderImpl { trait_def_id, impl_def_id }; + SyntaxExtension::default(SyntaxExtensionKind::GlobDelegation(Box::new(expander)), edition) + } + pub fn expn_data( &self, parent: LocalExpnId, @@ -1030,6 +1065,16 @@ pub trait ResolverExpand { /// Tools registered with `#![register_tool]` and used by tool attributes and lints. fn registered_tools(&self) -> &RegisteredTools; + + /// Mark this invocation id as a glob delegation. + fn register_glob_delegation(&mut self, invoc_id: LocalExpnId); + + /// Names of specific methods to which glob delegation expands. + fn glob_delegation_suffixes( + &mut self, + trait_def_id: DefId, + impl_def_id: LocalDefId, + ) -> Result)>, Indeterminate>; } pub trait LintStoreExpand { diff --git a/compiler/rustc_expand/src/errors.rs b/compiler/rustc_expand/src/errors.rs index 3f8b4661e5f5b..c883121fb4065 100644 --- a/compiler/rustc_expand/src/errors.rs +++ b/compiler/rustc_expand/src/errors.rs @@ -435,8 +435,16 @@ pub struct ExpectedParenOrBrace<'a> { } #[derive(Diagnostic)] -#[diag(expand_empty_delegation_list)] -pub(crate) struct EmptyDelegationList { +#[diag(expand_empty_delegation_mac)] +pub(crate) struct EmptyDelegationMac { + #[primary_span] + pub span: Span, + pub kind: String, +} + +#[derive(Diagnostic)] +#[diag(expand_glob_delegation_outside_impls)] +pub(crate) struct GlobDelegationOutsideImpls { #[primary_span] pub span: Span, } diff --git a/compiler/rustc_expand/src/expand.rs b/compiler/rustc_expand/src/expand.rs index c28a09eb57c77..716bfc8c26b1e 100644 --- a/compiler/rustc_expand/src/expand.rs +++ b/compiler/rustc_expand/src/expand.rs @@ -1,8 +1,8 @@ use crate::base::*; use crate::config::StripUnconfigured; use crate::errors::{ - EmptyDelegationList, IncompleteParse, RecursionLimitReached, RemoveExprNotSupported, - RemoveNodeNotSupported, UnsupportedKeyValue, WrongFragmentKind, + EmptyDelegationMac, GlobDelegationOutsideImpls, IncompleteParse, RecursionLimitReached, + RemoveExprNotSupported, RemoveNodeNotSupported, UnsupportedKeyValue, WrongFragmentKind, }; use crate::mbe::diagnostics::annotate_err_with_kind; use crate::module::{mod_dir_path, parse_external_mod, DirOwnership, ParsedExternalMod}; @@ -343,6 +343,9 @@ pub enum InvocationKind { is_const: bool, item: Annotatable, }, + GlobDelegation { + item: P, + }, } impl InvocationKind { @@ -370,6 +373,7 @@ impl Invocation { InvocationKind::Bang { span, .. } => *span, InvocationKind::Attr { attr, .. } => attr.span, InvocationKind::Derive { path, .. } => path.span, + InvocationKind::GlobDelegation { item } => item.span, } } @@ -378,6 +382,7 @@ impl Invocation { InvocationKind::Bang { span, .. } => span, InvocationKind::Attr { attr, .. } => &mut attr.span, InvocationKind::Derive { path, .. } => &mut path.span, + InvocationKind::GlobDelegation { item } => &mut item.span, } } } @@ -800,6 +805,36 @@ impl<'a, 'b> MacroExpander<'a, 'b> { } _ => unreachable!(), }, + InvocationKind::GlobDelegation { item } => { + let AssocItemKind::DelegationMac(deleg) = &item.kind else { unreachable!() }; + let suffixes = match ext { + SyntaxExtensionKind::GlobDelegation(expander) => match expander.expand(self.cx) + { + ExpandResult::Ready(suffixes) => suffixes, + ExpandResult::Retry(()) => { + // Reassemble the original invocation for retrying. + return ExpandResult::Retry(Invocation { + kind: InvocationKind::GlobDelegation { item }, + ..invoc + }); + } + }, + SyntaxExtensionKind::LegacyBang(..) => { + let msg = "expanded a dummy glob delegation"; + let guar = self.cx.dcx().span_delayed_bug(span, msg); + return ExpandResult::Ready(fragment_kind.dummy(span, guar)); + } + _ => unreachable!(), + }; + + type Node = AstNodeWrapper, ImplItemTag>; + let single_delegations = build_single_delegations::( + self.cx, deleg, &item, &suffixes, item.span, true, + ); + fragment_kind.expect_from_annotatables( + single_delegations.map(|item| Annotatable::ImplItem(P(item))), + ) + } }) } @@ -1067,7 +1102,7 @@ trait InvocationCollectorNode: HasAttrs + HasNodeId + Sized { fn take_mac_call(self) -> (P, Self::AttrsTy, AddSemicolon) { unreachable!() } - fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item)> { + fn delegation(&self) -> Option<(&ast::DelegationMac, &ast::Item)> { None } fn delegation_item_kind(_deleg: Box) -> Self::ItemKind { @@ -1126,7 +1161,7 @@ impl InvocationCollectorNode for P { _ => unreachable!(), } } - fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item)> { + fn delegation(&self) -> Option<(&ast::DelegationMac, &ast::Item)> { match &self.kind { ItemKind::DelegationMac(deleg) => Some((deleg, self)), _ => None, @@ -1270,7 +1305,7 @@ impl InvocationCollectorNode for AstNodeWrapper, TraitItemTag> _ => unreachable!(), } } - fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item)> { + fn delegation(&self) -> Option<(&ast::DelegationMac, &ast::Item)> { match &self.wrapped.kind { AssocItemKind::DelegationMac(deleg) => Some((deleg, &self.wrapped)), _ => None, @@ -1311,7 +1346,7 @@ impl InvocationCollectorNode for AstNodeWrapper, ImplItemTag> _ => unreachable!(), } } - fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item)> { + fn delegation(&self) -> Option<(&ast::DelegationMac, &ast::Item)> { match &self.wrapped.kind { AssocItemKind::DelegationMac(deleg) => Some((deleg, &self.wrapped)), _ => None, @@ -1487,7 +1522,7 @@ impl InvocationCollectorNode for ast::Stmt { }; (mac, attrs, if add_semicolon { AddSemicolon::Yes } else { AddSemicolon::No }) } - fn delegation_list(&self) -> Option<(&ast::DelegationMac, &ast::Item)> { + fn delegation(&self) -> Option<(&ast::DelegationMac, &ast::Item)> { match &self.kind { StmtKind::Item(item) => match &item.kind { ItemKind::DelegationMac(deleg) => Some((deleg, item)), @@ -1684,6 +1719,44 @@ impl InvocationCollectorNode for AstNodeWrapper, MethodReceiverTag> } } +fn build_single_delegations<'a, Node: InvocationCollectorNode>( + ecx: &ExtCtxt<'_>, + deleg: &'a ast::DelegationMac, + item: &'a ast::Item, + suffixes: &'a [(Ident, Option)], + item_span: Span, + from_glob: bool, +) -> impl Iterator> + 'a { + if suffixes.is_empty() { + // Report an error for now, to avoid keeping stem for resolution and + // stability checks. + let kind = String::from(if from_glob { "glob" } else { "list" }); + ecx.dcx().emit_err(EmptyDelegationMac { span: item.span, kind }); + } + + suffixes.iter().map(move |&(ident, rename)| { + let mut path = deleg.prefix.clone(); + path.segments.push(ast::PathSegment { ident, id: ast::DUMMY_NODE_ID, args: None }); + + ast::Item { + attrs: item.attrs.clone(), + id: ast::DUMMY_NODE_ID, + span: if from_glob { item_span } else { ident.span }, + vis: item.vis.clone(), + ident: rename.unwrap_or(ident), + kind: Node::delegation_item_kind(Box::new(ast::Delegation { + id: ast::DUMMY_NODE_ID, + qself: deleg.qself.clone(), + path, + rename, + body: deleg.body.clone(), + from_glob, + })), + tokens: None, + } + }) +} + struct InvocationCollector<'a, 'b> { cx: &'a mut ExtCtxt<'b>, invocations: Vec<(Invocation, Option>)>, @@ -1702,6 +1775,11 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { fn collect(&mut self, fragment_kind: AstFragmentKind, kind: InvocationKind) -> AstFragment { let expn_id = LocalExpnId::fresh_empty(); + if matches!(kind, InvocationKind::GlobDelegation { .. }) { + // In resolver we need to know which invocation ids are delegations early, + // before their `ExpnData` is filled. + self.cx.resolver.register_glob_delegation(expn_id); + } let vis = kind.placeholder_visibility(); self.invocations.push(( Invocation { @@ -1734,6 +1812,14 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { self.collect(kind, InvocationKind::Attr { attr, pos, item, derives }) } + fn collect_glob_delegation( + &mut self, + item: P, + kind: AstFragmentKind, + ) -> AstFragment { + self.collect(kind, InvocationKind::GlobDelegation { item }) + } + /// If `item` is an attribute invocation, remove the attribute and return it together with /// its position and derives following it. We have to collect the derives in order to resolve /// legacy derive helpers (helpers written before derives that introduce them). @@ -1901,37 +1987,27 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { Node::post_flat_map_node_collect_bang(&mut res, add_semicolon); res } - None if let Some((deleg, item)) = node.delegation_list() => { - if deleg.suffixes.is_empty() { - // Report an error for now, to avoid keeping stem for resolution and - // stability checks. - self.cx.dcx().emit_err(EmptyDelegationList { span: item.span }); - } - - Node::flatten_outputs(deleg.suffixes.iter().map(|&(ident, rename)| { - let mut path = deleg.prefix.clone(); - path.segments.push(ast::PathSegment { - ident, - id: ast::DUMMY_NODE_ID, - args: None, - }); - - let mut item = Node::from_item(ast::Item { - attrs: item.attrs.clone(), - id: ast::DUMMY_NODE_ID, - span: ident.span, - vis: item.vis.clone(), - ident: rename.unwrap_or(ident), - kind: Node::delegation_item_kind(Box::new(ast::Delegation { - id: ast::DUMMY_NODE_ID, - qself: deleg.qself.clone(), - path, - rename, - body: deleg.body.clone(), - })), - tokens: None, - }); + None if let Some((deleg, item)) = node.delegation() => { + let Some(suffixes) = &deleg.suffixes else { + let item = match node.to_annotatable() { + Annotatable::ImplItem(item) => item, + ann @ (Annotatable::Item(_) + | Annotatable::TraitItem(_) + | Annotatable::Stmt(_)) => { + let span = ann.span(); + self.cx.dcx().emit_err(GlobDelegationOutsideImpls { span }); + return Default::default(); + } + _ => unreachable!(), + }; + return self.collect_glob_delegation(item, Node::KIND).make_ast::(); + }; + let single_delegations = build_single_delegations::( + self.cx, deleg, item, suffixes, item.span, false, + ); + Node::flatten_outputs(single_delegations.map(|item| { + let mut item = Node::from_item(item); assign_id!(self, item.node_id_mut(), || item.noop_flat_map(self)) })) } @@ -1983,7 +2059,7 @@ impl<'a, 'b> InvocationCollector<'a, 'b> { self.collect_bang(mac, Node::KIND).make_ast::() }) } - None if node.delegation_list().is_some() => unreachable!(), + None if node.delegation().is_some() => unreachable!(), None => { assign_id!(self, node.node_id_mut(), || node.noop_visit(self)) } diff --git a/compiler/rustc_parse/src/errors.rs b/compiler/rustc_parse/src/errors.rs index 6c1fcbe06fc5e..314f3f17dab0c 100644 --- a/compiler/rustc_parse/src/errors.rs +++ b/compiler/rustc_parse/src/errors.rs @@ -2699,12 +2699,13 @@ pub(crate) struct SingleColonImportPath { #[derive(Diagnostic)] #[diag(parse_bad_item_kind)] -#[help] pub(crate) struct BadItemKind { #[primary_span] pub span: Span, pub descr: &'static str, pub ctx: &'static str, + #[help] + pub help: Option<()>, } #[derive(Diagnostic)] diff --git a/compiler/rustc_parse/src/parser/item.rs b/compiler/rustc_parse/src/parser/item.rs index 3f5a4afdad8ab..9df1431f6570d 100644 --- a/compiler/rustc_parse/src/parser/item.rs +++ b/compiler/rustc_parse/src/parser/item.rs @@ -707,15 +707,25 @@ impl<'a> Parser<'a> { }; let (ident, item_kind) = if self.eat(&token::PathSep) { - let (suffixes, _) = self.parse_delim_comma_seq(Delimiter::Brace, |p| { - Ok((p.parse_path_segment_ident()?, rename(p)?)) - })?; + let suffixes = if self.eat(&token::BinOp(token::Star)) { + None + } else { + let parse_suffix = |p: &mut Self| Ok((p.parse_path_segment_ident()?, rename(p)?)); + Some(self.parse_delim_comma_seq(Delimiter::Brace, parse_suffix)?.0) + }; let deleg = DelegationMac { qself, prefix: path, suffixes, body: body(self)? }; (Ident::empty(), ItemKind::DelegationMac(Box::new(deleg))) } else { let rename = rename(self)?; let ident = rename.unwrap_or_else(|| path.segments.last().unwrap().ident); - let deleg = Delegation { id: DUMMY_NODE_ID, qself, path, rename, body: body(self)? }; + let deleg = Delegation { + id: DUMMY_NODE_ID, + qself, + path, + rename, + body: body(self)?, + from_glob: false, + }; (ident, ItemKind::Delegation(Box::new(deleg))) }; @@ -1237,7 +1247,11 @@ impl<'a> Parser<'a> { // FIXME(#100717): needs variant for each `ItemKind` (instead of using `ItemKind::descr()`) let span = self.psess.source_map().guess_head_span(span); let descr = kind.descr(); - self.dcx().emit_err(errors::BadItemKind { span, descr, ctx }); + let help = match kind { + ItemKind::DelegationMac(deleg) if deleg.suffixes.is_none() => None, + _ => Some(()), + }; + self.dcx().emit_err(errors::BadItemKind { span, descr, ctx, help }); None } diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 689109b2840f8..e035749fc39ee 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -19,6 +19,7 @@ use rustc_ast::{self as ast, AssocItem, AssocItemKind, MetaItemKind, StmtKind}; use rustc_ast::{Block, ForeignItem, ForeignItemKind, Impl, Item, ItemKind, NodeId}; use rustc_attr as attr; use rustc_data_structures::sync::Lrc; +use rustc_expand::base::ResolverExpand; use rustc_expand::expand::AstFragment; use rustc_hir::def::{self, *}; use rustc_hir::def_id::{DefId, LocalDefId, CRATE_DEF_ID}; @@ -1358,6 +1359,14 @@ impl<'a, 'b, 'tcx> Visitor<'b> for BuildReducedGraphVisitor<'a, 'b, 'tcx> { self.visit_invoc_in_module(item.id); } AssocCtxt::Impl => { + let invoc_id = item.id.placeholder_to_expn_id(); + if !self.r.glob_delegation_invoc_ids.contains(&invoc_id) { + self.r + .impl_unexpanded_invocations + .entry(self.r.invocation_parent(invoc_id)) + .or_default() + .insert(invoc_id); + } self.visit_invoc(item.id); } } @@ -1379,18 +1388,21 @@ impl<'a, 'b, 'tcx> Visitor<'b> for BuildReducedGraphVisitor<'a, 'b, 'tcx> { self.r.feed_visibility(feed, vis); } + let ns = match item.kind { + AssocItemKind::Const(..) | AssocItemKind::Delegation(..) | AssocItemKind::Fn(..) => { + ValueNS + } + AssocItemKind::Type(..) => TypeNS, + AssocItemKind::MacCall(_) | AssocItemKind::DelegationMac(..) => bug!(), // handled above + }; if ctxt == AssocCtxt::Trait { - let ns = match item.kind { - AssocItemKind::Const(..) - | AssocItemKind::Delegation(..) - | AssocItemKind::Fn(..) => ValueNS, - AssocItemKind::Type(..) => TypeNS, - AssocItemKind::MacCall(_) | AssocItemKind::DelegationMac(..) => bug!(), // handled above - }; - let parent = self.parent_scope.module; let expansion = self.parent_scope.expansion; self.r.define(parent, item.ident, ns, (self.res(def_id), vis, item.span, expansion)); + } else if !matches!(&item.kind, AssocItemKind::Delegation(deleg) if deleg.from_glob) { + let impl_def_id = self.r.tcx.local_parent(local_def_id); + let key = BindingKey::new(item.ident.normalize_to_macros_2_0(), ns); + self.r.impl_binding_keys.entry(impl_def_id).or_default().insert(key); } visit::walk_assoc_item(self, item, ctxt); diff --git a/compiler/rustc_resolve/src/def_collector.rs b/compiler/rustc_resolve/src/def_collector.rs index ca97d10617b6e..fb6e55f2b7bdf 100644 --- a/compiler/rustc_resolve/src/def_collector.rs +++ b/compiler/rustc_resolve/src/def_collector.rs @@ -144,8 +144,9 @@ impl<'a, 'b, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'b, 'tcx> { } ItemKind::GlobalAsm(..) => DefKind::GlobalAsm, ItemKind::Use(..) => return visit::walk_item(self, i), - ItemKind::MacCall(..) => return self.visit_macro_invoc(i.id), - ItemKind::DelegationMac(..) => unreachable!(), + ItemKind::MacCall(..) | ItemKind::DelegationMac(..) => { + return self.visit_macro_invoc(i.id); + } }; let def_id = self.create_def(i.id, i.ident.name, def_kind, i.span); @@ -294,8 +295,9 @@ impl<'a, 'b, 'tcx> visit::Visitor<'a> for DefCollector<'a, 'b, 'tcx> { AssocItemKind::Fn(..) | AssocItemKind::Delegation(..) => DefKind::AssocFn, AssocItemKind::Const(..) => DefKind::AssocConst, AssocItemKind::Type(..) => DefKind::AssocTy, - AssocItemKind::MacCall(..) => return self.visit_macro_invoc(i.id), - AssocItemKind::DelegationMac(..) => unreachable!(), + AssocItemKind::MacCall(..) | AssocItemKind::DelegationMac(..) => { + return self.visit_macro_invoc(i.id); + } }; let def = self.create_def(i.id, i.ident.name, def_kind, i.span); diff --git a/compiler/rustc_resolve/src/late.rs b/compiler/rustc_resolve/src/late.rs index b0adc3775f667..fa711d932b63b 100644 --- a/compiler/rustc_resolve/src/late.rs +++ b/compiler/rustc_resolve/src/late.rs @@ -3281,17 +3281,19 @@ impl<'a: 'ast, 'b, 'ast, 'tcx> LateResolutionVisitor<'a, 'b, 'ast, 'tcx> { } self.visit_path(&delegation.path, delegation.id); if let Some(body) = &delegation.body { - // `PatBoundCtx` is not necessary in this context - let mut bindings = smallvec![(PatBoundCtx::Product, Default::default())]; - - let span = delegation.path.segments.last().unwrap().ident.span; - self.fresh_binding( - Ident::new(kw::SelfLower, span), - delegation.id, - PatternSource::FnParam, - &mut bindings, - ); - self.visit_block(body); + self.with_rib(ValueNS, RibKind::FnOrCoroutine, |this| { + // `PatBoundCtx` is not necessary in this context + let mut bindings = smallvec![(PatBoundCtx::Product, Default::default())]; + + let span = delegation.path.segments.last().unwrap().ident.span; + this.fresh_binding( + Ident::new(kw::SelfLower, span), + delegation.id, + PatternSource::FnParam, + &mut bindings, + ); + this.visit_block(body); + }); } } diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 9557b0f5ebc08..3a831a7f19e06 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -1089,7 +1089,7 @@ pub struct Resolver<'a, 'tcx> { single_segment_macro_resolutions: Vec<(Ident, MacroKind, ParentScope<'a>, Option>)>, multi_segment_macro_resolutions: - Vec<(Vec, Span, MacroKind, ParentScope<'a>, Option)>, + Vec<(Vec, Span, MacroKind, ParentScope<'a>, Option, Namespace)>, builtin_attrs: Vec<(Ident, ParentScope<'a>)>, /// `derive(Copy)` marks items they are applied to so they are treated specially later. /// Derive macros cannot modify the item themselves and have to store the markers in the global @@ -1163,6 +1163,15 @@ pub struct Resolver<'a, 'tcx> { doc_link_resolutions: FxHashMap, doc_link_traits_in_scope: FxHashMap>, all_macro_rules: FxHashMap, + + /// Invocation ids of all glob delegations. + glob_delegation_invoc_ids: FxHashSet, + /// Analogue of module `unexpanded_invocations` but in trait impls, excluding glob delegations. + /// Needed because glob delegations wait for all other neighboring macros to expand. + impl_unexpanded_invocations: FxHashMap>, + /// Simplified analogue of module `resolutions` but in trait impls, excluding glob delegations. + /// Needed because glob delegations exclude explicitly defined names. + impl_binding_keys: FxHashMap>, } /// Nothing really interesting here; it just provides memory for the rest of the crate. @@ -1504,6 +1513,9 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { doc_link_traits_in_scope: Default::default(), all_macro_rules: Default::default(), delegation_fn_sigs: Default::default(), + glob_delegation_invoc_ids: Default::default(), + impl_unexpanded_invocations: Default::default(), + impl_binding_keys: Default::default(), }; let root_parent_scope = ParentScope::module(graph_root, &resolver); diff --git a/compiler/rustc_resolve/src/macros.rs b/compiler/rustc_resolve/src/macros.rs index 268e7f06d0423..87794d11cea9c 100644 --- a/compiler/rustc_resolve/src/macros.rs +++ b/compiler/rustc_resolve/src/macros.rs @@ -5,7 +5,7 @@ use crate::errors::CannotDetermineMacroResolution; use crate::errors::{self, AddAsNonDerive, CannotFindIdentInThisScope}; use crate::errors::{MacroExpectedFound, RemoveSurroundingDerive}; use crate::Namespace::*; -use crate::{BuiltinMacroState, Determinacy, MacroData, Used}; +use crate::{BindingKey, BuiltinMacroState, Determinacy, MacroData, Used}; use crate::{DeriveData, Finalize, ParentScope, ResolutionError, Resolver, ScopeSet}; use crate::{ModuleKind, ModuleOrUniformRoot, NameBinding, PathResult, Segment, ToNameBinding}; use rustc_ast::expand::StrippedCfgItem; @@ -198,6 +198,11 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> { self.output_macro_rules_scopes.insert(expansion, output_macro_rules_scope); parent_scope.module.unexpanded_invocations.borrow_mut().remove(&expansion); + if let Some(unexpanded_invocations) = + self.impl_unexpanded_invocations.get_mut(&self.invocation_parent(expansion)) + { + unexpanded_invocations.remove(&expansion); + } } fn register_builtin_macro(&mut self, name: Symbol, ext: SyntaxExtensionKind) { @@ -262,15 +267,21 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> { } }; - let (path, kind, inner_attr, derives) = match invoc.kind { - InvocationKind::Attr { ref attr, ref derives, .. } => ( - &attr.get_normal_item().path, - MacroKind::Attr, - attr.style == ast::AttrStyle::Inner, - self.arenas.alloc_ast_paths(derives), - ), - InvocationKind::Bang { ref mac, .. } => (&mac.path, MacroKind::Bang, false, &[][..]), - InvocationKind::Derive { ref path, .. } => (path, MacroKind::Derive, false, &[][..]), + let (mut derives, mut inner_attr, mut deleg_impl) = (&[][..], false, None); + let (path, kind) = match invoc.kind { + InvocationKind::Attr { ref attr, derives: ref attr_derives, .. } => { + derives = self.arenas.alloc_ast_paths(attr_derives); + inner_attr = attr.style == ast::AttrStyle::Inner; + (&attr.get_normal_item().path, MacroKind::Attr) + } + InvocationKind::Bang { ref mac, .. } => (&mac.path, MacroKind::Bang), + InvocationKind::Derive { ref path, .. } => (path, MacroKind::Derive), + InvocationKind::GlobDelegation { ref item } => { + let ast::AssocItemKind::DelegationMac(deleg) = &item.kind else { unreachable!() }; + deleg_impl = Some(self.invocation_parent(invoc_id)); + // It is sufficient to consider glob delegation a bang macro for now. + (&deleg.prefix, MacroKind::Bang) + } }; // Derives are not included when `invocations` are collected, so we have to add them here. @@ -286,10 +297,11 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> { node_id, force, soft_custom_inner_attributes_gate(path, invoc), + deleg_impl, )?; let span = invoc.span(); - let def_id = res.opt_def_id(); + let def_id = if deleg_impl.is_some() { None } else { res.opt_def_id() }; invoc_id.set_expn_data( ext.expn_data( parent_scope.expansion, @@ -452,6 +464,45 @@ impl<'a, 'tcx> ResolverExpand for Resolver<'a, 'tcx> { fn registered_tools(&self) -> &RegisteredTools { self.registered_tools } + + fn register_glob_delegation(&mut self, invoc_id: LocalExpnId) { + self.glob_delegation_invoc_ids.insert(invoc_id); + } + + fn glob_delegation_suffixes( + &mut self, + trait_def_id: DefId, + impl_def_id: LocalDefId, + ) -> Result)>, Indeterminate> { + let target_trait = self.expect_module(trait_def_id); + if !target_trait.unexpanded_invocations.borrow().is_empty() { + return Err(Indeterminate); + } + // FIXME: Instead of waiting try generating all trait methods, and pruning + // the shadowed ones a bit later, e.g. when all macro expansion completes. + // Pros: expansion will be stuck less (but only in exotic cases), the implementation may be + // less hacky. + // Cons: More code is generated just to be deleted later, deleting already created `DefId`s + // may be nontrivial. + if let Some(unexpanded_invocations) = self.impl_unexpanded_invocations.get(&impl_def_id) + && !unexpanded_invocations.is_empty() + { + return Err(Indeterminate); + } + + let mut idents = Vec::new(); + target_trait.for_each_child(self, |this, ident, ns, _binding| { + // FIXME: Adjust hygiene for idents from globs, like for glob imports. + if let Some(overriding_keys) = this.impl_binding_keys.get(&impl_def_id) + && overriding_keys.contains(&BindingKey::new(ident.normalize_to_macros_2_0(), ns)) + { + // The name is overridden, do not produce it from the glob delegation. + } else { + idents.push((ident, None)); + } + }); + Ok(idents) + } } impl<'a, 'tcx> Resolver<'a, 'tcx> { @@ -468,15 +519,40 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { node_id: NodeId, force: bool, soft_custom_inner_attributes_gate: bool, + deleg_impl: Option, ) -> Result<(Lrc, Res), Indeterminate> { - let (ext, res) = match self.resolve_macro_path(path, Some(kind), parent_scope, true, force) - { + let (ext, res) = match self.resolve_macro_or_delegation_path( + path, + Some(kind), + parent_scope, + true, + force, + deleg_impl, + ) { Ok((Some(ext), res)) => (ext, res), Ok((None, res)) => (self.dummy_ext(kind), res), Err(Determinacy::Determined) => (self.dummy_ext(kind), Res::Err), Err(Determinacy::Undetermined) => return Err(Indeterminate), }; + // Everything below is irrelevant to glob delegation, take a shortcut. + if deleg_impl.is_some() { + if !matches!(res, Res::Err | Res::Def(DefKind::Trait, _)) { + self.dcx().emit_err(MacroExpectedFound { + span: path.span, + expected: "trait", + article: "a", + found: res.descr(), + macro_path: &pprust::path_to_string(path), + remove_surrounding_derive: None, + add_as_non_derive: None, + }); + return Ok((self.dummy_ext(kind), Res::Err)); + } + + return Ok((ext, res)); + } + // Report errors for the resolved macro. for segment in &path.segments { if let Some(args) = &segment.args { @@ -605,12 +681,25 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { parent_scope: &ParentScope<'a>, trace: bool, force: bool, + ) -> Result<(Option>, Res), Determinacy> { + self.resolve_macro_or_delegation_path(path, kind, parent_scope, trace, force, None) + } + + fn resolve_macro_or_delegation_path( + &mut self, + path: &ast::Path, + kind: Option, + parent_scope: &ParentScope<'a>, + trace: bool, + force: bool, + deleg_impl: Option, ) -> Result<(Option>, Res), Determinacy> { let path_span = path.span; let mut path = Segment::from_path(path); // Possibly apply the macro helper hack - if kind == Some(MacroKind::Bang) + if deleg_impl.is_none() + && kind == Some(MacroKind::Bang) && path.len() == 1 && path[0].ident.span.ctxt().outer_expn_data().local_inner_macros { @@ -618,13 +707,17 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { path.insert(0, Segment::from_ident(root)); } - let res = if path.len() > 1 { - let res = match self.maybe_resolve_path(&path, Some(MacroNS), parent_scope) { + let res = if deleg_impl.is_some() || path.len() > 1 { + let ns = if deleg_impl.is_some() { TypeNS } else { MacroNS }; + let res = match self.maybe_resolve_path(&path, Some(ns), parent_scope) { PathResult::NonModule(path_res) if let Some(res) = path_res.full_res() => Ok(res), PathResult::Indeterminate if !force => return Err(Determinacy::Undetermined), PathResult::NonModule(..) | PathResult::Indeterminate | PathResult::Failed { .. } => Err(Determinacy::Determined), + PathResult::Module(ModuleOrUniformRoot::Module(module)) => { + Ok(module.res().unwrap()) + } PathResult::Module(..) => unreachable!(), }; @@ -636,6 +729,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { kind, *parent_scope, res.ok(), + ns, )); } @@ -670,7 +764,18 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { res }; - res.map(|res| (self.get_macro(res).map(|macro_data| macro_data.ext.clone()), res)) + let res = res?; + let ext = match deleg_impl { + Some(impl_def_id) => match res { + def::Res::Def(DefKind::Trait, def_id) => { + let edition = self.tcx.sess.edition(); + Some(Lrc::new(SyntaxExtension::glob_delegation(def_id, impl_def_id, edition))) + } + _ => None, + }, + None => self.get_macro(res).map(|macro_data| macro_data.ext.clone()), + }; + Ok((ext, res)) } pub(crate) fn finalize_macro_resolutions(&mut self, krate: &Crate) { @@ -706,14 +811,14 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { }; let macro_resolutions = mem::take(&mut self.multi_segment_macro_resolutions); - for (mut path, path_span, kind, parent_scope, initial_res) in macro_resolutions { + for (mut path, path_span, kind, parent_scope, initial_res, ns) in macro_resolutions { // FIXME: Path resolution will ICE if segment IDs present. for seg in &mut path { seg.id = None; } match self.resolve_path( &path, - Some(MacroNS), + Some(ns), &parent_scope, Some(Finalize::new(ast::CRATE_NODE_ID, path_span)), None, @@ -721,6 +826,15 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { PathResult::NonModule(path_res) if let Some(res) = path_res.full_res() => { check_consistency(self, &path, path_span, kind, initial_res, res) } + // This may be a trait for glob delegation expansions. + PathResult::Module(ModuleOrUniformRoot::Module(module)) => check_consistency( + self, + &path, + path_span, + kind, + initial_res, + module.res().unwrap(), + ), path_res @ (PathResult::NonModule(..) | PathResult::Failed { .. }) => { let mut suggestion = None; let (span, label, module) = diff --git a/tests/ui/delegation/body-identity-glob.rs b/tests/ui/delegation/body-identity-glob.rs new file mode 100644 index 0000000000000..58b644f46d617 --- /dev/null +++ b/tests/ui/delegation/body-identity-glob.rs @@ -0,0 +1,32 @@ +//@ check-pass + +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +trait Trait { + fn foo(&self) {} + fn bar(&self) {} +} + +impl Trait for u8 {} + +struct S(u8); + +mod to_import { + pub fn check(arg: &u8) -> &u8 { arg } +} + +impl Trait for S { + reuse Trait::* { + use to_import::check; + + let _arr = Some(self.0).map(|x| [x * 2; 3]); + check(&self.0) + } +} + +fn main() { + let s = S(0); + s.foo(); + s.bar(); +} diff --git a/tests/ui/delegation/empty-glob.rs b/tests/ui/delegation/empty-glob.rs new file mode 100644 index 0000000000000..d98579d897257 --- /dev/null +++ b/tests/ui/delegation/empty-glob.rs @@ -0,0 +1,11 @@ +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +trait Trait {} + +struct S; +impl S { + reuse Trait::*; //~ ERROR empty glob delegation is not supported +} + +fn main() {} diff --git a/tests/ui/delegation/empty-glob.stderr b/tests/ui/delegation/empty-glob.stderr new file mode 100644 index 0000000000000..f4d282f6a0f6c --- /dev/null +++ b/tests/ui/delegation/empty-glob.stderr @@ -0,0 +1,8 @@ +error: empty glob delegation is not supported + --> $DIR/empty-glob.rs:8:5 + | +LL | reuse Trait::*; + | ^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/delegation/glob-bad-path.rs b/tests/ui/delegation/glob-bad-path.rs new file mode 100644 index 0000000000000..7bc4f0153a308 --- /dev/null +++ b/tests/ui/delegation/glob-bad-path.rs @@ -0,0 +1,12 @@ +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +trait Trait {} +struct S; + +impl Trait for u8 { + reuse unresolved::*; //~ ERROR failed to resolve: use of undeclared crate or module `unresolved` + reuse S::*; //~ ERROR expected trait, found struct `S` +} + +fn main() {} diff --git a/tests/ui/delegation/glob-bad-path.stderr b/tests/ui/delegation/glob-bad-path.stderr new file mode 100644 index 0000000000000..0c06364b3f0ab --- /dev/null +++ b/tests/ui/delegation/glob-bad-path.stderr @@ -0,0 +1,15 @@ +error: expected trait, found struct `S` + --> $DIR/glob-bad-path.rs:9:11 + | +LL | reuse S::*; + | ^ not a trait + +error[E0433]: failed to resolve: use of undeclared crate or module `unresolved` + --> $DIR/glob-bad-path.rs:8:11 + | +LL | reuse unresolved::*; + | ^^^^^^^^^^ use of undeclared crate or module `unresolved` + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0433`. diff --git a/tests/ui/delegation/glob-glob-conflict.rs b/tests/ui/delegation/glob-glob-conflict.rs new file mode 100644 index 0000000000000..2843bf8c4934a --- /dev/null +++ b/tests/ui/delegation/glob-glob-conflict.rs @@ -0,0 +1,33 @@ +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +trait Trait1 { + fn method(&self) -> u8; +} +trait Trait2 { + fn method(&self) -> u8; +} +trait Trait { + fn method(&self) -> u8; +} + +impl Trait1 for u8 { + fn method(&self) -> u8 { 0 } +} +impl Trait1 for u16 { + fn method(&self) -> u8 { 1 } +} +impl Trait2 for u8 { + fn method(&self) -> u8 { 2 } +} + +impl Trait for u8 { + reuse Trait1::*; + reuse Trait2::*; //~ ERROR duplicate definitions with name `method` +} +impl Trait for u16 { + reuse Trait1::*; + reuse Trait1::*; //~ ERROR duplicate definitions with name `method` +} + +fn main() {} diff --git a/tests/ui/delegation/glob-glob-conflict.stderr b/tests/ui/delegation/glob-glob-conflict.stderr new file mode 100644 index 0000000000000..8c7e5a4b023c7 --- /dev/null +++ b/tests/ui/delegation/glob-glob-conflict.stderr @@ -0,0 +1,25 @@ +error[E0201]: duplicate definitions with name `method`: + --> $DIR/glob-glob-conflict.rs:26:5 + | +LL | fn method(&self) -> u8; + | ----------------------- item in trait +... +LL | reuse Trait1::*; + | ---------------- previous definition here +LL | reuse Trait2::*; + | ^^^^^^^^^^^^^^^^ duplicate definition + +error[E0201]: duplicate definitions with name `method`: + --> $DIR/glob-glob-conflict.rs:30:5 + | +LL | fn method(&self) -> u8; + | ----------------------- item in trait +... +LL | reuse Trait1::*; + | ---------------- previous definition here +LL | reuse Trait1::*; + | ^^^^^^^^^^^^^^^^ duplicate definition + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0201`. diff --git a/tests/ui/delegation/glob-glob.rs b/tests/ui/delegation/glob-glob.rs new file mode 100644 index 0000000000000..ef7f9a15e1955 --- /dev/null +++ b/tests/ui/delegation/glob-glob.rs @@ -0,0 +1,36 @@ +//@ check-pass + +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +mod inner { + pub trait TraitFoo { + fn foo(&self) -> u8; + } + pub trait TraitBar { + fn bar(&self) -> u8; + } + + impl TraitFoo for u8 { + fn foo(&self) -> u8 { 0 } + } + impl TraitBar for u8 { + fn bar(&self) -> u8 { 1 } + } +} + +trait Trait { + fn foo(&self) -> u8; + fn bar(&self) -> u8; +} + +impl Trait for u8 { + reuse inner::TraitFoo::*; + reuse inner::TraitBar::*; +} + +fn main() { + let u = 0u8; + u.foo(); + u.bar(); +} diff --git a/tests/ui/delegation/glob-non-fn.rs b/tests/ui/delegation/glob-non-fn.rs new file mode 100644 index 0000000000000..ab312d51f4981 --- /dev/null +++ b/tests/ui/delegation/glob-non-fn.rs @@ -0,0 +1,38 @@ +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +trait Trait { + fn method(&self); + const CONST: u8; + type Type; + #[allow(non_camel_case_types)] + type method; +} + +impl Trait for u8 { + fn method(&self) {} + const CONST: u8 = 0; + type Type = u8; + type method = u8; +} + +struct Good(u8); +impl Trait for Good { + reuse Trait::* { &self.0 } + // Explicit definitions for non-delegatable items. + const CONST: u8 = 0; + type Type = u8; + type method = u8; +} + +struct Bad(u8); +impl Trait for Bad { //~ ERROR not all trait items implemented, missing: `CONST`, `Type`, `method` + reuse Trait::* { &self.0 } + //~^ ERROR item `CONST` is an associated method, which doesn't match its trait `Trait` + //~| ERROR item `Type` is an associated method, which doesn't match its trait `Trait` + //~| ERROR duplicate definitions with name `method` + //~| ERROR expected function, found associated constant `Trait::CONST` + //~| ERROR expected function, found associated type `Trait::Type` +} + +fn main() {} diff --git a/tests/ui/delegation/glob-non-fn.stderr b/tests/ui/delegation/glob-non-fn.stderr new file mode 100644 index 0000000000000..4b918c53b8489 --- /dev/null +++ b/tests/ui/delegation/glob-non-fn.stderr @@ -0,0 +1,62 @@ +error[E0324]: item `CONST` is an associated method, which doesn't match its trait `Trait` + --> $DIR/glob-non-fn.rs:30:5 + | +LL | const CONST: u8; + | ---------------- item in trait +... +LL | reuse Trait::* { &self.0 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ does not match trait + +error[E0324]: item `Type` is an associated method, which doesn't match its trait `Trait` + --> $DIR/glob-non-fn.rs:30:5 + | +LL | type Type; + | ---------- item in trait +... +LL | reuse Trait::* { &self.0 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ does not match trait + +error[E0201]: duplicate definitions with name `method`: + --> $DIR/glob-non-fn.rs:30:5 + | +LL | fn method(&self); + | ----------------- item in trait +... +LL | reuse Trait::* { &self.0 } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | | + | duplicate definition + | previous definition here + +error[E0423]: expected function, found associated constant `Trait::CONST` + --> $DIR/glob-non-fn.rs:30:11 + | +LL | reuse Trait::* { &self.0 } + | ^^^^^ not a function + +error[E0423]: expected function, found associated type `Trait::Type` + --> $DIR/glob-non-fn.rs:30:11 + | +LL | reuse Trait::* { &self.0 } + | ^^^^^ + | + = note: can't use a type alias as a constructor + +error[E0046]: not all trait items implemented, missing: `CONST`, `Type`, `method` + --> $DIR/glob-non-fn.rs:29:1 + | +LL | const CONST: u8; + | --------------- `CONST` from trait +LL | type Type; + | --------- `Type` from trait +LL | #[allow(non_camel_case_types)] +LL | type method; + | ----------- `method` from trait +... +LL | impl Trait for Bad { + | ^^^^^^^^^^^^^^^^^^ missing `CONST`, `Type`, `method` in implementation + +error: aborting due to 6 previous errors + +Some errors have detailed explanations: E0046, E0201, E0324, E0423. +For more information about an error, try `rustc --explain E0046`. diff --git a/tests/ui/delegation/glob-non-impl.rs b/tests/ui/delegation/glob-non-impl.rs new file mode 100644 index 0000000000000..d523731eeb35f --- /dev/null +++ b/tests/ui/delegation/glob-non-impl.rs @@ -0,0 +1,20 @@ +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +trait Trait { + fn method() {} +} + +reuse Trait::*; //~ ERROR glob delegation is only supported in impls + +trait OtherTrait { + reuse Trait::*; //~ ERROR glob delegation is only supported in impls +} + +extern { + reuse Trait::*; //~ ERROR delegation is not supported in `extern` blocks +} + +fn main() { + reuse Trait::*; //~ ERROR glob delegation is only supported in impls +} diff --git a/tests/ui/delegation/glob-non-impl.stderr b/tests/ui/delegation/glob-non-impl.stderr new file mode 100644 index 0000000000000..ea458fd5e90fb --- /dev/null +++ b/tests/ui/delegation/glob-non-impl.stderr @@ -0,0 +1,26 @@ +error: delegation is not supported in `extern` blocks + --> $DIR/glob-non-impl.rs:15:5 + | +LL | reuse Trait::*; + | ^^^^^^^^^^^^^^^ + +error: glob delegation is only supported in impls + --> $DIR/glob-non-impl.rs:8:1 + | +LL | reuse Trait::*; + | ^^^^^^^^^^^^^^^ + +error: glob delegation is only supported in impls + --> $DIR/glob-non-impl.rs:11:5 + | +LL | reuse Trait::*; + | ^^^^^^^^^^^^^^^ + +error: glob delegation is only supported in impls + --> $DIR/glob-non-impl.rs:19:5 + | +LL | reuse Trait::*; + | ^^^^^^^^^^^^^^^ + +error: aborting due to 4 previous errors + diff --git a/tests/ui/delegation/glob-override.rs b/tests/ui/delegation/glob-override.rs new file mode 100644 index 0000000000000..1d0dcf1df6e60 --- /dev/null +++ b/tests/ui/delegation/glob-override.rs @@ -0,0 +1,37 @@ +//@ check-pass + +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +trait Trait { + fn foo(&self) -> u8; + fn bar(&self) -> u8; +} + +impl Trait for u8 { + fn foo(&self) -> u8 { 0 } + fn bar(&self) -> u8 { 1 } +} + +struct S(u8); +struct Z(u8); + +impl Trait for S { + reuse Trait::* { &self.0 } + fn bar(&self) -> u8 { 2 } +} + +impl Trait for Z { + reuse Trait::* { &self.0 } + reuse Trait::bar { &self.0 } +} + +fn main() { + let s = S(2); + s.foo(); + s.bar(); + + let z = Z(2); + z.foo(); + z.bar(); +} diff --git a/tests/ui/delegation/glob.rs b/tests/ui/delegation/glob.rs new file mode 100644 index 0000000000000..5bc80c1664897 --- /dev/null +++ b/tests/ui/delegation/glob.rs @@ -0,0 +1,35 @@ +//@ check-pass + +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +trait Trait { + fn foo(&self) -> u8; + fn bar(&self) -> u8; +} + +impl Trait for u8 { + fn foo(&self) -> u8 { 0 } + fn bar(&self) -> u8 { 1 } +} + +struct S(u8); +struct Z(u8); + +impl Trait for S { + reuse Trait::* { &self.0 } +} + +impl Trait for Z { + reuse ::* { &self.0 } +} + +fn main() { + let s = S(2); + s.foo(); + s.bar(); + + let z = Z(3); + z.foo(); + z.bar(); +} diff --git a/tests/ui/delegation/macro-inside-glob.rs b/tests/ui/delegation/macro-inside-glob.rs new file mode 100644 index 0000000000000..1d529341c5b4b --- /dev/null +++ b/tests/ui/delegation/macro-inside-glob.rs @@ -0,0 +1,26 @@ +//@ check-pass + +#![feature(fn_delegation)] +#![allow(incomplete_features)] + +trait Trait { + fn foo(&self) -> u8 { 0 } + fn bar(&self) -> u8 { 1 } +} + +impl Trait for u8 {} + +struct S(u8); + +// Macro expansion works inside delegation items. +macro_rules! u8 { () => { u8 } } +macro_rules! self_0 { ($self:ident) => { &$self.0 } } +impl Trait for S { + reuse ::* { self_0!(self) } +} + +fn main() { + let s = S(2); + s.foo(); + s.bar(); +} diff --git a/tests/ui/delegation/macro-inside-list.rs b/tests/ui/delegation/macro-inside-list.rs index 16c74d396ef00..d07a4e47dd4cd 100644 --- a/tests/ui/delegation/macro-inside-list.rs +++ b/tests/ui/delegation/macro-inside-list.rs @@ -14,9 +14,9 @@ struct S(u8); // Macro expansion works inside delegation items. macro_rules! u8 { () => { u8 } } -macro_rules! self_0 { () => { &self.0 } } +macro_rules! self_0 { ($self:ident) => { &$self.0 } } impl Trait for S { - reuse ::{foo, bar} { self_0!() } + reuse ::{foo, bar} { self_0!(self) } } fn main() { From df1d6168f4deef4954f3b74a23fe1e923d9b95b6 Mon Sep 17 00:00:00 2001 From: Jack Wrenn Date: Fri, 14 Jun 2024 17:02:07 +0000 Subject: [PATCH 08/33] safe transmute: support non-ZST, variantful, uninhabited enums Previously, `Tree::from_enum`'s implementation branched into three disjoint cases: 1. enums that uninhabited 2. enums for which all but one variant is uninhabited 3. enums with multiple inhabited variants This branching (incorrectly) did not differentiate between variantful and variantless uninhabited enums. In both cases, we assumed (and asserted) that uninhabited enums are zero-sized types. This assumption is false for enums like: enum Uninhabited { A(!, u128) } ...which, currently, has the same size as `u128`. This faulty assumption manifested as the ICE reported in #126460. In this PR, we revise the first case of `Tree::from_enum` to consider only the narrow category of "enums that are uninhabited ZSTs". These enums, whose layouts are described with `Variants::Single { index }`, are special in their layouts otherwise resemble the `!` type and cannot be descended into like typical enums. This first case captures uninhabited enums like: enum Uninhabited { A(!, !), B(!) } The second case is revised to consider the broader category of "enums that defer their layout to one of their variants"; i.e., enums whose layouts are described with `Variants::Single { index }` and that do have a variant at `index`. This second case captures uninhabited enums that are not ZSTs, like: enum Uninhabited { A(!, u128) } ...which represent their variants with `Variants::Single`. Finally, the third case is revised to cover the broader category of "enums with multiple variants", which captures uninhabited, non-ZST enums like: enum Uninhabited { A(u8, !), B(!, u32) } ...which represent their variants with `Variants::Multiple`. This PR also adds a comment requested by RalfJung in his review of #126358 to `compiler/rustc_const_eval/src/interpret/discriminant.rs`. Fixes #126460 --- .../src/interpret/discriminant.rs | 11 ++++- compiler/rustc_transmute/src/layout/tree.rs | 36 +++++++--------- .../enums/uninhabited_optimization.rs | 6 +++ tests/ui/transmutability/uninhabited.rs | 24 ++++++++++- tests/ui/transmutability/uninhabited.stderr | 42 ++++++++++++++++--- 5 files changed, 89 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/discriminant.rs b/compiler/rustc_const_eval/src/interpret/discriminant.rs index a50b50d231d78..b3a139d553ade 100644 --- a/compiler/rustc_const_eval/src/interpret/discriminant.rs +++ b/compiler/rustc_const_eval/src/interpret/discriminant.rs @@ -241,7 +241,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { variant_index: VariantIdx, ) -> InterpResult<'tcx, Option<(ScalarInt, usize)>> { match self.layout_of(ty)?.variants { - abi::Variants::Single { .. } => Ok(None), + abi::Variants::Single { .. } => { + // The tag of a `Single` enum is like the tag of the niched + // variant: there's no tag as the discriminant is encoded + // entirely implicitly. If `write_discriminant` ever hits this + // case, we do a "validation read" to ensure the the right + // discriminant is encoded implicitly, so any attempt to write + // the wrong discriminant for a `Single` enum will reliably + // result in UB. + Ok(None) + } abi::Variants::Multiple { tag_encoding: TagEncoding::Direct, diff --git a/compiler/rustc_transmute/src/layout/tree.rs b/compiler/rustc_transmute/src/layout/tree.rs index 241381f5875ed..865f9487213f8 100644 --- a/compiler/rustc_transmute/src/layout/tree.rs +++ b/compiler/rustc_transmute/src/layout/tree.rs @@ -341,37 +341,29 @@ pub(crate) mod rustc { // We consider three kinds of enums, each demanding a different // treatment of their layout computation: - // 1. enums that are uninhabited - // 2. enums for which all but one variant is uninhabited - // 3. enums with multiple inhabited variants + // 1. enums that are uninhabited ZSTs + // 2. enums that delegate their layout to a variant + // 3. enums with multiple variants match layout.variants() { - _ if layout.abi.is_uninhabited() => { - // Uninhabited enums are usually (always?) zero-sized. In - // the (unlikely?) event that an uninhabited enum is - // non-zero-sized, this assert will trigger an ICE, and this - // code should be modified such that a `layout.size` amount - // of uninhabited bytes is returned instead. - // - // Uninhabited enums are currently implemented such that - // their layout is described with `Variants::Single`, even - // though they don't necessarily have a 'single' variant to - // defer to. That said, we don't bother specifically - // matching on `Variants::Single` in this arm because the - // behavioral principles here remain true even if, for - // whatever reason, the compiler describes an uninhabited - // enum with `Variants::Multiple`. - assert_eq!(layout.size, Size::ZERO); + Variants::Single { .. } + if layout.abi.is_uninhabited() && layout.size == Size::ZERO => + { + // The layout representation of uninhabited, ZST enums is + // defined to be like that of the `!` type, as opposed of a + // typical enum. Consequently, they cannot be descended into + // as if they typical enums. We therefore special-case this + // scenario and simply return an uninhabited `Tree`. Ok(Self::uninhabited()) } Variants::Single { index } => { - // `Variants::Single` on non-uninhabited enums denotes that + // `Variants::Single` on enums with variants denotes that // the enum delegates its layout to the variant at `index`. layout_of_variant(*index) } Variants::Multiple { tag_field, .. } => { // `Variants::Multiple` denotes an enum with multiple - // inhabited variants. The layout of such an enum is the - // disjunction of the layouts of its tagged variants. + // variants. The layout of such an enum is the disjunction + // of the layouts of its tagged variants. // For enums (but not coroutines), the tag field is // currently always the first field of the layout. diff --git a/tests/ui/transmutability/enums/uninhabited_optimization.rs b/tests/ui/transmutability/enums/uninhabited_optimization.rs index 04a8eb40c8b8d..c2d5b67ab2ce4 100644 --- a/tests/ui/transmutability/enums/uninhabited_optimization.rs +++ b/tests/ui/transmutability/enums/uninhabited_optimization.rs @@ -19,8 +19,14 @@ enum SingleUninhabited { Y(Uninhabited), } +enum MultipleUninhabited { + X(u8, Uninhabited), + Y(Uninhabited, u16), +} + fn main() { assert_transmutable::(); assert_transmutable::(); assert_transmutable::(); + assert_transmutable::(); } diff --git a/tests/ui/transmutability/uninhabited.rs b/tests/ui/transmutability/uninhabited.rs index b61b110f6a11c..7524922c16a7e 100644 --- a/tests/ui/transmutability/uninhabited.rs +++ b/tests/ui/transmutability/uninhabited.rs @@ -30,7 +30,7 @@ fn void() { } // Non-ZST uninhabited types are, nonetheless, uninhabited. -fn yawning_void() { +fn yawning_void_struct() { enum Void {} struct YawningVoid(Void, u128); @@ -49,6 +49,28 @@ fn yawning_void() { assert::is_maybe_transmutable::<(), Void>(); //~ ERROR: cannot be safely transmuted } +// Non-ZST uninhabited types are, nonetheless, uninhabited. +fn yawning_void_enum() { + enum Void {} + + enum YawningVoid { + A(Void, u128), + } + + const _: () = { + assert!(std::mem::size_of::() == std::mem::size_of::()); + // Just to be sure the above constant actually evaluated: + assert!(false); //~ ERROR: evaluation of constant value failed + }; + + // This transmutation is vacuously acceptable; since one cannot construct a + // `Void`, unsoundness cannot directly arise from transmuting a void into + // anything else. + assert::is_maybe_transmutable::(); + + assert::is_maybe_transmutable::<(), Void>(); //~ ERROR: cannot be safely transmuted +} + // References to uninhabited types are, logically, uninhabited, but for layout // purposes are not ZSTs, and aren't treated as uninhabited when they appear in // enum variants. diff --git a/tests/ui/transmutability/uninhabited.stderr b/tests/ui/transmutability/uninhabited.stderr index 60219b0f263c6..88a98c798fc3d 100644 --- a/tests/ui/transmutability/uninhabited.stderr +++ b/tests/ui/transmutability/uninhabited.stderr @@ -7,10 +7,18 @@ LL | assert!(false); = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) error[E0080]: evaluation of constant value failed - --> $DIR/uninhabited.rs:65:9 + --> $DIR/uninhabited.rs:63:9 | LL | assert!(false); - | ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:65:9 + | ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:63:9 + | + = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0080]: evaluation of constant value failed + --> $DIR/uninhabited.rs:87:9 + | +LL | assert!(false); + | ^^^^^^^^^^^^^^ the evaluated program panicked at 'assertion failed: false', $DIR/uninhabited.rs:87:9 | = note: this error originates in the macro `assert` (in Nightly builds, run with -Z macro-backtrace for more info) @@ -36,11 +44,33 @@ LL | | } LL | | }> | |__________^ required by this bound in `is_maybe_transmutable` -error[E0277]: `()` cannot be safely transmuted into `yawning_void::Void` +error[E0277]: `()` cannot be safely transmuted into `yawning_void_struct::Void` --> $DIR/uninhabited.rs:49:41 | LL | assert::is_maybe_transmutable::<(), Void>(); - | ^^^^ `yawning_void::Void` is uninhabited + | ^^^^ `yawning_void_struct::Void` is uninhabited + | +note: required by a bound in `is_maybe_transmutable` + --> $DIR/uninhabited.rs:10:14 + | +LL | pub fn is_maybe_transmutable() + | --------------------- required by a bound in this function +LL | where +LL | Dst: BikeshedIntrinsicFrom + | |__________^ required by this bound in `is_maybe_transmutable` + +error[E0277]: `()` cannot be safely transmuted into `yawning_void_enum::Void` + --> $DIR/uninhabited.rs:71:41 + | +LL | assert::is_maybe_transmutable::<(), Void>(); + | ^^^^ `yawning_void_enum::Void` is uninhabited | note: required by a bound in `is_maybe_transmutable` --> $DIR/uninhabited.rs:10:14 @@ -59,7 +89,7 @@ LL | | }> | |__________^ required by this bound in `is_maybe_transmutable` error[E0277]: `u128` cannot be safely transmuted into `DistantVoid` - --> $DIR/uninhabited.rs:70:43 + --> $DIR/uninhabited.rs:92:43 | LL | assert::is_maybe_transmutable::(); | ^^^^^^^^^^^ at least one value of `u128` isn't a bit-valid value of `DistantVoid` @@ -80,7 +110,7 @@ LL | | } LL | | }> | |__________^ required by this bound in `is_maybe_transmutable` -error: aborting due to 5 previous errors +error: aborting due to 7 previous errors Some errors have detailed explanations: E0080, E0277. For more information about an error, try `rustc --explain E0080`. From 55fc6bdcb34465f75e630d471f225b6176743135 Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Wed, 12 Jun 2024 01:22:31 -0700 Subject: [PATCH 09/33] Ban `ArrayToPointer` and `MutToConstPointer` from runtime MIR Apparently MIR borrowck cares about at least one of these for checking variance. In runtime MIR, though, there's no need for them as `PtrToPtr` does the same thing. (Banning them simplifies passes like GVN that no longer need to handle multiple cast possibilities.) --- compiler/rustc_middle/src/mir/syntax.rs | 13 +++++++++++-- .../src/cleanup_post_borrowck.rs | 19 ++++++++++++++++++- compiler/rustc_mir_transform/src/gvn.rs | 10 +++------- compiler/rustc_mir_transform/src/lib.rs | 6 +++--- compiler/rustc_mir_transform/src/validate.rs | 8 +++++++- ...oxed_slice.main.GVN.32bit.panic-abort.diff | 2 +- ...xed_slice.main.GVN.32bit.panic-unwind.diff | 2 +- ...oxed_slice.main.GVN.64bit.panic-abort.diff | 2 +- ...xed_slice.main.GVN.64bit.panic-unwind.diff | 2 +- .../casts.roundtrip.InstSimplify.diff | 2 +- tests/mir-opt/instsimplify/casts.rs | 2 +- ...ated_loop.PreCodegen.after.panic-abort.mir | 2 +- ...ted_loop.PreCodegen.after.panic-unwind.mir | 2 +- ...ward_loop.PreCodegen.after.panic-abort.mir | 2 +- ...ard_loop.PreCodegen.after.panic-unwind.mir | 2 +- ...erse_loop.PreCodegen.after.panic-abort.mir | 2 +- ...rse_loop.PreCodegen.after.panic-unwind.mir | 2 +- ...fg-pre-optimizations.after.panic-abort.mir | 4 ++-- ...g-pre-optimizations.after.panic-unwind.mir | 4 ++-- 19 files changed, 58 insertions(+), 30 deletions(-) diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index ebe77a1abfd8b..837e7fdfb9d6d 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -127,6 +127,9 @@ pub enum AnalysisPhase { /// * [`StatementKind::AscribeUserType`] /// * [`StatementKind::Coverage`] with [`CoverageKind::BlockMarker`] or [`CoverageKind::SpanMarker`] /// * [`Rvalue::Ref`] with `BorrowKind::Fake` + /// * [`CastKind::PointerCoercion`] with any of the following: + /// * [`PointerCoercion::ArrayToPointer`] + /// * [`PointerCoercion::MutToConstPointer`] /// /// Furthermore, `Deref` projections must be the first projection within any place (if they /// appear at all) @@ -1281,8 +1284,7 @@ pub enum Rvalue<'tcx> { /// /// This allows for casts from/to a variety of types. /// - /// **FIXME**: Document exactly which `CastKind`s allow which types of casts. Figure out why - /// `ArrayToPointer` and `MutToConstPointer` are special. + /// **FIXME**: Document exactly which `CastKind`s allow which types of casts. Cast(CastKind, Operand<'tcx>, Ty<'tcx>), /// * `Offset` has the same semantics as [`offset`](pointer::offset), except that the second @@ -1362,6 +1364,13 @@ pub enum CastKind { PointerWithExposedProvenance, /// Pointer related casts that are done by coercions. Note that reference-to-raw-ptr casts are /// translated into `&raw mut/const *r`, i.e., they are not actually casts. + /// + /// The following are allowed in [`AnalysisPhase::Initial`] as they're needed for borrowck, + /// but after that are forbidden (including in all phases of runtime MIR): + /// * [`PointerCoercion::ArrayToPointer`] + /// * [`PointerCoercion::MutToConstPointer`] + /// + /// Both are runtime nops, so should be [`CastKind::PtrToPtr`] instead in runtime MIR. PointerCoercion(PointerCoercion), /// Cast into a dyn* object. DynStar, diff --git a/compiler/rustc_mir_transform/src/cleanup_post_borrowck.rs b/compiler/rustc_mir_transform/src/cleanup_post_borrowck.rs index 48a6a83e14609..264d8a1399604 100644 --- a/compiler/rustc_mir_transform/src/cleanup_post_borrowck.rs +++ b/compiler/rustc_mir_transform/src/cleanup_post_borrowck.rs @@ -18,7 +18,8 @@ use crate::MirPass; use rustc_middle::mir::coverage::CoverageKind; -use rustc_middle::mir::{Body, BorrowKind, Rvalue, StatementKind, TerminatorKind}; +use rustc_middle::mir::{Body, BorrowKind, CastKind, Rvalue, StatementKind, TerminatorKind}; +use rustc_middle::ty::adjustment::PointerCoercion; use rustc_middle::ty::TyCtxt; pub struct CleanupPostBorrowck; @@ -36,6 +37,22 @@ impl<'tcx> MirPass<'tcx> for CleanupPostBorrowck { CoverageKind::BlockMarker { .. } | CoverageKind::SpanMarker { .. }, ) | StatementKind::FakeRead(..) => statement.make_nop(), + StatementKind::Assign(box ( + _, + Rvalue::Cast( + ref mut cast_kind @ CastKind::PointerCoercion( + PointerCoercion::ArrayToPointer + | PointerCoercion::MutToConstPointer, + ), + .., + ), + )) => { + // BorrowCk needed to track whether these cases were coercions or casts, + // to know whether to check lifetimes in their pointees, + // but from now on that distinction doesn't matter, + // so just make them ordinary pointer casts instead. + *cast_kind = CastKind::PtrToPtr; + } _ => (), } } diff --git a/compiler/rustc_mir_transform/src/gvn.rs b/compiler/rustc_mir_transform/src/gvn.rs index 121a3b99a39ee..9fb3fa71411f9 100644 --- a/compiler/rustc_mir_transform/src/gvn.rs +++ b/compiler/rustc_mir_transform/src/gvn.rs @@ -572,11 +572,7 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { let ret = self.ecx.ptr_to_ptr(&src, to).ok()?; ret.into() } - CastKind::PointerCoercion( - ty::adjustment::PointerCoercion::MutToConstPointer - | ty::adjustment::PointerCoercion::ArrayToPointer - | ty::adjustment::PointerCoercion::UnsafeFnPointer, - ) => { + CastKind::PointerCoercion(ty::adjustment::PointerCoercion::UnsafeFnPointer) => { let src = self.evaluated[value].as_ref()?; let src = self.ecx.read_immediate(src).ok()?; let to = self.ecx.layout_of(to).ok()?; @@ -1165,10 +1161,10 @@ impl<'body, 'tcx> VnState<'body, 'tcx> { } } - if let PtrToPtr | PointerCoercion(MutToConstPointer) = kind + if let PtrToPtr = kind && let Value::Cast { kind: inner_kind, value: inner_value, from: inner_from, to: _ } = *self.get(value) - && let PtrToPtr | PointerCoercion(MutToConstPointer) = inner_kind + && let PtrToPtr = inner_kind { from = inner_from; value = inner_value; diff --git a/compiler/rustc_mir_transform/src/lib.rs b/compiler/rustc_mir_transform/src/lib.rs index 551760f4703d3..72c5f3949b924 100644 --- a/compiler/rustc_mir_transform/src/lib.rs +++ b/compiler/rustc_mir_transform/src/lib.rs @@ -49,11 +49,11 @@ mod abort_unwinding_calls; mod add_call_guards; mod add_moves_for_packed_drops; mod add_retag; +mod add_subtyping_projections; +mod check_alignment; mod check_const_item_mutation; mod check_packed_ref; -mod remove_place_mention; // This pass is public to allow external drivers to perform MIR cleanup -mod add_subtyping_projections; pub mod cleanup_post_borrowck; mod copy_prop; mod coroutine; @@ -92,6 +92,7 @@ mod prettify; mod promote_consts; mod ref_prop; mod remove_noop_landing_pads; +mod remove_place_mention; mod remove_storage_markers; mod remove_uninit_drops; mod remove_unneeded_drops; @@ -101,7 +102,6 @@ mod reveal_all; mod shim; mod ssa; // This pass is public to allow external drivers to perform MIR cleanup -mod check_alignment; pub mod simplify; mod simplify_branches; mod simplify_comparison_integral; diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index 586c125499538..61686f241d996 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -1187,6 +1187,9 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { "CastKind::{kind:?} output must be a raw const pointer, not {:?}", ty::RawPtr(_, Mutability::Not) ); + if self.mir_phase >= MirPhase::Analysis(AnalysisPhase::PostCleanup) { + self.fail(location, format!("After borrowck, MIR disallows {kind:?}")); + } } CastKind::PointerCoercion(PointerCoercion::ArrayToPointer) => { // FIXME: Check pointee types @@ -1200,6 +1203,9 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { "CastKind::{kind:?} output must be a raw pointer, not {:?}", ty::RawPtr(..) ); + if self.mir_phase >= MirPhase::Analysis(AnalysisPhase::PostCleanup) { + self.fail(location, format!("After borrowck, MIR disallows {kind:?}")); + } } CastKind::PointerCoercion(PointerCoercion::Unsize) => { // This is used for all `CoerceUnsized` types, @@ -1211,7 +1217,7 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { if !input_valid || !target_valid { self.fail( location, - format!("Wrong cast kind {kind:?} for the type {op_ty}",), + format!("Wrong cast kind {kind:?} for the type {op_ty}"), ); } } diff --git a/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.32bit.panic-abort.diff b/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.32bit.panic-abort.diff index ca445046279b8..06011f9d75967 100644 --- a/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.32bit.panic-abort.diff +++ b/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.32bit.panic-abort.diff @@ -80,7 +80,7 @@ bb4: { StorageDead(_8); -- _11 = _6 as *const [bool; 0] (PointerCoercion(MutToConstPointer)); +- _11 = _6 as *const [bool; 0] (PtrToPtr); - _5 = NonNull::<[bool; 0]> { pointer: _11 }; + _11 = const {0x1 as *const [bool; 0]}; + _5 = const NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }}; diff --git a/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.32bit.panic-unwind.diff b/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.32bit.panic-unwind.diff index 1f498c65fa780..eb4a3ffd91d2e 100644 --- a/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.32bit.panic-unwind.diff +++ b/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.32bit.panic-unwind.diff @@ -84,7 +84,7 @@ bb5: { StorageDead(_8); -- _11 = _6 as *const [bool; 0] (PointerCoercion(MutToConstPointer)); +- _11 = _6 as *const [bool; 0] (PtrToPtr); - _5 = NonNull::<[bool; 0]> { pointer: _11 }; + _11 = const {0x1 as *const [bool; 0]}; + _5 = const NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }}; diff --git a/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.64bit.panic-abort.diff b/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.64bit.panic-abort.diff index da72e581496e1..a7cc243e548e9 100644 --- a/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.64bit.panic-abort.diff +++ b/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.64bit.panic-abort.diff @@ -80,7 +80,7 @@ bb4: { StorageDead(_8); -- _11 = _6 as *const [bool; 0] (PointerCoercion(MutToConstPointer)); +- _11 = _6 as *const [bool; 0] (PtrToPtr); - _5 = NonNull::<[bool; 0]> { pointer: _11 }; + _11 = const {0x1 as *const [bool; 0]}; + _5 = const NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }}; diff --git a/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.64bit.panic-unwind.diff b/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.64bit.panic-unwind.diff index 920e66452e3b3..c905a48862caa 100644 --- a/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.64bit.panic-unwind.diff +++ b/tests/mir-opt/dataflow-const-prop/default_boxed_slice.main.GVN.64bit.panic-unwind.diff @@ -84,7 +84,7 @@ bb5: { StorageDead(_8); -- _11 = _6 as *const [bool; 0] (PointerCoercion(MutToConstPointer)); +- _11 = _6 as *const [bool; 0] (PtrToPtr); - _5 = NonNull::<[bool; 0]> { pointer: _11 }; + _11 = const {0x1 as *const [bool; 0]}; + _5 = const NonNull::<[bool; 0]> {{ pointer: {0x1 as *const [bool; 0]} }}; diff --git a/tests/mir-opt/instsimplify/casts.roundtrip.InstSimplify.diff b/tests/mir-opt/instsimplify/casts.roundtrip.InstSimplify.diff index a6d68cd4e4b8e..e87ac762dfeb2 100644 --- a/tests/mir-opt/instsimplify/casts.roundtrip.InstSimplify.diff +++ b/tests/mir-opt/instsimplify/casts.roundtrip.InstSimplify.diff @@ -14,7 +14,7 @@ StorageLive(_4); _4 = _1; _3 = move _4 as *mut u8 (PtrToPtr); - _2 = move _3 as *const u8 (PointerCoercion(MutToConstPointer)); + _2 = move _3 as *const u8 (PtrToPtr); StorageDead(_4); StorageDead(_3); - _0 = move _2 as *const u8 (PtrToPtr); diff --git a/tests/mir-opt/instsimplify/casts.rs b/tests/mir-opt/instsimplify/casts.rs index a7786fa570f1f..15ceea7671363 100644 --- a/tests/mir-opt/instsimplify/casts.rs +++ b/tests/mir-opt/instsimplify/casts.rs @@ -21,7 +21,7 @@ pub fn roundtrip(x: *const u8) -> *const u8 { // CHECK-LABEL: fn roundtrip( // CHECK: _4 = _1; // CHECK: _3 = move _4 as *mut u8 (PtrToPtr); - // CHECK: _2 = move _3 as *const u8 (PointerCoercion(MutToConstPointer)); + // CHECK: _2 = move _3 as *const u8 (PtrToPtr); x as *mut u8 as *const u8 } diff --git a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-abort.mir index 8f41fb70925f2..8eb102e68f365 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-abort.mir @@ -79,7 +79,7 @@ fn enumerated_loop(_1: &[T], _2: impl Fn(usize, &T)) -> () { _7 = _4 as *mut T (PtrToPtr); _8 = Offset(_7, _3); StorageDead(_7); - _9 = move _8 as *const T (PointerCoercion(MutToConstPointer)); + _9 = move _8 as *const T (PtrToPtr); StorageDead(_8); goto -> bb3; } diff --git a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-unwind.mir index 17cf305468e8c..f805967d64ffb 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.enumerated_loop.PreCodegen.after.panic-unwind.mir @@ -79,7 +79,7 @@ fn enumerated_loop(_1: &[T], _2: impl Fn(usize, &T)) -> () { _7 = _4 as *mut T (PtrToPtr); _8 = Offset(_7, _3); StorageDead(_7); - _9 = move _8 as *const T (PointerCoercion(MutToConstPointer)); + _9 = move _8 as *const T (PtrToPtr); StorageDead(_8); goto -> bb3; } diff --git a/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-abort.mir index 3c1bbdc87424e..6ae64200f4e7e 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-abort.mir @@ -71,7 +71,7 @@ fn forward_loop(_1: &[T], _2: impl Fn(&T)) -> () { _7 = _4 as *mut T (PtrToPtr); _8 = Offset(_7, _3); StorageDead(_7); - _9 = move _8 as *const T (PointerCoercion(MutToConstPointer)); + _9 = move _8 as *const T (PtrToPtr); StorageDead(_8); goto -> bb3; } diff --git a/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-unwind.mir index b2ec1ea7b9f2f..ac72329fcd643 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.forward_loop.PreCodegen.after.panic-unwind.mir @@ -71,7 +71,7 @@ fn forward_loop(_1: &[T], _2: impl Fn(&T)) -> () { _7 = _4 as *mut T (PtrToPtr); _8 = Offset(_7, _3); StorageDead(_7); - _9 = move _8 as *const T (PointerCoercion(MutToConstPointer)); + _9 = move _8 as *const T (PtrToPtr); StorageDead(_8); goto -> bb3; } diff --git a/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-abort.mir b/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-abort.mir index bf982f076de7a..8008336e268c5 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-abort.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-abort.mir @@ -79,7 +79,7 @@ fn reverse_loop(_1: &[T], _2: impl Fn(&T)) -> () { _7 = _4 as *mut T (PtrToPtr); _8 = Offset(_7, _3); StorageDead(_7); - _9 = move _8 as *const T (PointerCoercion(MutToConstPointer)); + _9 = move _8 as *const T (PtrToPtr); StorageDead(_8); goto -> bb3; } diff --git a/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-unwind.mir b/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-unwind.mir index 532b81625212c..47253bf7a0d34 100644 --- a/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-unwind.mir +++ b/tests/mir-opt/pre-codegen/slice_iter.reverse_loop.PreCodegen.after.panic-unwind.mir @@ -79,7 +79,7 @@ fn reverse_loop(_1: &[T], _2: impl Fn(&T)) -> () { _7 = _4 as *mut T (PtrToPtr); _8 = Offset(_7, _3); StorageDead(_7); - _9 = move _8 as *const T (PointerCoercion(MutToConstPointer)); + _9 = move _8 as *const T (PtrToPtr); StorageDead(_8); goto -> bb3; } diff --git a/tests/mir-opt/retag.array_casts.SimplifyCfg-pre-optimizations.after.panic-abort.mir b/tests/mir-opt/retag.array_casts.SimplifyCfg-pre-optimizations.after.panic-abort.mir index f9d58ea60a390..6dba667dd1557 100644 --- a/tests/mir-opt/retag.array_casts.SimplifyCfg-pre-optimizations.after.panic-abort.mir +++ b/tests/mir-opt/retag.array_casts.SimplifyCfg-pre-optimizations.after.panic-abort.mir @@ -64,7 +64,7 @@ fn array_casts() -> () { StorageLive(_4); _4 = &mut _1; _3 = &raw mut (*_4); - _2 = move _3 as *mut usize (PointerCoercion(ArrayToPointer)); + _2 = move _3 as *mut usize (PtrToPtr); StorageDead(_3); StorageDead(_4); StorageLive(_5); @@ -87,7 +87,7 @@ fn array_casts() -> () { StorageLive(_11); _11 = &_8; _10 = &raw const (*_11); - _9 = move _10 as *const usize (PointerCoercion(ArrayToPointer)); + _9 = move _10 as *const usize (PtrToPtr); StorageDead(_10); StorageDead(_11); StorageLive(_12); diff --git a/tests/mir-opt/retag.array_casts.SimplifyCfg-pre-optimizations.after.panic-unwind.mir b/tests/mir-opt/retag.array_casts.SimplifyCfg-pre-optimizations.after.panic-unwind.mir index b0b70cd5d910d..fa812002e26cb 100644 --- a/tests/mir-opt/retag.array_casts.SimplifyCfg-pre-optimizations.after.panic-unwind.mir +++ b/tests/mir-opt/retag.array_casts.SimplifyCfg-pre-optimizations.after.panic-unwind.mir @@ -64,7 +64,7 @@ fn array_casts() -> () { StorageLive(_4); _4 = &mut _1; _3 = &raw mut (*_4); - _2 = move _3 as *mut usize (PointerCoercion(ArrayToPointer)); + _2 = move _3 as *mut usize (PtrToPtr); StorageDead(_3); StorageDead(_4); StorageLive(_5); @@ -87,7 +87,7 @@ fn array_casts() -> () { StorageLive(_11); _11 = &_8; _10 = &raw const (*_11); - _9 = move _10 as *const usize (PointerCoercion(ArrayToPointer)); + _9 = move _10 as *const usize (PtrToPtr); StorageDead(_10); StorageDead(_11); StorageLive(_12); From 64bba3556db92e28bed4a0984e369068c579bfdb Mon Sep 17 00:00:00 2001 From: Scott McMurray Date: Fri, 14 Jun 2024 23:01:22 -0700 Subject: [PATCH 10/33] `bug!` more uses of these in runtime stuff --- compiler/rustc_codegen_cranelift/src/base.rs | 21 ++++++++++--------- compiler/rustc_codegen_ssa/src/mir/rvalue.rs | 11 +++++----- .../rustc_const_eval/src/interpret/cast.rs | 4 +--- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index 6d26ca0b899b2..b117dc496c2bb 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -677,21 +677,22 @@ fn codegen_stmt<'tcx>( CastKind::PointerCoercion(PointerCoercion::UnsafeFnPointer), ref operand, to_ty, - ) - | Rvalue::Cast( - CastKind::PointerCoercion(PointerCoercion::MutToConstPointer), - ref operand, - to_ty, - ) - | Rvalue::Cast( - CastKind::PointerCoercion(PointerCoercion::ArrayToPointer), - ref operand, - to_ty, ) => { let to_layout = fx.layout_of(fx.monomorphize(to_ty)); let operand = codegen_operand(fx, operand); lval.write_cvalue(fx, operand.cast_pointer_to(to_layout)); } + Rvalue::Cast( + CastKind::PointerCoercion( + PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, + ), + .., + ) => { + bug!( + "{:?} is for borrowck, and should never appear in codegen", + to_place_and_rval.1 + ); + } Rvalue::Cast( CastKind::IntToInt | CastKind::FloatToFloat diff --git a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs index ad6b3f1159dec..35925a5a682a2 100644 --- a/compiler/rustc_codegen_ssa/src/mir/rvalue.rs +++ b/compiler/rustc_codegen_ssa/src/mir/rvalue.rs @@ -456,8 +456,12 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { base::unsize_ptr(bx, lldata, operand.layout.ty, cast.ty, llextra); OperandValue::Pair(lldata, llextra) } - mir::CastKind::PointerCoercion(PointerCoercion::MutToConstPointer) - | mir::CastKind::PtrToPtr + mir::CastKind::PointerCoercion( + PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, + ) => { + bug!("{kind:?} is for borrowck, and should never appear in codegen"); + } + mir::CastKind::PtrToPtr if bx.cx().is_backend_scalar_pair(operand.layout) => { if let OperandValue::Pair(data_ptr, meta) = operand.val { @@ -477,9 +481,6 @@ impl<'a, 'tcx, Bx: BuilderMethods<'a, 'tcx>> FunctionCx<'a, 'tcx, Bx> { base::cast_to_dyn_star(bx, lldata, operand.layout, cast.ty, llextra); OperandValue::Pair(lldata, llextra) } - mir::CastKind::PointerCoercion( - PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, - ) | mir::CastKind::IntToInt | mir::CastKind::FloatToInt | mir::CastKind::FloatToFloat diff --git a/compiler/rustc_const_eval/src/interpret/cast.rs b/compiler/rustc_const_eval/src/interpret/cast.rs index 0a45bbb3edb2f..977ffbf2b087d 100644 --- a/compiler/rustc_const_eval/src/interpret/cast.rs +++ b/compiler/rustc_const_eval/src/interpret/cast.rs @@ -70,9 +70,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { CastKind::PointerCoercion( PointerCoercion::MutToConstPointer | PointerCoercion::ArrayToPointer, ) => { - // These are NOPs, but can be wide pointers. - let v = self.read_immediate(src)?; - self.write_immediate(*v, dest)?; + bug!("{cast_kind:?} casts are for borrowck only, not runtime MIR"); } CastKind::PointerCoercion(PointerCoercion::ReifyFnPointer) => { From ab0e72781f6bccb1c033a7c79edaebc6124ac2b5 Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 13 Jun 2024 17:08:04 +0200 Subject: [PATCH 11/33] Suggest standalone doctest for non-local impl defs --- compiler/rustc_lint/messages.ftl | 1 + compiler/rustc_lint/src/lints.rs | 5 ++ compiler/rustc_lint/src/non_local_def.rs | 84 ++++++++++--------- .../rustdoc-ui/doctest/auxiliary/pub_trait.rs | 1 + .../rustdoc-ui/doctest/non-local-defs-impl.rs | 31 +++++++ .../doctest/non-local-defs-impl.stdout | 37 ++++++++ 6 files changed, 121 insertions(+), 38 deletions(-) create mode 100644 tests/rustdoc-ui/doctest/auxiliary/pub_trait.rs create mode 100644 tests/rustdoc-ui/doctest/non-local-defs-impl.rs create mode 100644 tests/rustdoc-ui/doctest/non-local-defs-impl.stdout diff --git a/compiler/rustc_lint/messages.ftl b/compiler/rustc_lint/messages.ftl index 733c73bc3d078..7430f1fc13ae7 100644 --- a/compiler/rustc_lint/messages.ftl +++ b/compiler/rustc_lint/messages.ftl @@ -548,6 +548,7 @@ lint_non_local_definitions_impl = non-local `impl` definition, `impl` blocks sho .without_trait = methods and associated constants are still usable outside the current expression, only `impl Local` and `impl dyn Local` can ever be private, and only if the type is nested in the same item as the `impl` .with_trait = an `impl` is never scoped, even when it is nested inside an item, as it may impact type checking outside of that item, which can be the case if neither the trait or the self type are at the same nesting level as the `impl` .bounds = `impl` may be usable in bounds, etc. from outside the expression, which might e.g. make something constructible that previously wasn't, because it's still on a publicly-visible type + .doctest = make this doc-test a standalone test with its own `fn main() {"{"} ... {"}"}` .exception = items in an anonymous const item (`const _: () = {"{"} ... {"}"}`) are treated as in the same scope as the anonymous const's declaration .const_anon = use a const-anon item to suppress this lint diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index b377da31a581b..6cc0a81aa4f67 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1358,6 +1358,7 @@ pub enum NonLocalDefinitionsDiag { cargo_update: Option, const_anon: Option>, move_to: Option<(Span, Vec)>, + doctest: bool, may_remove: Option<(Span, String)>, has_trait: bool, self_ty_str: String, @@ -1383,6 +1384,7 @@ impl<'a> LintDiagnostic<'a, ()> for NonLocalDefinitionsDiag { cargo_update, const_anon, move_to, + doctest, may_remove, has_trait, self_ty_str, @@ -1411,6 +1413,9 @@ impl<'a> LintDiagnostic<'a, ()> for NonLocalDefinitionsDiag { } diag.span_help(ms, fluent::lint_non_local_definitions_impl_move_help); } + if doctest { + diag.help(fluent::lint_doctest); + } if let Some((span, part)) = may_remove { diag.arg("may_remove_part", part); diff --git a/compiler/rustc_lint/src/non_local_def.rs b/compiler/rustc_lint/src/non_local_def.rs index d7ffc34d824fd..423862dcdbae9 100644 --- a/compiler/rustc_lint/src/non_local_def.rs +++ b/compiler/rustc_lint/src/non_local_def.rs @@ -111,6 +111,12 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions { } }; + // determining if we are in a doctest context can't currently be determined + // by the code itself (there are no specific attributes), but fortunately rustdoc + // sets a perma-unstable env var for libtest so we just reuse that for now + let is_at_toplevel_doctest = + || self.body_depth == 2 && std::env::var("UNSTABLE_RUSTDOC_TEST_PATH").is_ok(); + match item.kind { ItemKind::Impl(impl_) => { // The RFC states: @@ -191,29 +197,6 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions { None }; - let mut collector = PathCollector { paths: Vec::new() }; - collector.visit_ty(&impl_.self_ty); - if let Some(of_trait) = &impl_.of_trait { - collector.visit_trait_ref(of_trait); - } - collector.visit_generics(&impl_.generics); - - let mut may_move: Vec = collector - .paths - .into_iter() - .filter_map(|path| { - if let Some(did) = path.res.opt_def_id() - && did_has_local_parent(did, cx.tcx, parent, parent_parent) - { - Some(cx.tcx.def_span(did)) - } else { - None - } - }) - .collect(); - may_move.sort(); - may_move.dedup(); - let const_anon = matches!(parent_def_kind, DefKind::Const | DefKind::Static { .. }) .then_some(span_for_const_anon_suggestion); @@ -248,14 +231,44 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions { } else { None }; - let move_to = if may_move.is_empty() { - ms.push_span_label( - cx.tcx.def_span(parent), - fluent::lint_non_local_definitions_impl_move_help, - ); - None + + let (doctest, move_to) = if is_at_toplevel_doctest() { + (true, None) } else { - Some((cx.tcx.def_span(parent), may_move)) + let mut collector = PathCollector { paths: Vec::new() }; + collector.visit_ty(&impl_.self_ty); + if let Some(of_trait) = &impl_.of_trait { + collector.visit_trait_ref(of_trait); + } + collector.visit_generics(&impl_.generics); + + let mut may_move: Vec = collector + .paths + .into_iter() + .filter_map(|path| { + if let Some(did) = path.res.opt_def_id() + && did_has_local_parent(did, cx.tcx, parent, parent_parent) + { + Some(cx.tcx.def_span(did)) + } else { + None + } + }) + .collect(); + may_move.sort(); + may_move.dedup(); + + let move_to = if may_move.is_empty() { + ms.push_span_label( + cx.tcx.def_span(parent), + fluent::lint_non_local_definitions_impl_move_help, + ); + None + } else { + Some((cx.tcx.def_span(parent), may_move)) + }; + + (false, move_to) }; cx.emit_span_lint( @@ -272,6 +285,7 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions { self_ty_str, of_trait_str, move_to, + doctest, may_remove, has_trait: impl_.of_trait.is_some(), }, @@ -280,12 +294,6 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions { ItemKind::Macro(_macro, MacroKind::Bang) if cx.tcx.has_attr(item.owner_id.def_id, sym::macro_export) => { - // determining we if are in a doctest context can't currently be determined - // by the code it-self (no specific attrs), but fortunatly rustdoc sets a - // perma-unstable env for libtest so we just re-use that env for now - let is_at_toplevel_doctest = - self.body_depth == 2 && std::env::var("UNSTABLE_RUSTDOC_TEST_PATH").is_ok(); - cx.emit_span_lint( NON_LOCAL_DEFINITIONS, item.span, @@ -296,8 +304,8 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions { .map(|s| s.to_ident_string()) .unwrap_or_else(|| "".to_string()), cargo_update: cargo_update(), - help: (!is_at_toplevel_doctest).then_some(()), - doctest_help: is_at_toplevel_doctest.then_some(()), + help: (!is_at_toplevel_doctest()).then_some(()), + doctest_help: is_at_toplevel_doctest().then_some(()), }, ) } diff --git a/tests/rustdoc-ui/doctest/auxiliary/pub_trait.rs b/tests/rustdoc-ui/doctest/auxiliary/pub_trait.rs new file mode 100644 index 0000000000000..0a47fdc74d721 --- /dev/null +++ b/tests/rustdoc-ui/doctest/auxiliary/pub_trait.rs @@ -0,0 +1 @@ +pub trait Trait {} diff --git a/tests/rustdoc-ui/doctest/non-local-defs-impl.rs b/tests/rustdoc-ui/doctest/non-local-defs-impl.rs new file mode 100644 index 0000000000000..c984e097c0463 --- /dev/null +++ b/tests/rustdoc-ui/doctest/non-local-defs-impl.rs @@ -0,0 +1,31 @@ +//@ check-fail +//@ edition:2018 +//@ failure-status: 101 +//@ aux-build:pub_trait.rs +//@ compile-flags: --test --test-args --test-threads=1 +//@ normalize-stdout-test: "tests/rustdoc-ui/doctest" -> "$$DIR" +//@ normalize-stdout-test "finished in \d+\.\d+s" -> "finished in $$TIME" + +#![doc(test(attr(deny(non_local_definitions))))] +#![doc(test(attr(allow(dead_code))))] + +/// This will produce a warning: +/// ```rust,no_run +/// # extern crate pub_trait; +/// # use pub_trait::Trait; +/// +/// struct Local; +/// impl Trait for &Local {} +/// ``` +/// +/// But this shoudln't produce a warning: +/// ```rust,no_run +/// # extern crate pub_trait; +/// # use pub_trait::Trait; +/// +/// struct Local; +/// impl Trait for &Local {} +/// +/// # fn main() {} +/// ``` +pub fn doctest() {} diff --git a/tests/rustdoc-ui/doctest/non-local-defs-impl.stdout b/tests/rustdoc-ui/doctest/non-local-defs-impl.stdout new file mode 100644 index 0000000000000..27797e22f8ecb --- /dev/null +++ b/tests/rustdoc-ui/doctest/non-local-defs-impl.stdout @@ -0,0 +1,37 @@ + +running 2 tests +test $DIR/non-local-defs-impl.rs - doctest (line 13) - compile ... FAILED +test $DIR/non-local-defs-impl.rs - doctest (line 22) - compile ... ok + +failures: + +---- $DIR/non-local-defs-impl.rs - doctest (line 13) stdout ---- +error: non-local `impl` definition, `impl` blocks should be written at the same level as their item + --> $DIR/non-local-defs-impl.rs:18:1 + | +LL | impl Trait for &Local {} + | ^^^^^-----^^^^^------ + | | | + | | `&'_ Local` is not local + | | help: remove `&` to make the `impl` local + | `Trait` is not local + | + = note: `impl` may be usable in bounds, etc. from outside the expression, which might e.g. make something constructible that previously wasn't, because it's still on a publicly-visible type + = note: an `impl` is never scoped, even when it is nested inside an item, as it may impact type checking outside of that item, which can be the case if neither the trait or the self type are at the same nesting level as the `impl` + = help: make this doc-test a standalone test with its own `fn main() { ... }` + = note: this lint may become deny-by-default in the edition 2024 and higher, see the tracking issue +note: the lint level is defined here + --> $DIR/non-local-defs-impl.rs:11:9 + | +LL | #![deny(non_local_definitions)] + | ^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + +Couldn't compile the test. + +failures: + $DIR/non-local-defs-impl.rs - doctest (line 13) + +test result: FAILED. 1 passed; 1 failed; 0 ignored; 0 measured; 0 filtered out; finished in $TIME + From 94c282197d7081370d3fd538c66976fb41554cf6 Mon Sep 17 00:00:00 2001 From: Urgau Date: Thu, 13 Jun 2024 19:23:30 +0200 Subject: [PATCH 12/33] Also simplify macro_rules doctest code --- compiler/rustc_lint/src/lints.rs | 13 +++++-------- compiler/rustc_lint/src/non_local_def.rs | 3 +-- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_lint/src/lints.rs b/compiler/rustc_lint/src/lints.rs index 6cc0a81aa4f67..1c983a516c853 100644 --- a/compiler/rustc_lint/src/lints.rs +++ b/compiler/rustc_lint/src/lints.rs @@ -1368,8 +1368,7 @@ pub enum NonLocalDefinitionsDiag { depth: u32, body_kind_descr: &'static str, body_name: String, - help: Option<()>, - doctest_help: Option<()>, + doctest: bool, cargo_update: Option, }, } @@ -1448,8 +1447,7 @@ impl<'a> LintDiagnostic<'a, ()> for NonLocalDefinitionsDiag { depth, body_kind_descr, body_name, - help, - doctest_help, + doctest, cargo_update, } => { diag.primary_message(fluent::lint_non_local_definitions_macro_rules); @@ -1457,11 +1455,10 @@ impl<'a> LintDiagnostic<'a, ()> for NonLocalDefinitionsDiag { diag.arg("body_kind_descr", body_kind_descr); diag.arg("body_name", body_name); - if let Some(()) = help { - diag.help(fluent::lint_help); - } - if let Some(()) = doctest_help { + if doctest { diag.help(fluent::lint_help_doctest); + } else { + diag.help(fluent::lint_help); } diag.note(fluent::lint_non_local); diff --git a/compiler/rustc_lint/src/non_local_def.rs b/compiler/rustc_lint/src/non_local_def.rs index 423862dcdbae9..48d791a385968 100644 --- a/compiler/rustc_lint/src/non_local_def.rs +++ b/compiler/rustc_lint/src/non_local_def.rs @@ -304,8 +304,7 @@ impl<'tcx> LateLintPass<'tcx> for NonLocalDefinitions { .map(|s| s.to_ident_string()) .unwrap_or_else(|| "".to_string()), cargo_update: cargo_update(), - help: (!is_at_toplevel_doctest()).then_some(()), - doctest_help: is_at_toplevel_doctest().then_some(()), + doctest: is_at_toplevel_doctest(), }, ) } From 6b84d7566eaeefe521fdba12c65a9e0b137e34a6 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 3 Mar 2024 02:22:37 +0100 Subject: [PATCH 13/33] Add tests --- tests/mir-opt/or_pattern.rs | 24 +++++ ...ut_second_or.SimplifyCfg-initial.after.mir | 100 ++++++++++++++++++ ...le_switchint.SimplifyCfg-initial.after.mir | 79 ++++++++++++++ tests/ui/or-patterns/bindings-runpass-2.rs | 1 + tests/ui/or-patterns/inner-or-pat.or3.stderr | 2 +- tests/ui/or-patterns/inner-or-pat.or4.stderr | 2 +- tests/ui/or-patterns/inner-or-pat.rs | 4 +- ...ssue-70413-no-unreachable-pat-and-guard.rs | 21 ++-- tests/ui/or-patterns/search-via-bindings.rs | 22 ++++ .../or-patterns/simplification_subtleties.rs | 11 ++ 10 files changed, 250 insertions(+), 16 deletions(-) create mode 100644 tests/mir-opt/or_pattern.rs create mode 100644 tests/mir-opt/or_pattern.shortcut_second_or.SimplifyCfg-initial.after.mir create mode 100644 tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir create mode 100644 tests/ui/or-patterns/simplification_subtleties.rs diff --git a/tests/mir-opt/or_pattern.rs b/tests/mir-opt/or_pattern.rs new file mode 100644 index 0000000000000..0ad0ce8ead1e2 --- /dev/null +++ b/tests/mir-opt/or_pattern.rs @@ -0,0 +1,24 @@ +// skip-filecheck + +// EMIT_MIR or_pattern.shortcut_second_or.SimplifyCfg-initial.after.mir +fn shortcut_second_or() { + // Check that after matching `0`, failing to match `2 | 3` skips trying to match `(1, 2 | 3)`. + match ((0, 0), 0) { + (x @ (0, _) | x @ (_, 1), y @ 2 | y @ 3) => {} + _ => {} + } +} + +// EMIT_MIR or_pattern.single_switchint.SimplifyCfg-initial.after.mir +fn single_switchint() { + // Check how many `SwitchInt`s we do. In theory a single one is necessary. + match (1, true) { + (1, true) => 1, + (2, false) => 2, + (1 | 2, true | false) => 3, + (3 | 4, true | false) => 4, + _ => 5, + }; +} + +fn main() {} diff --git a/tests/mir-opt/or_pattern.shortcut_second_or.SimplifyCfg-initial.after.mir b/tests/mir-opt/or_pattern.shortcut_second_or.SimplifyCfg-initial.after.mir new file mode 100644 index 0000000000000..56edd38a6da4c --- /dev/null +++ b/tests/mir-opt/or_pattern.shortcut_second_or.SimplifyCfg-initial.after.mir @@ -0,0 +1,100 @@ +// MIR for `shortcut_second_or` after SimplifyCfg-initial + +fn shortcut_second_or() -> () { + let mut _0: (); + let mut _1: ((i32, i32), i32); + let mut _2: (i32, i32); + let _3: (i32, i32); + let _4: i32; + scope 1 { + debug x => _3; + debug y => _4; + } + + bb0: { + StorageLive(_1); + StorageLive(_2); + _2 = (const 0_i32, const 0_i32); + _1 = (move _2, const 0_i32); + StorageDead(_2); + PlaceMention(_1); + switchInt(((_1.0: (i32, i32)).0: i32)) -> [0: bb4, otherwise: bb2]; + } + + bb1: { + _0 = const (); + goto -> bb14; + } + + bb2: { + switchInt(((_1.0: (i32, i32)).1: i32)) -> [1: bb3, otherwise: bb1]; + } + + bb3: { + switchInt((_1.1: i32)) -> [2: bb7, 3: bb8, otherwise: bb1]; + } + + bb4: { + switchInt((_1.1: i32)) -> [2: bb5, 3: bb6, otherwise: bb1]; + } + + bb5: { + falseEdge -> [real: bb10, imaginary: bb6]; + } + + bb6: { + falseEdge -> [real: bb11, imaginary: bb2]; + } + + bb7: { + falseEdge -> [real: bb12, imaginary: bb8]; + } + + bb8: { + falseEdge -> [real: bb13, imaginary: bb1]; + } + + bb9: { + _0 = const (); + StorageDead(_4); + StorageDead(_3); + goto -> bb14; + } + + bb10: { + StorageLive(_3); + _3 = (_1.0: (i32, i32)); + StorageLive(_4); + _4 = (_1.1: i32); + goto -> bb9; + } + + bb11: { + StorageLive(_3); + _3 = (_1.0: (i32, i32)); + StorageLive(_4); + _4 = (_1.1: i32); + goto -> bb9; + } + + bb12: { + StorageLive(_3); + _3 = (_1.0: (i32, i32)); + StorageLive(_4); + _4 = (_1.1: i32); + goto -> bb9; + } + + bb13: { + StorageLive(_3); + _3 = (_1.0: (i32, i32)); + StorageLive(_4); + _4 = (_1.1: i32); + goto -> bb9; + } + + bb14: { + StorageDead(_1); + return; + } +} diff --git a/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir b/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir new file mode 100644 index 0000000000000..72b1a835cf081 --- /dev/null +++ b/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir @@ -0,0 +1,79 @@ +// MIR for `single_switchint` after SimplifyCfg-initial + +fn single_switchint() -> () { + let mut _0: (); + let _1: i32; + let mut _2: (i32, bool); + + bb0: { + StorageLive(_1); + StorageLive(_2); + _2 = (const 1_i32, const true); + PlaceMention(_2); + switchInt((_2.0: i32)) -> [1: bb6, 2: bb8, otherwise: bb1]; + } + + bb1: { + switchInt((_2.0: i32)) -> [1: bb3, 2: bb3, otherwise: bb2]; + } + + bb2: { + switchInt((_2.0: i32)) -> [3: bb5, 4: bb5, otherwise: bb4]; + } + + bb3: { + falseEdge -> [real: bb12, imaginary: bb2]; + } + + bb4: { + _1 = const 5_i32; + goto -> bb14; + } + + bb5: { + falseEdge -> [real: bb13, imaginary: bb4]; + } + + bb6: { + switchInt((_2.1: bool)) -> [0: bb1, otherwise: bb7]; + } + + bb7: { + falseEdge -> [real: bb10, imaginary: bb8]; + } + + bb8: { + switchInt((_2.1: bool)) -> [0: bb9, otherwise: bb1]; + } + + bb9: { + falseEdge -> [real: bb11, imaginary: bb1]; + } + + bb10: { + _1 = const 1_i32; + goto -> bb14; + } + + bb11: { + _1 = const 2_i32; + goto -> bb14; + } + + bb12: { + _1 = const 3_i32; + goto -> bb14; + } + + bb13: { + _1 = const 4_i32; + goto -> bb14; + } + + bb14: { + StorageDead(_2); + StorageDead(_1); + _0 = const (); + return; + } +} diff --git a/tests/ui/or-patterns/bindings-runpass-2.rs b/tests/ui/or-patterns/bindings-runpass-2.rs index 657d7f1ed189f..a9ae998108405 100644 --- a/tests/ui/or-patterns/bindings-runpass-2.rs +++ b/tests/ui/or-patterns/bindings-runpass-2.rs @@ -26,5 +26,6 @@ fn main() { assert_eq!(or_at(Err(7)), 207); assert_eq!(or_at(Err(8)), 8); assert_eq!(or_at(Err(20)), 220); + assert_eq!(or_at(Err(34)), 134); assert_eq!(or_at(Err(50)), 500); } diff --git a/tests/ui/or-patterns/inner-or-pat.or3.stderr b/tests/ui/or-patterns/inner-or-pat.or3.stderr index 10ec7c202e4d6..5c522a97ccef3 100644 --- a/tests/ui/or-patterns/inner-or-pat.or3.stderr +++ b/tests/ui/or-patterns/inner-or-pat.or3.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/inner-or-pat.rs:38:54 + --> $DIR/inner-or-pat.rs:36:54 | LL | match x { | - this expression has type `&str` diff --git a/tests/ui/or-patterns/inner-or-pat.or4.stderr b/tests/ui/or-patterns/inner-or-pat.or4.stderr index 97800161d82fe..508520c823793 100644 --- a/tests/ui/or-patterns/inner-or-pat.or4.stderr +++ b/tests/ui/or-patterns/inner-or-pat.or4.stderr @@ -1,5 +1,5 @@ error[E0408]: variable `x` is not bound in all patterns - --> $DIR/inner-or-pat.rs:53:37 + --> $DIR/inner-or-pat.rs:51:37 | LL | (x @ "red" | (x @ "blue" | "red")) => { | - ^^^^^ pattern doesn't bind `x` diff --git a/tests/ui/or-patterns/inner-or-pat.rs b/tests/ui/or-patterns/inner-or-pat.rs index ceb0a8b3f7969..4d136de005357 100644 --- a/tests/ui/or-patterns/inner-or-pat.rs +++ b/tests/ui/or-patterns/inner-or-pat.rs @@ -1,7 +1,5 @@ -//@ revisions: or1 or2 or3 or4 or5 +//@ revisions: or1 or3 or4 //@ [or1] run-pass -//@ [or2] run-pass -//@ [or5] run-pass #![allow(unreachable_patterns)] #![allow(unused_variables)] diff --git a/tests/ui/or-patterns/issue-70413-no-unreachable-pat-and-guard.rs b/tests/ui/or-patterns/issue-70413-no-unreachable-pat-and-guard.rs index 7d62364a6aeec..76dc298a5c851 100644 --- a/tests/ui/or-patterns/issue-70413-no-unreachable-pat-and-guard.rs +++ b/tests/ui/or-patterns/issue-70413-no-unreachable-pat-and-guard.rs @@ -1,21 +1,20 @@ -//@ check-pass +//@ run-pass #![deny(unreachable_patterns)] fn main() { - match (3,42) { - (a,_) | (_,a) if a > 10 => {println!("{}", a)} - _ => () + match (3, 42) { + (a, _) | (_, a) if a > 10 => {} + _ => unreachable!(), } - match Some((3,42)) { - Some((a, _)) | Some((_, a)) if a > 10 => {println!("{}", a)} - _ => () - + match Some((3, 42)) { + Some((a, _)) | Some((_, a)) if a > 10 => {} + _ => unreachable!(), } - match Some((3,42)) { - Some((a, _) | (_, a)) if a > 10 => {println!("{}", a)} - _ => () + match Some((3, 42)) { + Some((a, _) | (_, a)) if a > 10 => {} + _ => unreachable!(), } } diff --git a/tests/ui/or-patterns/search-via-bindings.rs b/tests/ui/or-patterns/search-via-bindings.rs index a760112f1d421..42174bd7cef73 100644 --- a/tests/ui/or-patterns/search-via-bindings.rs +++ b/tests/ui/or-patterns/search-via-bindings.rs @@ -42,6 +42,23 @@ fn search_old_style(target: (bool, bool, bool)) -> u32 { } } +// Check that a dummy or-pattern also leads to running the guard multiple times. +fn search_with_dummy(target: (bool, bool)) -> u32 { + let x = ((false, true), (false, true), ()); + let mut guard_count = 0; + match x { + ((a, _) | (_, a), (b, _) | (_, b), _ | _) + if { + guard_count += 1; + (a, b) == target + } => + { + guard_count + } + _ => unreachable!(), + } +} + fn main() { assert_eq!(search((false, false, false)), 1); assert_eq!(search((false, false, true)), 2); @@ -60,4 +77,9 @@ fn main() { assert_eq!(search_old_style((true, false, true)), 6); assert_eq!(search_old_style((true, true, false)), 7); assert_eq!(search_old_style((true, true, true)), 8); + + assert_eq!(search_with_dummy((false, false)), 1); + assert_eq!(search_with_dummy((false, true)), 3); + assert_eq!(search_with_dummy((true, false)), 5); + assert_eq!(search_with_dummy((true, true)), 7); } diff --git a/tests/ui/or-patterns/simplification_subtleties.rs b/tests/ui/or-patterns/simplification_subtleties.rs new file mode 100644 index 0000000000000..a932bd531e6d8 --- /dev/null +++ b/tests/ui/or-patterns/simplification_subtleties.rs @@ -0,0 +1,11 @@ +//@ run-pass + +#[allow(unreachable_patterns)] +fn main() { + // Test that we don't naively sort the two `2`s together and confuse the failure paths. + match (1, true) { + (1 | 2, false | false) => unreachable!(), + (2, _) => unreachable!(), + _ => {} + } +} From e74b30e3a9f75f8854fb04a6f9b528077c5e9ec5 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 20 Mar 2024 15:53:25 +0100 Subject: [PATCH 14/33] Tweak simple or-pattern expansion --- .../rustc_mir_build/src/build/matches/mod.rs | 37 +++++++++++-------- 1 file changed, 21 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 544f27b84e90d..13e208524f156 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1364,9 +1364,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { otherwise_block: BasicBlock, candidates: &mut [&mut Candidate<'pat, 'tcx>], ) { - let mut split_or_candidate = false; - for candidate in &mut *candidates { - if let [MatchPair { test_case: TestCase::Or { .. }, .. }] = &*candidate.match_pairs { + let expand_or_pats = candidates.iter().any(|candidate| { + matches!(&*candidate.match_pairs, [MatchPair { test_case: TestCase::Or { .. }, .. }]) + }); + + ensure_sufficient_stack(|| { + if expand_or_pats { // Split a candidate in which the only match-pair is an or-pattern into multiple // candidates. This is so that // @@ -1376,30 +1379,32 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // } // // only generates a single switch. - let match_pair = candidate.match_pairs.pop().unwrap(); - self.create_or_subcandidates(candidate, match_pair); - split_or_candidate = true; - } - } - - ensure_sufficient_stack(|| { - if split_or_candidate { - // At least one of the candidates has been split into subcandidates. - // We need to change the candidate list to include those. let mut new_candidates = Vec::new(); for candidate in candidates.iter_mut() { - candidate.visit_leaves(|leaf_candidate| new_candidates.push(leaf_candidate)); + if let [MatchPair { test_case: TestCase::Or { .. }, .. }] = + &*candidate.match_pairs + { + let match_pair = candidate.match_pairs.pop().unwrap(); + self.create_or_subcandidates(candidate, match_pair); + for subcandidate in candidate.subcandidates.iter_mut() { + new_candidates.push(subcandidate); + } + } else { + new_candidates.push(candidate); + } } self.match_candidates( span, scrutinee_span, start_block, otherwise_block, - &mut *new_candidates, + new_candidates.as_mut_slice(), ); for candidate in candidates { - self.merge_trivial_subcandidates(candidate); + if !candidate.subcandidates.is_empty() { + self.merge_trivial_subcandidates(candidate); + } } } else { self.match_simplified_candidates( From 5fe2ca65cf5b2696d4d48c261cb7f6cecb2f7099 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Sun, 3 Mar 2024 01:42:18 +0100 Subject: [PATCH 15/33] Always set `otherwise_block`s --- .../rustc_mir_build/src/build/matches/mod.rs | 33 +++++++++++-------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 13e208524f156..a67c59138cd29 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1074,12 +1074,9 @@ struct Candidate<'pat, 'tcx> { // because that would break binding consistency. subcandidates: Vec>, - /// ...and the guard must be evaluated if there is one. + /// ...and if there is a guard it must be evaluated; if it's `false` then branch to `otherwise_block`. has_guard: bool, - /// If the guard is `false` then branch to `otherwise_block`. - otherwise_block: Option, - /// If the candidate matches, bindings and ascriptions must be established. extra_data: PatternExtraData<'tcx>, @@ -1090,6 +1087,9 @@ struct Candidate<'pat, 'tcx> { /// The block before the `bindings` have been established. pre_binding_block: Option, + /// The block to branch to if the guard or a nested candidate fails to match. + otherwise_block: Option, + /// The earliest block that has only candidates >= this one as descendents. Used for false /// edges, see the doc for [`Builder::match_expr`]. false_edge_start_block: Option, @@ -1500,11 +1500,9 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { candidate.pre_binding_block = Some(start_block); let otherwise_block = self.cfg.start_new_block(); - if candidate.has_guard { - // Create the otherwise block for this candidate, which is the - // pre-binding block for the next candidate. - candidate.otherwise_block = Some(otherwise_block); - } + // Create the otherwise block for this candidate, which is the + // pre-binding block for the next candidate. + candidate.otherwise_block = Some(otherwise_block); otherwise_block } @@ -1591,10 +1589,15 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { assert!(leaf_candidate.match_pairs.is_empty()); leaf_candidate.match_pairs.extend(remaining_match_pairs.iter().cloned()); let or_start = leaf_candidate.pre_binding_block.unwrap(); - // In a case like `(a | b, c | d)`, if `a` succeeds and `c | d` fails, we know `(b, - // c | d)` will fail too. If there is no guard, we skip testing of `b` by branching - // directly to `remainder_start`. If there is a guard, we have to try `(b, c | d)`. - let or_otherwise = leaf_candidate.otherwise_block.unwrap_or(remainder_start); + // In a case like `(P | Q, R | S)`, if `P` succeeds and `R | S` fails, we know `(Q, + // R | S)` will fail too. If there is no guard, we skip testing of `Q` by branching + // directly to `remainder_start`. If there is a guard, `or_otherwise` can be reached + // by guard failure as well, so we can't skip `Q`. + let or_otherwise = if leaf_candidate.has_guard { + leaf_candidate.otherwise_block.unwrap() + } else { + remainder_start + }; self.test_candidates_with_or( span, scrutinee_span, @@ -1669,6 +1672,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { subcandidate.subcandidates.is_empty() && subcandidate.extra_data.is_empty() }); if can_merge { + let mut last_otherwise = None; let any_matches = self.cfg.start_new_block(); let or_span = candidate.or_span.take().unwrap(); let source_info = self.source_info(or_span); @@ -1679,8 +1683,11 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { for subcandidate in mem::take(&mut candidate.subcandidates) { let or_block = subcandidate.pre_binding_block.unwrap(); self.cfg.goto(or_block, source_info, any_matches); + last_otherwise = subcandidate.otherwise_block; } candidate.pre_binding_block = Some(any_matches); + assert!(last_otherwise.is_some()); + candidate.otherwise_block = last_otherwise; } else { // Never subcandidates may have a set of bindings inconsistent with their siblings, // which would break later code. So we filter them out. Note that we can't filter out From 764f086f2cd7cc4e2ca9c5fd9f53ba4c0b3a1740 Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 4 Mar 2024 01:41:37 +0100 Subject: [PATCH 16/33] Use `otherwise_block` for or-pattern shortcutting --- compiler/rustc_mir_build/src/build/matches/mod.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index a67c59138cd29..a5f24ba3e5b20 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1585,18 +1585,23 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // We could add them to the or-candidates before the call to `test_or_pattern` but this // would make it impossible to detect simplifiable or-patterns. That would guarantee // exponentially large CFGs for cases like `(1 | 2, 3 | 4, ...)`. + let mut last_otherwise = None; + first_candidate.visit_leaves(|leaf_candidate| { + last_otherwise = leaf_candidate.otherwise_block; + }); first_candidate.visit_leaves(|leaf_candidate| { assert!(leaf_candidate.match_pairs.is_empty()); leaf_candidate.match_pairs.extend(remaining_match_pairs.iter().cloned()); let or_start = leaf_candidate.pre_binding_block.unwrap(); // In a case like `(P | Q, R | S)`, if `P` succeeds and `R | S` fails, we know `(Q, // R | S)` will fail too. If there is no guard, we skip testing of `Q` by branching - // directly to `remainder_start`. If there is a guard, `or_otherwise` can be reached - // by guard failure as well, so we can't skip `Q`. + // directly to `last_otherwise`. If there is a guard, + // `leaf_candidate.otherwise_block` can be reached by guard failure as well, so we + // can't skip `Q`. let or_otherwise = if leaf_candidate.has_guard { leaf_candidate.otherwise_block.unwrap() } else { - remainder_start + last_otherwise.unwrap() }; self.test_candidates_with_or( span, From ce374fcbc16f6bd9fcbb03ce1894ae13e5e3d67b Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 20 Mar 2024 01:56:24 +0100 Subject: [PATCH 17/33] Factor out `finalize_or_candidate` --- .../rustc_mir_build/src/build/matches/mod.rs | 60 +++++++++++++------ 1 file changed, 42 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index a5f24ba3e5b20..70ba83c55d7c5 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1575,21 +1575,52 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } let first_match_pair = first_candidate.match_pairs.remove(0); - let remaining_match_pairs = mem::take(&mut first_candidate.match_pairs); let remainder_start = self.cfg.start_new_block(); // Test the alternatives of this or-pattern. - self.test_or_pattern(first_candidate, start_block, remainder_start, first_match_pair); + self.test_or_pattern( + span, + scrutinee_span, + first_candidate, + start_block, + remainder_start, + first_match_pair, + ); + + // Test the remaining candidates. + self.match_candidates( + span, + scrutinee_span, + remainder_start, + otherwise_block, + remaining_candidates, + ); + } + + /// Simplify subcandidates and process any leftover match pairs. The candidate should have been + /// expanded with `create_or_subcandidates`. + fn finalize_or_candidate( + &mut self, + span: Span, + scrutinee_span: Span, + candidate: &mut Candidate<'_, 'tcx>, + ) { + if candidate.subcandidates.is_empty() { + return; + } + + self.merge_trivial_subcandidates(candidate); - if !remaining_match_pairs.is_empty() { + if !candidate.match_pairs.is_empty() { // If more match pairs remain, test them after each subcandidate. // We could add them to the or-candidates before the call to `test_or_pattern` but this // would make it impossible to detect simplifiable or-patterns. That would guarantee // exponentially large CFGs for cases like `(1 | 2, 3 | 4, ...)`. let mut last_otherwise = None; - first_candidate.visit_leaves(|leaf_candidate| { + candidate.visit_leaves(|leaf_candidate| { last_otherwise = leaf_candidate.otherwise_block; }); - first_candidate.visit_leaves(|leaf_candidate| { + let remaining_match_pairs = mem::take(&mut candidate.match_pairs); + candidate.visit_leaves(|leaf_candidate| { assert!(leaf_candidate.match_pairs.is_empty()); leaf_candidate.match_pairs.extend(remaining_match_pairs.iter().cloned()); let or_start = leaf_candidate.pre_binding_block.unwrap(); @@ -1612,20 +1643,13 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ); }); } - - // Test the remaining candidates. - self.match_candidates( - span, - scrutinee_span, - remainder_start, - otherwise_block, - remaining_candidates, - ); } #[instrument(skip(self, start_block, otherwise_block, candidate, match_pair), level = "debug")] fn test_or_pattern<'pat>( &mut self, + span: Span, + scrutinee_span: Span, candidate: &mut Candidate<'pat, 'tcx>, start_block: BasicBlock, otherwise_block: BasicBlock, @@ -1641,12 +1665,12 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { otherwise_block, &mut or_candidate_refs, ); - self.merge_trivial_subcandidates(candidate); + self.finalize_or_candidate(span, scrutinee_span, candidate); } /// Given a match-pair that corresponds to an or-pattern, expand each subpattern into a new /// subcandidate. Any candidate that has been expanded that way should be passed to - /// `merge_trivial_subcandidates` after its subcandidates have been processed. + /// `finalize_or_candidate` after its subcandidates have been processed. fn create_or_subcandidates<'pat>( &mut self, candidate: &mut Candidate<'pat, 'tcx>, @@ -1664,8 +1688,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } /// Try to merge all of the subcandidates of the given candidate into one. This avoids - /// exponentially large CFGs in cases like `(1 | 2, 3 | 4, ...)`. The or-pattern should have - /// been expanded with `create_or_subcandidates`. + /// exponentially large CFGs in cases like `(1 | 2, 3 | 4, ...)`. The candidate should have been + /// expanded with `create_or_subcandidates`. fn merge_trivial_subcandidates(&mut self, candidate: &mut Candidate<'_, 'tcx>) { if candidate.subcandidates.is_empty() || candidate.has_guard { // FIXME(or_patterns; matthewjasper) Don't give up if we have a guard. From 7b764be9f1dd4cde35c393ceaad891b7c22b2ebb Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Wed, 20 Mar 2024 15:57:51 +0100 Subject: [PATCH 18/33] Expand or-candidates mixed with candidates above We can't mix them with candidates below them, but we can mix them with candidates above. --- .../rustc_mir_build/src/build/matches/mod.rs | 176 ++++++++---------- ...le_switchint.SimplifyCfg-initial.after.mir | 40 ++-- 2 files changed, 97 insertions(+), 119 deletions(-) diff --git a/compiler/rustc_mir_build/src/build/matches/mod.rs b/compiler/rustc_mir_build/src/build/matches/mod.rs index 70ba83c55d7c5..68244136d1adf 100644 --- a/compiler/rustc_mir_build/src/build/matches/mod.rs +++ b/compiler/rustc_mir_build/src/build/matches/mod.rs @@ -1364,61 +1364,105 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { otherwise_block: BasicBlock, candidates: &mut [&mut Candidate<'pat, 'tcx>], ) { - let expand_or_pats = candidates.iter().any(|candidate| { - matches!(&*candidate.match_pairs, [MatchPair { test_case: TestCase::Or { .. }, .. }]) - }); + // We process or-patterns here. If any candidate starts with an or-pattern, we have to + // expand the or-pattern before we can proceed further. + // + // We can't expand them freely however. The rule is: if the candidate has an or-pattern as + // its only remaining match pair, we can expand it freely. If it has other match pairs, we + // can expand it but we can't process more candidates after it. + // + // If we didn't stop, the `otherwise` cases could get mixed up. E.g. in the following, + // or-pattern simplification (in `merge_trivial_subcandidates`) makes it so the `1` and `2` + // cases branch to a same block (which then tests `false`). If we took `(2, _)` in the same + // set of candidates, when we reach the block that tests `false` we don't know whether we + // came from `1` or `2`, hence we can't know where to branch on failure. + // ```ignore(illustrative) + // match (1, true) { + // (1 | 2, false) => {}, + // (2, _) => {}, + // _ => {} + // } + // ``` + // + // We therefore split the `candidates` slice in two, expand or-patterns in the first half, + // and process both halves separately. + let mut expand_until = 0; + for (i, candidate) in candidates.iter().enumerate() { + if matches!( + &*candidate.match_pairs, + [MatchPair { test_case: TestCase::Or { .. }, .. }, ..] + ) { + expand_until = i + 1; + if candidate.match_pairs.len() > 1 { + break; + } + } + } + let (candidates_to_expand, remaining_candidates) = candidates.split_at_mut(expand_until); ensure_sufficient_stack(|| { - if expand_or_pats { - // Split a candidate in which the only match-pair is an or-pattern into multiple - // candidates. This is so that - // - // match x { - // 0 | 1 => { ... }, - // 2 | 3 => { ... }, - // } - // - // only generates a single switch. - let mut new_candidates = Vec::new(); - for candidate in candidates.iter_mut() { - if let [MatchPair { test_case: TestCase::Or { .. }, .. }] = + if candidates_to_expand.is_empty() { + // No candidates start with an or-pattern, we can continue. + self.match_expanded_candidates( + span, + scrutinee_span, + start_block, + otherwise_block, + remaining_candidates, + ); + } else { + // Expand one level of or-patterns for each candidate in `candidates_to_expand`. + let mut expanded_candidates = Vec::new(); + for candidate in candidates_to_expand.iter_mut() { + if let [MatchPair { test_case: TestCase::Or { .. }, .. }, ..] = &*candidate.match_pairs { - let match_pair = candidate.match_pairs.pop().unwrap(); - self.create_or_subcandidates(candidate, match_pair); + let or_match_pair = candidate.match_pairs.remove(0); + // Expand the or-pattern into subcandidates. + self.create_or_subcandidates(candidate, or_match_pair); + // Collect the newly created subcandidates. for subcandidate in candidate.subcandidates.iter_mut() { - new_candidates.push(subcandidate); + expanded_candidates.push(subcandidate); } } else { - new_candidates.push(candidate); + expanded_candidates.push(candidate); } } + + // Process the expanded candidates. + let remainder_start = self.cfg.start_new_block(); + // There might be new or-patterns obtained from expanding the old ones, so we call + // `match_candidates` again. self.match_candidates( span, scrutinee_span, start_block, - otherwise_block, - new_candidates.as_mut_slice(), + remainder_start, + expanded_candidates.as_mut_slice(), ); - for candidate in candidates { + // Simplify subcandidates and process any leftover match pairs. + for candidate in candidates_to_expand { if !candidate.subcandidates.is_empty() { - self.merge_trivial_subcandidates(candidate); + self.finalize_or_candidate(span, scrutinee_span, candidate); } } - } else { - self.match_simplified_candidates( + + // Process the remaining candidates. + self.match_candidates( span, scrutinee_span, - start_block, + remainder_start, otherwise_block, - candidates, + remaining_candidates, ); } }); } - fn match_simplified_candidates( + /// Construct the decision tree for `candidates`. Caller must ensure that no candidate in + /// `candidates` starts with an or-pattern. + fn match_expanded_candidates( &mut self, span: Span, scrutinee_span: Span, @@ -1443,7 +1487,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { // The first candidate has satisfied all its match pairs; we link it up and continue // with the remaining candidates. start_block = self.select_matched_candidate(first, start_block); - self.match_simplified_candidates( + self.match_expanded_candidates( span, scrutinee_span, start_block, @@ -1453,7 +1497,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } candidates => { // The first candidate has some unsatisfied match pairs; we proceed to do more tests. - self.test_candidates_with_or( + self.test_candidates( span, scrutinee_span, candidates, @@ -1506,8 +1550,8 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { otherwise_block } - /// Tests a candidate where there are only or-patterns left to test, or - /// forwards to [Builder::test_candidates]. + /// Simplify subcandidates and process any leftover match pairs. The candidate should have been + /// expanded with `create_or_subcandidates`. /// /// Given a pattern `(P | Q, R | S)` we (in principle) generate a CFG like /// so: @@ -1559,45 +1603,6 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { /// | /// ... /// ``` - fn test_candidates_with_or( - &mut self, - span: Span, - scrutinee_span: Span, - candidates: &mut [&mut Candidate<'_, 'tcx>], - start_block: BasicBlock, - otherwise_block: BasicBlock, - ) { - let (first_candidate, remaining_candidates) = candidates.split_first_mut().unwrap(); - assert!(first_candidate.subcandidates.is_empty()); - if !matches!(first_candidate.match_pairs[0].test_case, TestCase::Or { .. }) { - self.test_candidates(span, scrutinee_span, candidates, start_block, otherwise_block); - return; - } - - let first_match_pair = first_candidate.match_pairs.remove(0); - let remainder_start = self.cfg.start_new_block(); - // Test the alternatives of this or-pattern. - self.test_or_pattern( - span, - scrutinee_span, - first_candidate, - start_block, - remainder_start, - first_match_pair, - ); - - // Test the remaining candidates. - self.match_candidates( - span, - scrutinee_span, - remainder_start, - otherwise_block, - remaining_candidates, - ); - } - - /// Simplify subcandidates and process any leftover match pairs. The candidate should have been - /// expanded with `create_or_subcandidates`. fn finalize_or_candidate( &mut self, span: Span, @@ -1634,40 +1639,17 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } else { last_otherwise.unwrap() }; - self.test_candidates_with_or( + self.match_candidates( span, scrutinee_span, - &mut [leaf_candidate], or_start, or_otherwise, + &mut [leaf_candidate], ); }); } } - #[instrument(skip(self, start_block, otherwise_block, candidate, match_pair), level = "debug")] - fn test_or_pattern<'pat>( - &mut self, - span: Span, - scrutinee_span: Span, - candidate: &mut Candidate<'pat, 'tcx>, - start_block: BasicBlock, - otherwise_block: BasicBlock, - match_pair: MatchPair<'pat, 'tcx>, - ) { - let or_span = match_pair.pattern.span; - self.create_or_subcandidates(candidate, match_pair); - let mut or_candidate_refs: Vec<_> = candidate.subcandidates.iter_mut().collect(); - self.match_candidates( - or_span, - or_span, - start_block, - otherwise_block, - &mut or_candidate_refs, - ); - self.finalize_or_candidate(span, scrutinee_span, candidate); - } - /// Given a match-pair that corresponds to an or-pattern, expand each subpattern into a new /// subcandidate. Any candidate that has been expanded that way should be passed to /// `finalize_or_candidate` after its subcandidates have been processed. diff --git a/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir b/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir index 72b1a835cf081..eafe95b4a11ea 100644 --- a/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir +++ b/tests/mir-opt/or_pattern.single_switchint.SimplifyCfg-initial.after.mir @@ -10,67 +10,63 @@ fn single_switchint() -> () { StorageLive(_2); _2 = (const 1_i32, const true); PlaceMention(_2); - switchInt((_2.0: i32)) -> [1: bb6, 2: bb8, otherwise: bb1]; + switchInt((_2.0: i32)) -> [1: bb2, 2: bb4, otherwise: bb1]; } bb1: { - switchInt((_2.0: i32)) -> [1: bb3, 2: bb3, otherwise: bb2]; + switchInt((_2.0: i32)) -> [3: bb8, 4: bb8, otherwise: bb7]; } bb2: { - switchInt((_2.0: i32)) -> [3: bb5, 4: bb5, otherwise: bb4]; + switchInt((_2.1: bool)) -> [0: bb6, otherwise: bb3]; } bb3: { - falseEdge -> [real: bb12, imaginary: bb2]; + falseEdge -> [real: bb9, imaginary: bb4]; } bb4: { - _1 = const 5_i32; - goto -> bb14; + switchInt((_2.1: bool)) -> [0: bb5, otherwise: bb6]; } bb5: { - falseEdge -> [real: bb13, imaginary: bb4]; + falseEdge -> [real: bb10, imaginary: bb6]; } bb6: { - switchInt((_2.1: bool)) -> [0: bb1, otherwise: bb7]; + falseEdge -> [real: bb11, imaginary: bb1]; } bb7: { - falseEdge -> [real: bb10, imaginary: bb8]; + _1 = const 5_i32; + goto -> bb13; } bb8: { - switchInt((_2.1: bool)) -> [0: bb9, otherwise: bb1]; + falseEdge -> [real: bb12, imaginary: bb7]; } bb9: { - falseEdge -> [real: bb11, imaginary: bb1]; - } - - bb10: { _1 = const 1_i32; - goto -> bb14; + goto -> bb13; } - bb11: { + bb10: { _1 = const 2_i32; - goto -> bb14; + goto -> bb13; } - bb12: { + bb11: { _1 = const 3_i32; - goto -> bb14; + goto -> bb13; } - bb13: { + bb12: { _1 = const 4_i32; - goto -> bb14; + goto -> bb13; } - bb14: { + bb13: { StorageDead(_2); StorageDead(_1); _0 = const (); From 207c5bc5a9007acd5bf3dbd58ef361bbb3ff3866 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Mon, 17 Jun 2024 08:48:49 +0300 Subject: [PATCH 19/33] override user defined channel when using precompiled rustc Signed-off-by: onur-ozkan --- src/bootstrap/src/core/config/config.rs | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/src/core/config/config.rs b/src/bootstrap/src/core/config/config.rs index a1d8ca3cbcaa7..0438dee7241fe 100644 --- a/src/bootstrap/src/core/config/config.rs +++ b/src/bootstrap/src/core/config/config.rs @@ -1718,7 +1718,23 @@ impl Config { config.omit_git_hash = omit_git_hash.unwrap_or(default); config.rust_info = GitInfo::new(config.omit_git_hash, &config.src); - if config.rust_info.is_from_tarball() && !is_user_configured_rust_channel { + // We need to override `rust.channel` if it's manually specified when using the CI rustc. + // This is because if the compiler uses a different channel than the one specified in config.toml, + // tests may fail due to using a different channel than the one used by the compiler during tests. + if let Some(commit) = &config.download_rustc_commit { + if is_user_configured_rust_channel { + println!( + "WARNING: `rust.download-rustc` is enabled. The `rust.channel` option will be overridden by the CI rustc's channel." + ); + + let channel = config + .read_file_by_commit(&PathBuf::from("src/ci/channel"), commit) + .trim() + .to_owned(); + + config.channel = channel; + } + } else if config.rust_info.is_from_tarball() && !is_user_configured_rust_channel { ci_channel.clone_into(&mut config.channel); } From 5ae2446109ccdc64921561228eb983f795705b17 Mon Sep 17 00:00:00 2001 From: onur-ozkan Date: Mon, 17 Jun 2024 08:49:36 +0300 Subject: [PATCH 20/33] simplify `Builder::doc_rust_lang_org_channel` This is already handled at the config parsing level, so we can simplify it. Signed-off-by: onur-ozkan --- src/bootstrap/src/core/builder.rs | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/src/bootstrap/src/core/builder.rs b/src/bootstrap/src/core/builder.rs index d9e4cbae17d71..73e2cebc21c15 100644 --- a/src/bootstrap/src/core/builder.rs +++ b/src/bootstrap/src/core/builder.rs @@ -1036,23 +1036,12 @@ impl<'a> Builder<'a> { } pub fn doc_rust_lang_org_channel(&self) -> String { - // When using precompiled compiler from CI, we need to use CI rustc's channel and - // ignore `rust.channel` from the configuration. Otherwise most of the rustdoc tests - // will fail due to incompatible `DOC_RUST_LANG_ORG_CHANNEL`. - let channel = if let Some(commit) = self.config.download_rustc_commit() { - self.config - .read_file_by_commit(&PathBuf::from("src/ci/channel"), commit) - .trim() - .to_owned() - } else { - match &*self.config.channel { - "stable" => &self.version, - "beta" => "beta", - "nightly" | "dev" => "nightly", - // custom build of rustdoc maybe? link to the latest stable docs just in case - _ => "stable", - } - .to_owned() + let channel = match &*self.config.channel { + "stable" => &self.version, + "beta" => "beta", + "nightly" | "dev" => "nightly", + // custom build of rustdoc maybe? link to the latest stable docs just in case + _ => "stable", }; format!("https://doc.rust-lang.org/{channel}") From 6f5b0efb28593d355a6f8bcf2eea78ff396ff1aa Mon Sep 17 00:00:00 2001 From: Mads Marquart Date: Mon, 17 Jun 2024 13:15:09 +0200 Subject: [PATCH 21/33] Add badboy and BlackHoleFox as Mac Catalyst maintainers --- src/doc/rustc/src/platform-support/apple-ios-macabi.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/doc/rustc/src/platform-support/apple-ios-macabi.md b/src/doc/rustc/src/platform-support/apple-ios-macabi.md index 278ee94b6d4e0..15ba31e0f0645 100644 --- a/src/doc/rustc/src/platform-support/apple-ios-macabi.md +++ b/src/doc/rustc/src/platform-support/apple-ios-macabi.md @@ -9,6 +9,8 @@ Apple Mac Catalyst targets. ## Target maintainers +- [@badboy](https://github.com/badboy) +- [@BlackHoleFox](https://github.com/BlackHoleFox) - [@madsmtm](https://github.com/madsmtm) ## Requirements From b7c23761d87c593d239118b647a2cfb8acd3aa28 Mon Sep 17 00:00:00 2001 From: Erick Tryzelaar Date: Fri, 31 May 2024 20:08:29 +0000 Subject: [PATCH 22/33] Sync fuchsia test runner with clang test runner This synchronizes the fuchsia test running code with the clang test runner. This brings with it: * Improved logging * Uses the fuchsia image from the SDK version * Caches the product bundle across test runs * Strips the binaries to reduce the data sent to the emulator --- src/ci/docker/scripts/fuchsia-test-runner.py | 1014 +++++++++++------- 1 file changed, 628 insertions(+), 386 deletions(-) diff --git a/src/ci/docker/scripts/fuchsia-test-runner.py b/src/ci/docker/scripts/fuchsia-test-runner.py index d791550a8db87..115ee69a5891b 100755 --- a/src/ci/docker/scripts/fuchsia-test-runner.py +++ b/src/ci/docker/scripts/fuchsia-test-runner.py @@ -8,34 +8,137 @@ """ import argparse +from concurrent.futures import ThreadPoolExecutor from dataclasses import dataclass import glob -import hashlib +import io import json +import logging import os import platform +import shlex import shutil import subprocess import sys -from typing import ClassVar, List - - -@dataclass +from pathlib import Path +from typing import ClassVar, List, Optional + + +def check_call_with_logging( + args, *, stdout_handler, stderr_handler, check=True, text=True, **kwargs +): + stdout_handler(f"Subprocess: {shlex.join(str(arg) for arg in args)}") + + with subprocess.Popen( + args, + text=text, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs, + ) as process: + with ThreadPoolExecutor(max_workers=2) as executor: + + def exhaust_pipe(handler, pipe): + for line in pipe: + handler(line.rstrip()) + + executor_out = executor.submit( + exhaust_pipe, stdout_handler, process.stdout + ) + executor_err = executor.submit( + exhaust_pipe, stderr_handler, process.stderr + ) + executor_out.result() + executor_err.result() + retcode = process.poll() + if check and retcode: + raise subprocess.CalledProcessError(retcode, process.args) + return subprocess.CompletedProcess(process.args, retcode) + + +def check_output_with_logging( + args, *, stdout_handler, stderr_handler, check=True, text=True, **kwargs +): + stdout_handler(f"Subprocess: {shlex.join(str(arg) for arg in args)}") + + buf = io.StringIO() + + with subprocess.Popen( + args, + text=text, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + **kwargs, + ) as process: + with ThreadPoolExecutor(max_workers=2) as executor: + + def exhaust_stdout(handler, buf, pipe): + for line in pipe: + handler(line.rstrip()) + buf.write(line) + buf.write("\n") + + def exhaust_stderr(handler, pipe): + for line in pipe: + handler(line.rstrip()) + + executor_out = executor.submit( + exhaust_stdout, stdout_handler, buf, process.stdout + ) + executor_err = executor.submit( + exhaust_stderr, stderr_handler, process.stderr + ) + executor_out.result() + executor_err.result() + retcode = process.poll() + if check and retcode: + raise subprocess.CalledProcessError(retcode, process.args) + + return buf.getvalue() + + +def atomic_link(link: Path, target: Path): + link_dir = link.parent + os.makedirs(link_dir, exist_ok=True) + link_file = link.name + tmp_file = link_dir.joinpath(link_file + "_tmp") + os.link(target, tmp_file) + try: + os.rename(tmp_file, link) + except Exception as e: + raise e + finally: + if tmp_file.exists(): + os.remove(tmp_file) + + +@dataclass(kw_only=True) class TestEnvironment: - rust_build_dir: str - sdk_dir: str + rust_build_dir: Path + sdk_dir: Path target: str + toolchain_dir: Path + local_pb_path: Optional[Path] + use_local_pb: bool verbose: bool = False + env_logger = logging.getLogger("env") + subprocess_logger = logging.getLogger("env.subprocess") + __tmp_dir = None + @staticmethod - def tmp_dir(): + def tmp_dir() -> Path: + if TestEnvironment.__tmp_dir: + return TestEnvironment.__tmp_dir tmp_dir = os.environ.get("TEST_TOOLCHAIN_TMP_DIR") if tmp_dir is not None: - return os.path.abspath(tmp_dir) - return os.path.join(os.path.dirname(__file__), "tmp~") + TestEnvironment.__tmp_dir = Path(tmp_dir).absolute() + else: + TestEnvironment.__tmp_dir = Path(__file__).parent.joinpath("tmp~") + return TestEnvironment.__tmp_dir @staticmethod - def triple_to_arch(triple): + def triple_to_arch(triple) -> str: if "x86_64" in triple: return "x64" elif "aarch64" in triple: @@ -44,61 +147,175 @@ def triple_to_arch(triple): raise Exception(f"Unrecognized target triple {triple}") @classmethod - def env_file_path(cls): - return os.path.join(cls.tmp_dir(), "test_env.json") + def env_file_path(cls) -> Path: + return cls.tmp_dir().joinpath("test_env.json") @classmethod def from_args(cls, args): + local_pb_path = args.local_product_bundle_path + if local_pb_path is not None: + local_pb_path = Path(local_pb_path).absolute() + return cls( - os.path.abspath(args.rust_build), - os.path.abspath(args.sdk), - args.target, + rust_build_dir=Path(args.rust_build).absolute(), + sdk_dir=Path(args.sdk).absolute(), + target=args.target, + toolchain_dir=Path(args.toolchain_dir).absolute(), + local_pb_path=local_pb_path, + use_local_pb=args.use_local_product_bundle_if_exists, verbose=args.verbose, ) @classmethod def read_from_file(cls): with open(cls.env_file_path(), encoding="utf-8") as f: - test_env = json.loads(f.read()) + test_env = json.load(f) + local_pb_path = test_env["local_pb_path"] + if local_pb_path is not None: + local_pb_path = Path(local_pb_path) + return cls( - test_env["rust_build_dir"], - test_env["sdk_dir"], - test_env["target"], + rust_build_dir=Path(test_env["rust_build_dir"]), + sdk_dir=Path(test_env["sdk_dir"]), + target=test_env["target"], + toolchain_dir=Path(test_env["toolchain_dir"]), + local_pb_path=local_pb_path, + use_local_pb=test_env["use_local_pb"], verbose=test_env["verbose"], ) + def build_id(self, binary): + llvm_readelf = Path(self.toolchain_dir).joinpath("bin", "llvm-readelf") + process = subprocess.run( + args=[ + llvm_readelf, + "-n", + "--elf-output-style=JSON", + binary, + ], + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + ) + if process.returncode: + self.env_logger.error( + f"llvm-readelf failed for binary {binary} with output {process.stdout}" + ) + raise Exception(f"Unreadable build-id for binary {binary}") + data = json.loads(process.stdout) + if len(data) != 1: + raise Exception( + f"Unreadable output from llvm-readelf for binary {binary}" + ) + notes = data[0]["Notes"] + for note in notes: + note_section = note["NoteSection"] + if note_section["Name"] == ".note.gnu.build-id": + return note_section["Note"]["Build ID"] + raise Exception(f"Build ID not found for binary {binary}") + + def generate_buildid_dir( + self, + binary: Path, + build_id_dir: Path, + build_id: str, + log_handler: logging.Logger, + ): + os.makedirs(build_id_dir, exist_ok=True) + suffix = ".debug" + # Hardlink the original binary + build_id_prefix_dir = build_id_dir.joinpath(build_id[:2]) + unstripped_binary = build_id_prefix_dir.joinpath(build_id[2:] + suffix) + build_id_prefix_dir.mkdir(parents=True, exist_ok=True) + atomic_link(unstripped_binary, binary) + assert unstripped_binary.exists() + stripped_binary = unstripped_binary.with_suffix("") + llvm_objcopy = Path(self.toolchain_dir).joinpath("bin", "llvm-objcopy") + strip_mode = "--strip-sections" + check_call_with_logging( + [ + llvm_objcopy, + strip_mode, + unstripped_binary, + stripped_binary, + ], + stdout_handler=log_handler.info, + stderr_handler=log_handler.error, + ) + return stripped_binary + def write_to_file(self): with open(self.env_file_path(), "w", encoding="utf-8") as f: - f.write(json.dumps(self.__dict__)) + local_pb_path = self.local_pb_path + if local_pb_path is not None: + local_pb_path = str(local_pb_path) + + json.dump( + { + "rust_build_dir": str(self.rust_build_dir), + "sdk_dir": str(self.sdk_dir), + "target": self.target, + "toolchain_dir": str(self.toolchain_dir), + "local_pb_path": local_pb_path, + "use_local_pb": self.use_local_pb, + "verbose": self.verbose, + }, + f, + ) - def package_server_log_path(self): - return os.path.join(self.tmp_dir(), "package_server_log") + def setup_logging(self, log_to_file=False): + fs = logging.Formatter("%(asctime)s %(levelname)s:%(name)s:%(message)s") + if log_to_file: + logfile_handler = logging.FileHandler( + self.tmp_dir().joinpath("log") + ) + logfile_handler.setLevel(logging.DEBUG) + logfile_handler.setFormatter(fs) + logging.getLogger().addHandler(logfile_handler) + stream_handler = logging.StreamHandler(sys.stdout) + stream_handler.setFormatter(fs) + if self.verbose: + stream_handler.setLevel(logging.DEBUG) + else: + stream_handler.setLevel(logging.INFO) + logging.getLogger().addHandler(stream_handler) + logging.getLogger().setLevel(logging.DEBUG) + + @property + def package_server_log_path(self) -> Path: + return self.tmp_dir().joinpath("package_server_log") + + @property + def emulator_log_path(self) -> Path: + return self.tmp_dir().joinpath("emulator_log") - def emulator_log_path(self): - return os.path.join(self.tmp_dir(), "emulator_log") + @property + def packages_dir(self) -> Path: + return self.tmp_dir().joinpath("packages") - def packages_dir(self): - return os.path.join(self.tmp_dir(), "packages") + @property + def output_dir(self) -> Path: + return self.tmp_dir().joinpath("output") - def output_dir(self): - return os.path.join(self.tmp_dir(), "output") + def read_sdk_version(self): + meta_json_path = Path(self.sdk_dir).joinpath("meta", "manifest.json") + with open(meta_json_path, encoding="utf-8") as f: + meta_json = json.load(f) + return meta_json["id"] TEST_REPO_NAME: ClassVar[str] = "rust-testing" - def repo_dir(self): - return os.path.join(self.tmp_dir(), self.TEST_REPO_NAME) + def repo_dir(self) -> Path: + return self.tmp_dir().joinpath(self.TEST_REPO_NAME) - def libs_dir(self): - return os.path.join( - self.rust_build_dir, + def libs_dir(self) -> Path: + return self.rust_build_dir.joinpath( "host", "stage2", "lib", ) - def rustlibs_dir(self): - return os.path.join( - self.libs_dir(), + def rustlibs_dir(self) -> Path: + return self.libs_dir().joinpath( "rustlib", self.target, "lib", @@ -112,8 +329,8 @@ def sdk_arch(self): return "a64" raise Exception(f"Unrecognized host architecture {machine}") - def tool_path(self, tool): - return os.path.join(self.sdk_dir, "tools", self.sdk_arch(), tool) + def tool_path(self, tool) -> Path: + return Path(self.sdk_dir).joinpath("tools", self.sdk_arch(), tool) def host_arch_triple(self): machine = platform.machine() @@ -123,45 +340,25 @@ def host_arch_triple(self): return "aarch64-unknown-linux-gnu" raise Exception(f"Unrecognized host architecture {machine}") - def zxdb_script_path(self): - return os.path.join(self.tmp_dir(), "zxdb_script") - - def pm_lockfile_path(self): - return os.path.join(self.tmp_dir(), "pm.lock") - - def log_info(self, msg): - print(msg) - - def log_debug(self, msg): - if self.verbose: - print(msg) - - def subprocess_output(self): - if self.verbose: - return sys.stdout - return subprocess.DEVNULL - - def check_call(self, args, **kwargs): - self.log_info(f"Running: {' '.join(args)}") - return subprocess.check_call(args, **kwargs) - - def check_output(self, args, **kwargs): - self.log_info(f"Running: {' '.join(args)}") - return subprocess.check_output(args, **kwargs) + def zxdb_script_path(self) -> Path: + return Path(self.tmp_dir(), "zxdb_script") + @property def ffx_daemon_log_path(self): - return os.path.join(self.tmp_dir(), "ffx_daemon_log") + return self.tmp_dir().joinpath("ffx_daemon_log") + @property def ffx_isolate_dir(self): - return os.path.join(self.tmp_dir(), "ffx_isolate") + return self.tmp_dir().joinpath("ffx_isolate") + @property def home_dir(self): - return os.path.join(self.tmp_dir(), "user-home") + return self.tmp_dir().joinpath("user-home") def start_ffx_isolation(self): # Most of this is translated directly from ffx's isolate library - os.mkdir(self.ffx_isolate_dir()) - os.mkdir(self.home_dir()) + os.mkdir(self.ffx_isolate_dir) + os.mkdir(self.home_dir) ffx_path = self.tool_path("ffx") ffx_env = self.ffx_cmd_env() @@ -170,7 +367,7 @@ def start_ffx_isolation(self): # We want this to be a long-running process that persists after the script finishes # pylint: disable=consider-using-with with open( - self.ffx_daemon_log_path(), "w", encoding="utf-8" + self.ffx_daemon_log_path, "w", encoding="utf-8" ) as ffx_daemon_log_file: subprocess.Popen( [ @@ -184,7 +381,7 @@ def start_ffx_isolation(self): ) # Disable analytics - self.check_call( + check_call_with_logging( [ ffx_path, "config", @@ -192,8 +389,8 @@ def start_ffx_isolation(self): "disable", ], env=ffx_env, - stdout=self.subprocess_output(), - stderr=self.subprocess_output(), + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, ) # Set configs @@ -203,7 +400,7 @@ def start_ffx_isolation(self): "test.experimental_structured_output": "true", } for key, value in configs.items(): - self.check_call( + check_call_with_logging( [ ffx_path, "config", @@ -212,14 +409,14 @@ def start_ffx_isolation(self): value, ], env=ffx_env, - stdout=self.subprocess_output(), - stderr=self.subprocess_output(), + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, ) def ffx_cmd_env(self): return { - "HOME": self.home_dir(), - "FFX_ISOLATE_DIR": self.ffx_isolate_dir(), + "HOME": self.home_dir, + "FFX_ISOLATE_DIR": self.ffx_isolate_dir, # We want to use our own specified temp directory "TMP": self.tmp_dir(), "TEMP": self.tmp_dir(), @@ -228,16 +425,15 @@ def ffx_cmd_env(self): } def stop_ffx_isolation(self): - self.check_call( + check_call_with_logging( [ self.tool_path("ffx"), "daemon", "stop", - "-w", ], env=self.ffx_cmd_env(), - stdout=self.subprocess_output(), - stderr=self.subprocess_output(), + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, ) def start(self): @@ -256,22 +452,23 @@ def start(self): """ # Initialize temp directory - if not os.path.exists(self.tmp_dir()): - os.mkdir(self.tmp_dir()) - elif len(os.listdir(self.tmp_dir())) != 0: - raise Exception(f"Temp directory is not clean (in {self.tmp_dir()})") - - os.mkdir(self.output_dir()) + os.makedirs(self.tmp_dir(), exist_ok=True) + if len(os.listdir(self.tmp_dir())) != 0: + raise Exception( + f"Temp directory is not clean (in {self.tmp_dir()})" + ) + self.setup_logging(log_to_file=True) + os.mkdir(self.output_dir) ffx_path = self.tool_path("ffx") ffx_env = self.ffx_cmd_env() # Start ffx isolation - self.log_info("Starting ffx isolation...") + self.env_logger.info("Starting ffx isolation...") self.start_ffx_isolation() # Stop any running emulators (there shouldn't be any) - self.check_call( + check_call_with_logging( [ ffx_path, "emu", @@ -279,79 +476,95 @@ def start(self): "--all", ], env=ffx_env, - stdout=self.subprocess_output(), - stderr=self.subprocess_output(), + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, ) - # Look up the product bundle transfer manifest. - self.log_info("Looking up the product bundle transfer manifest...") - product_name = "minimal." + self.triple_to_arch(self.target) - fuchsia_version = "21.20240610.2.1" + if not self.local_pb_path: + self.local_pb_path = os.path.join(self.tmp_dir(), "local_pb") + else: + self.local_pb_path = os.path.abspath(self.local_pb_path) - out = self.check_output( - [ - ffx_path, - "--machine", - "json", - "product", - "lookup", - product_name, - fuchsia_version, - "--base-url", - "gs://fuchsia/development/" + fuchsia_version, - ], - env=ffx_env, - stderr=self.subprocess_output(), - ) + if self.use_local_pb and os.path.exists(self.local_pb_path): + self.env_logger.info( + 'Using existing emulator image at "%s"' % self.local_pb_path + ) + else: + shutil.rmtree(self.local_pb_path, ignore_errors=True) - self.log_debug(out) + # Look up the product bundle transfer manifest. + self.env_logger.info( + "Looking up the product bundle transfer manifest..." + ) + product_name = "minimal." + self.triple_to_arch(self.target) + sdk_version = self.read_sdk_version() - try: - transfer_manifest_url = json.loads(out)["transfer_manifest_url"] - except Exception as e: - print(e) - raise Exception("Unable to parse transfer manifest") from e + output = check_output_with_logging( + [ + ffx_path, + "--machine", + "json", + "product", + "lookup", + product_name, + sdk_version, + "--base-url", + "gs://fuchsia/development/" + sdk_version, + ], + env=ffx_env, + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, + ) - # Download the product bundle. - product_bundle_dir = os.path.join(self.tmp_dir(), 'product-bundle') - self.check_call( - [ - ffx_path, - "product", - "download", - transfer_manifest_url, - product_bundle_dir, - "--force", - ], - env=ffx_env, - stdout=self.subprocess_output(), - stderr=self.subprocess_output(), - ) + try: + transfer_manifest_url = json.loads(output)[ + "transfer_manifest_url" + ] + except Exception as e: + print(e) + raise Exception("Unable to parse transfer manifest") from e + + # Download the product bundle. + self.env_logger.info("Downloading the product bundle...") + check_call_with_logging( + [ + ffx_path, + "product", + "download", + transfer_manifest_url, + self.local_pb_path, + ], + env=ffx_env, + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, + ) # Start emulator + self.env_logger.info("Starting emulator...") + # FIXME: condition --accel hyper on target arch matching host arch - self.check_call( + check_call_with_logging( [ ffx_path, "emu", "start", - product_bundle_dir, + self.local_pb_path, "--headless", "--log", - self.emulator_log_path(), + self.emulator_log_path, "--net", - "tap", + "auto", "--accel", - "hyper", + "auto", ], env=ffx_env, - stdout=self.subprocess_output(), - stderr=self.subprocess_output(), + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, ) # Create new package repo - self.log_info("Creating package repo...") - self.check_call( + self.env_logger.info("Creating package repo...") + check_call_with_logging( [ ffx_path, "repository", @@ -359,11 +572,12 @@ def start(self): self.repo_dir(), ], env=ffx_env, - stdout=self.subprocess_output(), - stderr=self.subprocess_output(), + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, ) - self.check_call( + # Add repository + check_call_with_logging( [ ffx_path, "repository", @@ -373,15 +587,12 @@ def start(self): self.repo_dir(), ], env=ffx_env, - stdout=self.subprocess_output(), - stderr=self.subprocess_output(), + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, ) - # Write to file - self.write_to_file() - # Start repository server - self.check_call( + check_call_with_logging( [ ffx_path, "repository", @@ -391,12 +602,12 @@ def start(self): "[::]:0", ], env=ffx_env, - stdout=self.subprocess_output(), - stderr=self.subprocess_output(), + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, ) # Register with newly-started emulator - self.check_call( + check_call_with_logging( [ ffx_path, "target", @@ -406,11 +617,14 @@ def start(self): self.TEST_REPO_NAME, ], env=ffx_env, - stdout=self.subprocess_output(), - stderr=self.subprocess_output(), + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, ) - self.log_info("Success! Your environment is ready to run tests.") + # Write to file + self.write_to_file() + + self.env_logger.info("Success! Your environment is ready to run tests.") # FIXME: shardify this # `facet` statement required for TCP testing via @@ -481,7 +695,7 @@ def run(self, args): - Forward the test's stdout and stderr as this script's stdout and stderr """ - bin_path = os.path.abspath(args.bin_path) + bin_path = Path(args.bin_path).absolute() # Find libstd and libtest libstd_paths = glob.glob(os.path.join(self.rustlibs_dir(), "libstd-*.so")) @@ -490,233 +704,240 @@ def run(self, args): if not libstd_paths: raise Exception(f"Failed to locate libstd (in {self.rustlibs_dir()})") - # Build a unique, deterministic name for the test using the name of the - # binary and the last 6 hex digits of the hash of the full path - def path_checksum(path): - m = hashlib.sha256() - m.update(path.encode("utf-8")) - return m.hexdigest()[0:6] - base_name = os.path.basename(os.path.dirname(args.bin_path)) exe_name = base_name.lower().replace(".", "_") - package_name = f"{exe_name}_{path_checksum(bin_path)}" - - package_dir = os.path.join(self.packages_dir(), package_name) - cml_path = os.path.join(package_dir, "meta", f"{package_name}.cml") - cm_path = os.path.join(package_dir, "meta", f"{package_name}.cm") - manifest_path = os.path.join(package_dir, f"{package_name}.manifest") - manifest_json_path = os.path.join(package_dir, "package_manifest.json") - far_path = os.path.join(package_dir, f"{package_name}-0.far") + build_id = self.build_id(bin_path) + package_name = f"{exe_name}_{build_id}" + + package_dir = self.packages_dir.joinpath(package_name) + package_dir.mkdir(parents=True, exist_ok=True) + meta_dir = package_dir.joinpath("meta") + meta_dir.mkdir(parents=True, exist_ok=True) + meta_package_path = meta_dir.joinpath("package") + cml_path = meta_dir.joinpath(f"{package_name}.cml") + cm_path = meta_dir.joinpath(f"{package_name}.cm") + manifest_path = package_dir.joinpath(f"{package_name}.manifest") shared_libs = args.shared_libs[: args.n] arguments = args.shared_libs[args.n :] - test_output_dir = os.path.join(self.output_dir(), package_name) + test_output_dir = self.output_dir.joinpath(package_name) # Clean and create temporary output directory - if os.path.exists(test_output_dir): + if test_output_dir.exists(): shutil.rmtree(test_output_dir) - - os.mkdir(test_output_dir) + test_output_dir.mkdir(parents=True) # Open log file - log_path = os.path.join(test_output_dir, "log") - with open(log_path, "w", encoding="utf-8") as log_file: - - def log(msg): - print(msg, file=log_file) - log_file.flush() + runner_logger = logging.getLogger(f"env.package.{package_name}") + runner_logger.setLevel(logging.DEBUG) + logfile_handler = logging.FileHandler(test_output_dir.joinpath("log")) + logfile_handler.setLevel(logging.DEBUG) + logfile_handler.setFormatter( + logging.Formatter("%(levelname)s:%(name)s:%(message)s") + ) + runner_logger.addHandler(logfile_handler) + + runner_logger.info(f"Bin path: {bin_path}") + runner_logger.info("Setting up package...") + + # Link binary to build-id dir and strip it. + build_id_dir = self.tmp_dir().joinpath(".build-id") + stripped_binary = self.generate_buildid_dir( + binary=bin_path, + build_id_dir=build_id_dir, + build_id=build_id, + log_handler=runner_logger, + ) + runner_logger.info(f"Stripped Bin path: {stripped_binary}") - log(f"Bin path: {bin_path}") + runner_logger.info("Writing CML...") - log("Writing CML...") + # Write and compile CML + with open(cml_path, "w", encoding="utf-8") as cml: + # Collect environment variables + env_vars = "" + for var_name in self.TEST_ENV_VARS: + var_value = os.getenv(var_name) + if var_value is not None: + env_vars += f'\n "{var_name}={var_value}",' - # Write and compile CML - with open(cml_path, "w", encoding="utf-8") as cml: - # Collect environment variables - env_vars = "" - for var_name in self.TEST_ENV_VARS: - var_value = os.getenv(var_name) - if var_value is not None: - env_vars += f'\n "{var_name}={var_value}",' + # Default to no backtrace for test suite + if os.getenv("RUST_BACKTRACE") is None: + env_vars += '\n "RUST_BACKTRACE=0",' - # Default to no backtrace for test suite - if os.getenv("RUST_BACKTRACE") is None: - env_vars += '\n "RUST_BACKTRACE=0",' + # Use /tmp as the test temporary directory + env_vars += '\n "RUST_TEST_TMPDIR=/tmp",' - # Use /tmp as the test temporary directory - env_vars += '\n "RUST_TEST_TMPDIR=/tmp",' + cml.write( + self.CML_TEMPLATE.format(env_vars=env_vars, exe_name=exe_name) + ) - cml.write( - self.CML_TEMPLATE.format(env_vars=env_vars, exe_name=exe_name) - ) + runner_logger.info("Compiling CML...") - log("Compiling CML...") + check_call_with_logging( + [ + self.tool_path("cmc"), + "compile", + cml_path, + "--includepath", + ".", + "--output", + cm_path, + ], + stdout_handler=runner_logger.info, + stderr_handler=runner_logger.warning, + ) - self.check_call( - [ - self.tool_path("cmc"), - "compile", - cml_path, - "--includepath", - ".", - "--output", - cm_path, - ], - stdout=log_file, - stderr=log_file, + runner_logger.info("Writing meta/package...") + with open(meta_package_path, "w", encoding="utf-8") as f: + json.dump({"name": package_name, "version": "0"}, f) + + runner_logger.info("Writing manifest...") + + # Write package manifest + with open(manifest_path, "w", encoding="utf-8") as manifest: + manifest.write( + self.MANIFEST_TEMPLATE.format( + bin_path=stripped_binary, + exe_name=exe_name, + package_dir=package_dir, + package_name=package_name, + target=self.target, + sdk_dir=self.sdk_dir, + libstd_name=os.path.basename(libstd_paths[0]), + libstd_path=libstd_paths[0], + target_arch=self.triple_to_arch(self.target), + ) ) - - log("Writing manifest...") - - # Write, build, and archive manifest - with open(manifest_path, "w", encoding="utf-8") as manifest: + # `libtest`` was historically a shared library, but now seems to be (sometimes?) + # statically linked. If we find it as a shared library, include it in the manifest. + if libtest_paths: manifest.write( - self.MANIFEST_TEMPLATE.format( - bin_path=bin_path, - exe_name=exe_name, - package_dir=package_dir, - package_name=package_name, - target=self.target, - sdk_dir=self.sdk_dir, - libstd_name=os.path.basename(libstd_paths[0]), - libstd_path=libstd_paths[0], - target_arch=self.triple_to_arch(self.target), - ) + f"lib/{os.path.basename(libtest_paths[0])}={libtest_paths[0]}\n" ) - # `libtest`` was historically a shared library, but now seems to be (sometimes?) - # statically linked. If we find it as a shared library, include it in the manifest. - if libtest_paths: - manifest.write( - f"lib/{os.path.basename(libtest_paths[0])}={libtest_paths[0]}\n" - ) - for shared_lib in shared_libs: - manifest.write(f"lib/{os.path.basename(shared_lib)}={shared_lib}\n") - - log("Determining API level...") - out = self.check_output( - [ - self.tool_path("ffx"), - "--machine", - "json", - "version", - ], - env=self.ffx_cmd_env(), - stderr=log_file, - ) - api_level = json.loads(out)["tool_version"]["api_level"] + for shared_lib in shared_libs: + manifest.write(f"lib/{os.path.basename(shared_lib)}={shared_lib}\n") - log("Compiling and archiving manifest...") + runner_logger.info("Determining API level...") + out = check_output_with_logging( + [ + self.tool_path("ffx"), + "--machine", + "json", + "version", + ], + env=self.ffx_cmd_env(), + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, + ) + api_level = json.loads(out)["tool_version"]["api_level"] - self.check_call( - [ - self.tool_path("ffx"), - "package", - "build", - manifest_path, - "-o", - package_dir, - "--api-level", - str(api_level), - ], - env=self.ffx_cmd_env(), - stdout=log_file, - stderr=log_file, - ) + runner_logger.info("Compiling manifest...") - self.check_call( - [ - self.tool_path("ffx"), - "package", - "archive", - "create", - "-o", - far_path, - manifest_json_path, - ], - env=self.ffx_cmd_env(), - stdout=log_file, - stderr=log_file, - ) + check_call_with_logging( + [ + self.tool_path("ffx"), + "package", + "build", + manifest_path, + "-o", + package_dir, + "--api-level", + str(api_level), + ], + env=self.ffx_cmd_env(), + stdout_handler=runner_logger.info, + stderr_handler=runner_logger.warning, + ) - log("Publishing package to repo...") + runner_logger.info("Publishing package to repo...") - # Publish package to repo - self.check_call( - [ - self.tool_path("ffx"), - "repository", - "publish", - "--package", - os.path.join(package_dir, "package_manifest.json"), - self.repo_dir(), - ], - stdout=log_file, - stderr=log_file, - ) + # Publish package to repo + check_call_with_logging( + [ + self.tool_path("ffx"), + "repository", + "publish", + "--package", + os.path.join(package_dir, "package_manifest.json"), + self.repo_dir(), + ], + env=self.ffx_cmd_env(), + stdout_handler=runner_logger.info, + stderr_handler=runner_logger.warning, + ) - log("Running ffx test...") + runner_logger.info("Running ffx test...") - # Run test on emulator - subprocess.run( - [ - self.tool_path("ffx"), - "test", - "run", - f"fuchsia-pkg://{self.TEST_REPO_NAME}/{package_name}#meta/{package_name}.cm", - "--min-severity-logs", - "TRACE", - "--output-directory", - test_output_dir, - "--", - ] - + arguments, - env=self.ffx_cmd_env(), - check=False, - stdout=log_file, - stderr=log_file, - ) + # Run test on emulator + check_call_with_logging( + [ + self.tool_path("ffx"), + "test", + "run", + f"fuchsia-pkg://{self.TEST_REPO_NAME}/{package_name}#meta/{package_name}.cm", + "--min-severity-logs", + "TRACE", + "--output-directory", + test_output_dir, + "--", + ] + + arguments, + env=self.ffx_cmd_env(), + check=False, + stdout_handler=runner_logger.info, + stderr_handler=runner_logger.warning, + ) - log("Reporting test suite output...") + runner_logger.info("Reporting test suite output...") - # Read test suite output - run_summary_path = os.path.join(test_output_dir, "run_summary.json") - if os.path.exists(run_summary_path): - with open(run_summary_path, encoding="utf-8") as f: - run_summary = json.loads(f.read()) + # Read test suite output + run_summary_path = test_output_dir.joinpath("run_summary.json") + if not run_summary_path.exists(): + runner_logger.error("Failed to open test run summary") + return 254 - suite = run_summary["data"]["suites"][0] - case = suite["cases"][0] + with open(run_summary_path, encoding="utf-8") as f: + run_summary = json.load(f) - return_code = 0 if case["outcome"] == "PASSED" else 1 + suite = run_summary["data"]["suites"][0] + case = suite["cases"][0] - artifacts = case["artifacts"] - artifact_dir = case["artifact_dir"] - stdout_path = None - stderr_path = None + return_code = 0 if case["outcome"] == "PASSED" else 1 - for path, artifact in artifacts.items(): - artifact_path = os.path.join(test_output_dir, artifact_dir, path) - artifact_type = artifact["artifact_type"] + artifacts = case["artifacts"] + artifact_dir = case["artifact_dir"] + stdout_path = None + stderr_path = None - if artifact_type == "STDERR": - stderr_path = artifact_path - elif artifact_type == "STDOUT": - stdout_path = artifact_path + for path, artifact in artifacts.items(): + artifact_path = os.path.join(test_output_dir, artifact_dir, path) + artifact_type = artifact["artifact_type"] - if stdout_path is not None and os.path.exists(stdout_path): - with open(stdout_path, encoding="utf-8") as f: - print(f.read(), file=sys.stdout, end="") + if artifact_type == "STDERR": + stderr_path = artifact_path + elif artifact_type == "STDOUT": + stdout_path = artifact_path - if stderr_path is not None and os.path.exists(stderr_path): - with open(stderr_path, encoding="utf-8") as f: - print(f.read(), file=sys.stderr, end="") + if stdout_path is not None: + if not os.path.exists(stdout_path): + runner_logger.error( + f"stdout file {stdout_path} does not exist." + ) else: - log("Failed to open test run summary") - return_code = 254 - - log("Done!") + with open(stdout_path, encoding="utf-8", errors="ignore") as f: + runner_logger.info(f.read()) + if stderr_path is not None: + if not os.path.exists(stderr_path): + runner_logger.error( + f"stderr file {stderr_path} does not exist." + ) + else: + with open(stderr_path, encoding="utf-8", errors="ignore") as f: + runner_logger.error(f.read()) + runner_logger.info("Done!") return return_code def stop(self): @@ -730,65 +951,65 @@ def stop(self): During cleanup, this function will stop the emulator, package server, and update server, then delete all temporary files. If an error is encountered while stopping any running processes, the temporary files will not be deleted. - Passing --delete-tmp will force the process to delete the files anyway. + Passing --cleanup will force the process to delete the files anyway. """ - self.log_debug("Reporting logs...") + self.env_logger.debug("Reporting logs...") # Print test log files - for test_dir in os.listdir(self.output_dir()): - log_path = os.path.join(self.output_dir(), test_dir, "log") - self.log_debug(f"\n---- Logs for test '{test_dir}' ----\n") + for test_dir in os.listdir(self.output_dir): + log_path = os.path.join(self.output_dir, test_dir, "log") + self.env_logger.debug(f"\n---- Logs for test '{test_dir}' ----\n") if os.path.exists(log_path): - with open(log_path, encoding="utf-8") as log: - self.log_debug(log.read()) + with open(log_path, encoding="utf-8", errors="ignore") as log: + self.env_logger.debug(log.read()) else: - self.log_debug("No logs found") + self.env_logger.debug("No logs found") # Print the emulator log - self.log_debug("\n---- Emulator logs ----\n") - if os.path.exists(self.emulator_log_path()): - with open(self.emulator_log_path(), encoding="utf-8") as log: - self.log_debug(log.read()) + self.env_logger.debug("\n---- Emulator logs ----\n") + if os.path.exists(self.emulator_log_path): + with open(self.emulator_log_path, encoding="utf-8") as log: + self.env_logger.debug(log.read()) else: - self.log_debug("No emulator logs found") + self.env_logger.debug("No emulator logs found") # Print the package server log - self.log_debug("\n---- Package server log ----\n") - if os.path.exists(self.package_server_log_path()): - with open(self.package_server_log_path(), encoding="utf-8") as log: - self.log_debug(log.read()) + self.env_logger.debug("\n---- Package server log ----\n") + if os.path.exists(self.package_server_log_path): + with open(self.package_server_log_path, encoding="utf-8") as log: + self.env_logger.debug(log.read()) else: - self.log_debug("No package server log found") + self.env_logger.debug("No package server log found") # Print the ffx daemon log - self.log_debug("\n---- ffx daemon log ----\n") - if os.path.exists(self.ffx_daemon_log_path()): - with open(self.ffx_daemon_log_path(), encoding="utf-8") as log: - self.log_debug(log.read()) + self.env_logger.debug("\n---- ffx daemon log ----\n") + if os.path.exists(self.ffx_daemon_log_path): + with open(self.ffx_daemon_log_path, encoding="utf-8") as log: + self.env_logger.debug(log.read()) else: - self.log_debug("No ffx daemon log found") + self.env_logger.debug("No ffx daemon log found") # Shut down the emulator - self.log_info("Stopping emulator...") - self.check_call( + self.env_logger.info("Stopping emulator...") + check_call_with_logging( [ self.tool_path("ffx"), "emu", "stop", ], env=self.ffx_cmd_env(), - stdout=self.subprocess_output(), - stderr=self.subprocess_output(), + stdout_handler=self.subprocess_logger.debug, + stderr_handler=self.subprocess_logger.debug, ) # Stop ffx isolation - self.log_info("Stopping ffx isolation...") + self.env_logger.info("Stopping ffx isolation...") self.stop_ffx_isolation() - def delete_tmp(self): + def cleanup(self): # Remove temporary files - self.log_info("Deleting temporary files...") + self.env_logger.info("Deleting temporary files...") shutil.rmtree(self.tmp_dir(), ignore_errors=True) def debug(self, args): @@ -816,7 +1037,7 @@ def debug(self, args): f"--symbol-path={self.rust_dir}/lib/rustlib/{self.target}/lib", ] - # Add rust source if it's available + # Add rust source if it's available rust_src_map = None if args.rust_src is not None: # This matches the remapped prefix used by compiletest. There's no @@ -908,21 +1129,24 @@ def start(args): def run(args): test_env = TestEnvironment.read_from_file() + test_env.setup_logging(log_to_file=True) return test_env.run(args) def stop(args): test_env = TestEnvironment.read_from_file() + test_env.setup_logging(log_to_file=False) test_env.stop() - if not args.no_delete: - test_env.delete_tmp() + if not args.no_cleanup: + test_env.cleanup() return 0 -def delete_tmp(args): +def cleanup(args): del args test_env = TestEnvironment.read_from_file() - test_env.delete_tmp() + test_env.setup_logging(log_to_file=False) + test_env.cleanup() return 0 @@ -934,6 +1158,7 @@ def debug(args): def syslog(args): test_env = TestEnvironment.read_from_file() + test_env.setup_logging(log_to_file=True) test_env.syslog(args) return 0 @@ -973,6 +1198,21 @@ def print_help(args): help="the target platform to test", required=True, ) + start_parser.add_argument( + "--toolchain-dir", + help="the toolchain directory", + required=True, + ) + start_parser.add_argument( + "--local-product-bundle-path", + help="the path where the product-bundle should be downloaded to", + ) + start_parser.add_argument( + "--use-local-product-bundle-if-exists", + help="if the product bundle already exists in the local path, use " + "it instead of downloading it again", + action="store_true", + ) start_parser.set_defaults(func=start) run_parser = subparsers.add_parser( @@ -993,18 +1233,23 @@ def print_help(args): "stop", help="shuts down and cleans up the testing environment" ) stop_parser.add_argument( - "--no-delete", + "--no-cleanup", default=False, action="store_true", help="don't delete temporary files after stopping", ) stop_parser.set_defaults(func=stop) - delete_parser = subparsers.add_parser( - "delete-tmp", + cleanup_parser = subparsers.add_parser( + "cleanup", help="deletes temporary files after the testing environment has been manually cleaned up", ) - delete_parser.set_defaults(func=delete_tmp) + cleanup_parser.set_defaults(func=cleanup) + + syslog_parser = subparsers.add_parser( + "syslog", help="prints the device syslog" + ) + syslog_parser.set_defaults(func=syslog) debug_parser = subparsers.add_parser( "debug", @@ -1033,9 +1278,6 @@ def print_help(args): ) debug_parser.set_defaults(func=debug) - syslog_parser = subparsers.add_parser("syslog", help="prints the device syslog") - syslog_parser.set_defaults(func=syslog) - args = parser.parse_args() return args.func(args) From 0f8c3f78825a1b6eb765b97f658b92252b55c5df Mon Sep 17 00:00:00 2001 From: Ding Xiang Fei Date: Fri, 12 Apr 2024 23:24:45 +0800 Subject: [PATCH 23/33] tail expression behind terminating scope --- compiler/rustc_ast_lowering/src/expr.rs | 54 ++++++--- compiler/rustc_feature/src/unstable.rs | 2 + .../rustc_hir_analysis/src/check/region.rs | 10 +- compiler/rustc_span/src/symbol.rs | 1 + .../ui/drop/auxiliary/edition-2021-macros.rs | 8 ++ .../ui/drop/auxiliary/edition-2024-macros.rs | 9 ++ ...xpr-drop-order-negative.edition2024.stderr | 16 +++ .../ui/drop/tail-expr-drop-order-negative.rs | 17 +++ tests/ui/drop/tail-expr-drop-order.rs | 108 ++++++++++++++++++ .../feature-gate-shorter_tail_lifetimes.rs | 8 ++ ...feature-gate-shorter_tail_lifetimes.stderr | 26 +++++ .../refcell-in-tail-expr.edition2021.stderr | 26 +++++ tests/ui/lifetimes/refcell-in-tail-expr.rs | 16 +++ ...rter-tail-expr-lifetime.edition2021.stderr | 26 +++++ .../lifetimes/shorter-tail-expr-lifetime.rs | 15 +++ .../ui/lifetimes/tail-expr-in-nested-expr.rs | 9 ++ .../lifetimes/tail-expr-in-nested-expr.stderr | 15 +++ .../ui/lifetimes/tail-expr-lock-poisoning.rs | 29 +++++ tests/ui/nll/issue-52534-1.rs | 4 +- 19 files changed, 376 insertions(+), 23 deletions(-) create mode 100644 tests/ui/drop/auxiliary/edition-2021-macros.rs create mode 100644 tests/ui/drop/auxiliary/edition-2024-macros.rs create mode 100644 tests/ui/drop/tail-expr-drop-order-negative.edition2024.stderr create mode 100644 tests/ui/drop/tail-expr-drop-order-negative.rs create mode 100644 tests/ui/drop/tail-expr-drop-order.rs create mode 100644 tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.rs create mode 100644 tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.stderr create mode 100644 tests/ui/lifetimes/refcell-in-tail-expr.edition2021.stderr create mode 100644 tests/ui/lifetimes/refcell-in-tail-expr.rs create mode 100644 tests/ui/lifetimes/shorter-tail-expr-lifetime.edition2021.stderr create mode 100644 tests/ui/lifetimes/shorter-tail-expr-lifetime.rs create mode 100644 tests/ui/lifetimes/tail-expr-in-nested-expr.rs create mode 100644 tests/ui/lifetimes/tail-expr-in-nested-expr.stderr create mode 100644 tests/ui/lifetimes/tail-expr-lock-poisoning.rs diff --git a/compiler/rustc_ast_lowering/src/expr.rs b/compiler/rustc_ast_lowering/src/expr.rs index 77f95869e9dac..79cff0fbcd2ee 100644 --- a/compiler/rustc_ast_lowering/src/expr.rs +++ b/compiler/rustc_ast_lowering/src/expr.rs @@ -1716,24 +1716,28 @@ impl<'hir> LoweringContext<'_, 'hir> { // `mut iter => { ... }` let iter_arm = self.arm(iter_pat, loop_expr); - let into_iter_expr = match loop_kind { + let match_expr = match loop_kind { ForLoopKind::For => { // `::std::iter::IntoIterator::into_iter()` - self.expr_call_lang_item_fn( + let into_iter_expr = self.expr_call_lang_item_fn( head_span, hir::LangItem::IntoIterIntoIter, arena_vec![self; head], - ) + ); + + self.arena.alloc(self.expr_match( + for_span, + into_iter_expr, + arena_vec![self; iter_arm], + hir::MatchSource::ForLoopDesugar, + )) } - // ` unsafe { Pin::new_unchecked(&mut into_async_iter()) }` + // `match into_async_iter() { ref mut iter => match unsafe { Pin::new_unchecked(iter) } { ... } }` ForLoopKind::ForAwait => { - // `::core::async_iter::IntoAsyncIterator::into_async_iter()` - let iter = self.expr_call_lang_item_fn( - head_span, - hir::LangItem::IntoAsyncIterIntoIter, - arena_vec![self; head], - ); - let iter = self.expr_mut_addr_of(head_span, iter); + let iter_ident = iter; + let (async_iter_pat, async_iter_pat_id) = + self.pat_ident_binding_mode(head_span, iter_ident, hir::BindingMode::REF_MUT); + let iter = self.expr_ident_mut(head_span, iter_ident, async_iter_pat_id); // `Pin::new_unchecked(...)` let iter = self.arena.alloc(self.expr_call_lang_item_fn_mut( head_span, @@ -1742,17 +1746,29 @@ impl<'hir> LoweringContext<'_, 'hir> { )); // `unsafe { ... }` let iter = self.arena.alloc(self.expr_unsafe(iter)); - iter + let inner_match_expr = self.arena.alloc(self.expr_match( + for_span, + iter, + arena_vec![self; iter_arm], + hir::MatchSource::ForLoopDesugar, + )); + + // `::core::async_iter::IntoAsyncIterator::into_async_iter()` + let iter = self.expr_call_lang_item_fn( + head_span, + hir::LangItem::IntoAsyncIterIntoIter, + arena_vec![self; head], + ); + let iter_arm = self.arm(async_iter_pat, inner_match_expr); + self.arena.alloc(self.expr_match( + for_span, + iter, + arena_vec![self; iter_arm], + hir::MatchSource::ForLoopDesugar, + )) } }; - let match_expr = self.arena.alloc(self.expr_match( - for_span, - into_iter_expr, - arena_vec![self; iter_arm], - hir::MatchSource::ForLoopDesugar, - )); - // This is effectively `{ let _result = ...; _result }`. // The construct was introduced in #21984 and is necessary to make sure that // temporaries in the `head` expression are dropped and do not leak to the diff --git a/compiler/rustc_feature/src/unstable.rs b/compiler/rustc_feature/src/unstable.rs index 58832cb108750..77e72b5f4fb67 100644 --- a/compiler/rustc_feature/src/unstable.rs +++ b/compiler/rustc_feature/src/unstable.rs @@ -588,6 +588,8 @@ declare_features! ( (incomplete, return_type_notation, "1.70.0", Some(109417)), /// Allows `extern "rust-cold"`. (unstable, rust_cold_cc, "1.63.0", Some(97544)), + /// Shortern the tail expression lifetime + (unstable, shorter_tail_lifetimes, "1.79.0", Some(123739)), /// Allows the use of SIMD types in functions declared in `extern` blocks. (unstable, simd_ffi, "1.0.0", Some(27731)), /// Allows specialization of implementations (RFC 1210). diff --git a/compiler/rustc_hir_analysis/src/check/region.rs b/compiler/rustc_hir_analysis/src/check/region.rs index 72e431926ca32..2b5efd3b2f6f9 100644 --- a/compiler/rustc_hir_analysis/src/check/region.rs +++ b/compiler/rustc_hir_analysis/src/check/region.rs @@ -6,7 +6,6 @@ //! //! [rustc dev guide]: https://rustc-dev-guide.rust-lang.org/borrow_check.html -use rustc_ast::visit::visit_opt; use rustc_data_structures::fx::FxHashSet; use rustc_hir as hir; use rustc_hir::def_id::DefId; @@ -168,7 +167,14 @@ fn resolve_block<'tcx>(visitor: &mut RegionResolutionVisitor<'tcx>, blk: &'tcx h hir::StmtKind::Expr(..) | hir::StmtKind::Semi(..) => visitor.visit_stmt(statement), } } - visit_opt!(visitor, visit_expr, &blk.expr); + if let Some(tail_expr) = blk.expr { + if visitor.tcx.features().shorter_tail_lifetimes + && blk.span.edition().at_least_rust_2024() + { + visitor.terminating_scopes.insert(tail_expr.hir_id.local_id); + } + visitor.visit_expr(tail_expr); + } } visitor.cx = prev_cx; diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index f44fa1bcb4fd1..9fa8086c8fad2 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -1678,6 +1678,7 @@ symbols! { shadow_call_stack, shl, shl_assign, + shorter_tail_lifetimes, should_panic, shr, shr_assign, diff --git a/tests/ui/drop/auxiliary/edition-2021-macros.rs b/tests/ui/drop/auxiliary/edition-2021-macros.rs new file mode 100644 index 0000000000000..8a6444f8614d4 --- /dev/null +++ b/tests/ui/drop/auxiliary/edition-2021-macros.rs @@ -0,0 +1,8 @@ +//@ edition:2021 + +#[macro_export] +macro_rules! edition_2021_block { + ($($c:tt)*) => { + { $($c)* } + } +} diff --git a/tests/ui/drop/auxiliary/edition-2024-macros.rs b/tests/ui/drop/auxiliary/edition-2024-macros.rs new file mode 100644 index 0000000000000..236340bfed4f4 --- /dev/null +++ b/tests/ui/drop/auxiliary/edition-2024-macros.rs @@ -0,0 +1,9 @@ +//@ edition:2024 +//@ compile-flags: -Zunstable-options + +#[macro_export] +macro_rules! edition_2024_block { + ($($c:tt)*) => { + { $($c)* } + } +} diff --git a/tests/ui/drop/tail-expr-drop-order-negative.edition2024.stderr b/tests/ui/drop/tail-expr-drop-order-negative.edition2024.stderr new file mode 100644 index 0000000000000..75fc34e409b54 --- /dev/null +++ b/tests/ui/drop/tail-expr-drop-order-negative.edition2024.stderr @@ -0,0 +1,16 @@ +error[E0716]: temporary value dropped while borrowed + --> $DIR/tail-expr-drop-order-negative.rs:11:15 + | +LL | x.replace(std::cell::RefCell::new(123).borrow()).is_some() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ - temporary value is freed at the end of this statement + | | + | creates a temporary value which is freed while still in use +LL | +LL | } + | - borrow might be used here, when `x` is dropped and runs the destructor for type `Option>` + | + = note: consider using a `let` binding to create a longer lived value + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0716`. diff --git a/tests/ui/drop/tail-expr-drop-order-negative.rs b/tests/ui/drop/tail-expr-drop-order-negative.rs new file mode 100644 index 0000000000000..c570b3a1ee235 --- /dev/null +++ b/tests/ui/drop/tail-expr-drop-order-negative.rs @@ -0,0 +1,17 @@ +//@ revisions: edition2021 edition2024 +//@ [edition2024] compile-flags: -Zunstable-options +//@ [edition2024] edition: 2024 +//@ [edition2021] check-pass + +#![feature(shorter_tail_lifetimes)] + +fn why_would_you_do_this() -> bool { + let mut x = None; + // Make a temporary `RefCell` and put a `Ref` that borrows it in `x`. + x.replace(std::cell::RefCell::new(123).borrow()).is_some() + //[edition2024]~^ ERROR: temporary value dropped while borrowed +} + +fn main() { + why_would_you_do_this(); +} diff --git a/tests/ui/drop/tail-expr-drop-order.rs b/tests/ui/drop/tail-expr-drop-order.rs new file mode 100644 index 0000000000000..5d87f980b1563 --- /dev/null +++ b/tests/ui/drop/tail-expr-drop-order.rs @@ -0,0 +1,108 @@ +//@ aux-build:edition-2021-macros.rs +//@ aux-build:edition-2024-macros.rs +//@ compile-flags: -Z validate-mir -Zunstable-options +//@ edition: 2024 +//@ run-pass + +#![feature(shorter_tail_lifetimes)] +#![allow(unused_imports)] +#![allow(dead_code)] +#![allow(unused_variables)] + +#[macro_use] +extern crate edition_2021_macros; +#[macro_use] +extern crate edition_2024_macros; +use std::cell::RefCell; +use std::convert::TryInto; + +#[derive(Default)] +struct DropOrderCollector(RefCell>); + +struct LoudDrop<'a>(&'a DropOrderCollector, u32); + +impl Drop for LoudDrop<'_> { + fn drop(&mut self) { + println!("{}", self.1); + self.0.0.borrow_mut().push(self.1); + } +} + +impl DropOrderCollector { + fn option_loud_drop(&self, n: u32) -> Option { + Some(LoudDrop(self, n)) + } + + fn loud_drop(&self, n: u32) -> LoudDrop { + LoudDrop(self, n) + } + + fn assert_sorted(&self, expected: usize) { + let result = self.0.borrow(); + assert_eq!(result.len(), expected); + for i in 1..result.len() { + assert!( + result[i - 1] < result[i], + "inversion at {} ({} followed by {})", + i - 1, + result[i - 1], + result[i] + ); + } + } +} + +fn edition_2021_around_2021() { + let c = DropOrderCollector::default(); + let _ = edition_2021_block! { + let a = c.loud_drop(1); + edition_2021_block! { + let b = c.loud_drop(0); + c.loud_drop(2).1 + } + }; + c.assert_sorted(3); +} + +fn edition_2021_around_2024() { + let c = DropOrderCollector::default(); + let _ = edition_2021_block! { + let a = c.loud_drop(2); + edition_2024_block! { + let b = c.loud_drop(1); + c.loud_drop(0).1 + } + }; + c.assert_sorted(3); +} + +fn edition_2024_around_2021() { + let c = DropOrderCollector::default(); + let _ = edition_2024_block! { + let a = c.loud_drop(2); + edition_2021_block! { + let b = c.loud_drop(0); + c.loud_drop(1).1 + } + }; + c.assert_sorted(3); +} + +fn edition_2024_around_2024() { + let c = DropOrderCollector::default(); + let _ = edition_2024_block! { + let a = c.loud_drop(2); + edition_2024_block! { + let b = c.loud_drop(1); + c.loud_drop(0).1 + } + }; + c.assert_sorted(3); +} + +fn main() { + edition_2021_around_2021(); + edition_2021_around_2024(); + edition_2024_around_2021(); + edition_2024_around_2024(); +} diff --git a/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.rs b/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.rs new file mode 100644 index 0000000000000..5292c44bb2d40 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.rs @@ -0,0 +1,8 @@ +fn f() -> usize { + let c = std::cell::RefCell::new(".."); + c.borrow().len() //~ ERROR: `c` does not live long enough +} + +fn main() { + let _ = f(); +} diff --git a/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.stderr b/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.stderr new file mode 100644 index 0000000000000..648c3d5daa1c6 --- /dev/null +++ b/tests/ui/feature-gates/feature-gate-shorter_tail_lifetimes.stderr @@ -0,0 +1,26 @@ +error[E0597]: `c` does not live long enough + --> $DIR/feature-gate-shorter_tail_lifetimes.rs:3:5 + | +LL | let c = std::cell::RefCell::new(".."); + | - binding `c` declared here +LL | c.borrow().len() + | ^--------- + | | + | borrowed value does not live long enough + | a temporary with access to the borrow is created here ... +LL | } + | - + | | + | `c` dropped here while still borrowed + | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>` + | + = note: the temporary is part of an expression at the end of a block; + consider forcing this temporary to be dropped sooner, before the block's local variables are dropped +help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block + | +LL | let x = c.borrow().len(); x + | +++++++ +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/lifetimes/refcell-in-tail-expr.edition2021.stderr b/tests/ui/lifetimes/refcell-in-tail-expr.edition2021.stderr new file mode 100644 index 0000000000000..858be42d54094 --- /dev/null +++ b/tests/ui/lifetimes/refcell-in-tail-expr.edition2021.stderr @@ -0,0 +1,26 @@ +error[E0597]: `cell` does not live long enough + --> $DIR/refcell-in-tail-expr.rs:12:27 + | +LL | let cell = std::cell::RefCell::new(0u8); + | ---- binding `cell` declared here +LL | +LL | if let Ok(mut byte) = cell.try_borrow_mut() { + | ^^^^----------------- + | | + | borrowed value does not live long enough + | a temporary with access to the borrow is created here ... +... +LL | } + | - + | | + | `cell` dropped here while still borrowed + | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Result, BorrowMutError>` + | +help: consider adding semicolon after the expression so its temporaries are dropped sooner, before the local variables declared by the block are dropped + | +LL | }; + | + + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/lifetimes/refcell-in-tail-expr.rs b/tests/ui/lifetimes/refcell-in-tail-expr.rs new file mode 100644 index 0000000000000..b1814c1e32713 --- /dev/null +++ b/tests/ui/lifetimes/refcell-in-tail-expr.rs @@ -0,0 +1,16 @@ +//@ revisions: edition2021 edition2024 +//@ [edition2021] edition: 2021 +//@ [edition2024] edition: 2024 +//@ [edition2024] compile-flags: -Zunstable-options +//@ [edition2024] check-pass + +#![cfg_attr(edition2024, feature(shorter_tail_lifetimes))] + +fn main() { + let cell = std::cell::RefCell::new(0u8); + + if let Ok(mut byte) = cell.try_borrow_mut() { + //[edition2021]~^ ERROR: `cell` does not live long enough + *byte = 1; + } +} diff --git a/tests/ui/lifetimes/shorter-tail-expr-lifetime.edition2021.stderr b/tests/ui/lifetimes/shorter-tail-expr-lifetime.edition2021.stderr new file mode 100644 index 0000000000000..ad28ae2f80d66 --- /dev/null +++ b/tests/ui/lifetimes/shorter-tail-expr-lifetime.edition2021.stderr @@ -0,0 +1,26 @@ +error[E0597]: `c` does not live long enough + --> $DIR/shorter-tail-expr-lifetime.rs:10:5 + | +LL | let c = std::cell::RefCell::new(".."); + | - binding `c` declared here +LL | c.borrow().len() + | ^--------- + | | + | borrowed value does not live long enough + | a temporary with access to the borrow is created here ... +LL | } + | - + | | + | `c` dropped here while still borrowed + | ... and the borrow might be used here, when that temporary is dropped and runs the destructor for type `Ref<'_, &str>` + | + = note: the temporary is part of an expression at the end of a block; + consider forcing this temporary to be dropped sooner, before the block's local variables are dropped +help: for example, you could save the expression's value in a new local variable `x` and then make `x` be the expression at the end of the block + | +LL | let x = c.borrow().len(); x + | +++++++ +++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/lifetimes/shorter-tail-expr-lifetime.rs b/tests/ui/lifetimes/shorter-tail-expr-lifetime.rs new file mode 100644 index 0000000000000..0392b6c6d9ada --- /dev/null +++ b/tests/ui/lifetimes/shorter-tail-expr-lifetime.rs @@ -0,0 +1,15 @@ +//@ revisions: edition2021 edition2024 +//@ [edition2024] compile-flags: -Zunstable-options +//@ [edition2024] edition: 2024 +//@ [edition2024] run-pass + +#![cfg_attr(edition2024, feature(shorter_tail_lifetimes))] + +fn f() -> usize { + let c = std::cell::RefCell::new(".."); + c.borrow().len() //[edition2021]~ ERROR: `c` does not live long enough +} + +fn main() { + let _ = f(); +} diff --git a/tests/ui/lifetimes/tail-expr-in-nested-expr.rs b/tests/ui/lifetimes/tail-expr-in-nested-expr.rs new file mode 100644 index 0000000000000..a8989f22f4b5b --- /dev/null +++ b/tests/ui/lifetimes/tail-expr-in-nested-expr.rs @@ -0,0 +1,9 @@ +//@ edition: 2024 +//@ compile-flags: -Zunstable-options + +#![feature(shorter_tail_lifetimes)] + +fn main() { + let _ = { String::new().as_str() }.len(); + //~^ ERROR temporary value dropped while borrowed +} diff --git a/tests/ui/lifetimes/tail-expr-in-nested-expr.stderr b/tests/ui/lifetimes/tail-expr-in-nested-expr.stderr new file mode 100644 index 0000000000000..f699d184bdb1d --- /dev/null +++ b/tests/ui/lifetimes/tail-expr-in-nested-expr.stderr @@ -0,0 +1,15 @@ +error[E0716]: temporary value dropped while borrowed + --> $DIR/tail-expr-in-nested-expr.rs:7:15 + | +LL | let _ = { String::new().as_str() }.len(); + | ^^^^^^^^^^^^^--------- + | | | + | | temporary value is freed at the end of this statement + | creates a temporary value which is freed while still in use + | borrow later used here + | + = note: consider using a `let` binding to create a longer lived value + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0716`. diff --git a/tests/ui/lifetimes/tail-expr-lock-poisoning.rs b/tests/ui/lifetimes/tail-expr-lock-poisoning.rs new file mode 100644 index 0000000000000..69b8f286d774f --- /dev/null +++ b/tests/ui/lifetimes/tail-expr-lock-poisoning.rs @@ -0,0 +1,29 @@ +//@ revisions: edition2021 edition2024 +//@ ignore-wasm no panic or subprocess support +//@ [edition2024] compile-flags: -Zunstable-options +//@ [edition2024] edition: 2024 +//@ run-pass +#![cfg_attr(edition2024, feature(shorter_tail_lifetimes))] + +use std::sync::Mutex; + +struct PanicOnDrop; +impl Drop for PanicOnDrop { + fn drop(&mut self) { + panic!() + } +} + +fn f(m: &Mutex) -> i32 { + let _x = PanicOnDrop; + *m.lock().unwrap() +} + +fn main() { + let m = Mutex::new(0); + let _ = std::panic::catch_unwind(|| f(&m)); + #[cfg(edition2024)] + assert!(m.lock().is_ok()); + #[cfg(edition2021)] + assert!(m.lock().is_err()); +} diff --git a/tests/ui/nll/issue-52534-1.rs b/tests/ui/nll/issue-52534-1.rs index d9ea3ae42c49e..526b81bb2d056 100644 --- a/tests/ui/nll/issue-52534-1.rs +++ b/tests/ui/nll/issue-52534-1.rs @@ -17,14 +17,14 @@ fn foo(x: &u32) -> &u32 { fn baz(x: &u32) -> &&u32 { let x = 22; &&x -//~^ ERROR cannot return value referencing local variable +//~^ ERROR cannot return value referencing local variable `x` //~| ERROR cannot return reference to temporary value } fn foobazbar<'a>(x: u32, y: &'a u32) -> &'a u32 { let x = 22; &x -//~^ ERROR cannot return reference to local variable +//~^ ERROR cannot return reference to local variable `x` } fn foobar<'a>(x: &'a u32) -> &'a u32 { From 475b63fa283efc4b5aafd8ddfde69229204dd059 Mon Sep 17 00:00:00 2001 From: Trevor Gross Date: Mon, 17 Jun 2024 22:56:12 -0500 Subject: [PATCH 24/33] Add `/rustc-ice*/ to `.gitignore` --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 485968d9c56ff..87d02563ed048 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ build/ /src/tools/x/target # Created by default with `src/ci/docker/run.sh` /obj/ +/rustc-ice* ## Temporary files *~ From af10880f6b4a56e5f9a3c458878772f8eda19544 Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Mon, 17 Jun 2024 17:00:45 +0300 Subject: [PATCH 25/33] Make async drop code more consistent with regular drop code Fixes #126573 --- .../src/back/symbol_export.rs | 19 ++++++++--- compiler/rustc_middle/src/query/mod.rs | 33 +++++++++++++++---- compiler/rustc_middle/src/ty/instance.rs | 28 ++++++++++++++-- .../cfi/typeid/itanium_cxx_abi/transform.rs | 1 + 4 files changed, 67 insertions(+), 14 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index 1d61c15640910..c868904c5511c 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -371,7 +371,7 @@ fn exported_symbols_provider_local( }) => { // A little sanity-check debug_assert_eq!( - args.non_erasable_generics(tcx, def_id).skip(1).next(), + args.non_erasable_generics(tcx, def_id).next(), Some(GenericArgKind::Type(ty)) ); symbols.push(( @@ -422,10 +422,7 @@ fn upstream_monomorphizations_provider( } ExportedSymbol::AsyncDropGlueCtorShim(ty) => { if let Some(async_drop_in_place_fn_def_id) = async_drop_in_place_fn_def_id { - ( - async_drop_in_place_fn_def_id, - tcx.mk_args(&[tcx.lifetimes.re_erased.into(), ty.into()]), - ) + (async_drop_in_place_fn_def_id, tcx.mk_args(&[ty.into()])) } else { // `drop_in_place` in place does not exist, don't try // to use it. @@ -480,6 +477,17 @@ fn upstream_drop_glue_for_provider<'tcx>( } } +fn upstream_async_drop_glue_for_provider<'tcx>( + tcx: TyCtxt<'tcx>, + args: GenericArgsRef<'tcx>, +) -> Option { + if let Some(def_id) = tcx.lang_items().async_drop_in_place_fn() { + tcx.upstream_monomorphizations_for(def_id).and_then(|monos| monos.get(&args).cloned()) + } else { + None + } +} + fn is_unreachable_local_definition_provider(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { !tcx.reachable_set(()).contains(&def_id) } @@ -491,6 +499,7 @@ pub fn provide(providers: &mut Providers) { providers.upstream_monomorphizations = upstream_monomorphizations_provider; providers.is_unreachable_local_definition = is_unreachable_local_definition_provider; providers.upstream_drop_glue_for = upstream_drop_glue_for_provider; + providers.upstream_async_drop_glue_for = upstream_async_drop_glue_for_provider; providers.wasm_import_module_map = wasm_import_module_map; providers.extern_queries.is_reachable_non_generic = is_reachable_non_generic_provider_extern; providers.extern_queries.upstream_monomorphizations_for = diff --git a/compiler/rustc_middle/src/query/mod.rs b/compiler/rustc_middle/src/query/mod.rs index 0f08694c4eabd..c5afecffb07af 100644 --- a/compiler/rustc_middle/src/query/mod.rs +++ b/compiler/rustc_middle/src/query/mod.rs @@ -1554,12 +1554,13 @@ rustc_queries! { } } - /// The entire set of monomorphizations the local crate can safely link - /// to because they are exported from upstream crates. Do not depend on - /// this directly, as its value changes anytime a monomorphization gets - /// added or removed in any upstream crate. Instead use the narrower - /// `upstream_monomorphizations_for`, `upstream_drop_glue_for`, or, even - /// better, `Instance::upstream_monomorphization()`. + /// The entire set of monomorphizations the local crate can safely + /// link to because they are exported from upstream crates. Do + /// not depend on this directly, as its value changes anytime + /// a monomorphization gets added or removed in any upstream + /// crate. Instead use the narrower `upstream_monomorphizations_for`, + /// `upstream_drop_glue_for`, `upstream_async_drop_glue_for`, or, + /// even better, `Instance::upstream_monomorphization()`. query upstream_monomorphizations(_: ()) -> &'tcx DefIdMap, CrateNum>> { arena_cache desc { "collecting available upstream monomorphizations" } @@ -1601,6 +1602,26 @@ rustc_queries! { desc { "available upstream drop-glue for `{:?}`", args } } + /// Returns the upstream crate that exports async-drop-glue for + /// the given type (`args` is expected to be a single-item list + /// containing the type one wants async-drop-glue for). + /// + /// This is a subset of `upstream_monomorphizations_for` in order + /// to increase dep-tracking granularity. Otherwise adding or + /// removing any type with async-drop-glue in any upstream crate + /// would invalidate all functions calling async-drop-glue of an + /// upstream type. + /// + /// You likely want to call `Instance::upstream_monomorphization()` + /// instead of invoking this query directly. + /// + /// NOTE: This query could easily be extended to also support other + /// common functions that have are large set of monomorphizations + /// (like `Clone::clone` for example). + query upstream_async_drop_glue_for(args: GenericArgsRef<'tcx>) -> Option { + desc { "available upstream async-drop-glue for `{:?}`", args } + } + /// Returns a list of all `extern` blocks of a crate. query foreign_modules(_: CrateNum) -> &'tcx FxIndexMap { arena_cache diff --git a/compiler/rustc_middle/src/ty/instance.rs b/compiler/rustc_middle/src/ty/instance.rs index 5718264c94466..099b14cca9ce2 100644 --- a/compiler/rustc_middle/src/ty/instance.rs +++ b/compiler/rustc_middle/src/ty/instance.rs @@ -219,8 +219,9 @@ impl<'tcx> Instance<'tcx> { InstanceKind::Item(def) => tcx .upstream_monomorphizations_for(def) .and_then(|monos| monos.get(&self.args).cloned()), - InstanceKind::DropGlue(_, Some(_)) | InstanceKind::AsyncDropGlueCtorShim(_, _) => { - tcx.upstream_drop_glue_for(self.args) + InstanceKind::DropGlue(_, Some(_)) => tcx.upstream_drop_glue_for(self.args), + InstanceKind::AsyncDropGlueCtorShim(_, Some(_)) => { + tcx.upstream_async_drop_glue_for(self.args) } _ => None, } @@ -256,7 +257,7 @@ impl<'tcx> InstanceKind<'tcx> { match self { ty::InstanceKind::Item(def) => Some(def), ty::InstanceKind::DropGlue(def_id, Some(_)) - | InstanceKind::AsyncDropGlueCtorShim(def_id, _) + | InstanceKind::AsyncDropGlueCtorShim(def_id, Some(_)) | InstanceKind::ThreadLocalShim(def_id) => Some(def_id), InstanceKind::VTableShim(..) | InstanceKind::ReifyShim(..) @@ -267,6 +268,7 @@ impl<'tcx> InstanceKind<'tcx> { | ty::InstanceKind::ConstructCoroutineInClosureShim { .. } | ty::InstanceKind::CoroutineKindShim { .. } | InstanceKind::DropGlue(..) + | InstanceKind::AsyncDropGlueCtorShim(..) | InstanceKind::CloneShim(..) | InstanceKind::FnPtrAddrShim(..) => None, } @@ -332,6 +334,26 @@ impl<'tcx> InstanceKind<'tcx> { .map_or_else(|| adt_def.is_enum(), |dtor| tcx.cross_crate_inlinable(dtor.did)) }); } + if let ty::InstanceKind::AsyncDropGlueCtorShim(.., Some(ty)) = *self { + // Async drop glue generally wants to be instantiated at + // every codegen unit, but without an #[inline] hint. We + // should make this available to normal end-users. + if tcx.sess.opts.incremental.is_none() { + return true; + } + // When compiling with incremental, we can generate a *lot* of + // codegen units. Including drop glue into all of them has a + // considerable compile time cost. + // + // We include enums without destructors to allow, say, optimizing + // drops of `Option::None` before LTO. We also respect the intent of + // `#[inline]` on `Drop::drop` implementations. + return ty.ty_adt_def().map_or(true, |adt_def| { + adt_def + .async_destructor(tcx) + .map_or_else(|| adt_def.is_enum(), |dtor| tcx.cross_crate_inlinable(dtor.ctor)) + }); + } if let ty::InstanceKind::ThreadLocalShim(..) = *self { return false; } diff --git a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs index 832efb1199926..742ec4c377ccf 100644 --- a/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs +++ b/compiler/rustc_sanitizers/src/cfi/typeid/itanium_cxx_abi/transform.rs @@ -288,6 +288,7 @@ pub fn transform_instance<'tcx>( mut instance: Instance<'tcx>, options: TransformTyOptions, ) -> Instance<'tcx> { + // FIXME: account for async-drop-glue if (matches!(instance.def, ty::InstanceKind::Virtual(..)) && tcx.is_lang_item(instance.def_id(), LangItem::DropInPlace)) || matches!(instance.def, ty::InstanceKind::DropGlue(..)) From 1a8eae1aba998af1b0fdc597c0e263d4d391b169 Mon Sep 17 00:00:00 2001 From: Daria Sukhonina Date: Tue, 18 Jun 2024 14:28:00 +0300 Subject: [PATCH 26/33] Apply suggestions from oli-obk's review Co-authored-by: Oli Scherer --- .../src/back/symbol_export.rs | 14 +++----- compiler/rustc_middle/src/ty/instance.rs | 35 ++++++------------- 2 files changed, 15 insertions(+), 34 deletions(-) diff --git a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs index c868904c5511c..6abe4fa1c3809 100644 --- a/compiler/rustc_codegen_ssa/src/back/symbol_export.rs +++ b/compiler/rustc_codegen_ssa/src/back/symbol_export.rs @@ -470,22 +470,16 @@ fn upstream_drop_glue_for_provider<'tcx>( tcx: TyCtxt<'tcx>, args: GenericArgsRef<'tcx>, ) -> Option { - if let Some(def_id) = tcx.lang_items().drop_in_place_fn() { - tcx.upstream_monomorphizations_for(def_id).and_then(|monos| monos.get(&args).cloned()) - } else { - None - } + let def_id = tcx.lang_items().drop_in_place_fn()?; + tcx.upstream_monomorphizations_for(def_id)?.get(&args).cloned() } fn upstream_async_drop_glue_for_provider<'tcx>( tcx: TyCtxt<'tcx>, args: GenericArgsRef<'tcx>, ) -> Option { - if let Some(def_id) = tcx.lang_items().async_drop_in_place_fn() { - tcx.upstream_monomorphizations_for(def_id).and_then(|monos| monos.get(&args).cloned()) - } else { - None - } + let def_id = tcx.lang_items().async_drop_in_place_fn()?; + tcx.upstream_monomorphizations_for(def_id)?.get(&args).cloned() } fn is_unreachable_local_definition_provider(tcx: TyCtxt<'_>, def_id: LocalDefId) -> bool { diff --git a/compiler/rustc_middle/src/ty/instance.rs b/compiler/rustc_middle/src/ty/instance.rs index 099b14cca9ce2..efaf9c7231bb4 100644 --- a/compiler/rustc_middle/src/ty/instance.rs +++ b/compiler/rustc_middle/src/ty/instance.rs @@ -314,7 +314,9 @@ impl<'tcx> InstanceKind<'tcx> { if self.requires_inline(tcx) { return true; } - if let ty::InstanceKind::DropGlue(.., Some(ty)) = *self { + if let ty::InstanceKind::DropGlue(.., Some(ty)) + | ty::InstanceKind::AsyncDropGlueCtorShim(.., Some(ty)) = *self + { // Drop glue generally wants to be instantiated at every codegen // unit, but without an #[inline] hint. We should make this // available to normal end-users. @@ -329,29 +331,14 @@ impl<'tcx> InstanceKind<'tcx> { // drops of `Option::None` before LTO. We also respect the intent of // `#[inline]` on `Drop::drop` implementations. return ty.ty_adt_def().map_or(true, |adt_def| { - adt_def - .destructor(tcx) - .map_or_else(|| adt_def.is_enum(), |dtor| tcx.cross_crate_inlinable(dtor.did)) - }); - } - if let ty::InstanceKind::AsyncDropGlueCtorShim(.., Some(ty)) = *self { - // Async drop glue generally wants to be instantiated at - // every codegen unit, but without an #[inline] hint. We - // should make this available to normal end-users. - if tcx.sess.opts.incremental.is_none() { - return true; - } - // When compiling with incremental, we can generate a *lot* of - // codegen units. Including drop glue into all of them has a - // considerable compile time cost. - // - // We include enums without destructors to allow, say, optimizing - // drops of `Option::None` before LTO. We also respect the intent of - // `#[inline]` on `Drop::drop` implementations. - return ty.ty_adt_def().map_or(true, |adt_def| { - adt_def - .async_destructor(tcx) - .map_or_else(|| adt_def.is_enum(), |dtor| tcx.cross_crate_inlinable(dtor.ctor)) + match *self { + ty::InstanceKind::DropGlue(..) => adt_def.destructor(tcx).map(|dtor| dtor.did), + ty::InstanceKind::AsyncDropGlueCtorShim(..) => { + adt_def.async_destructor(tcx).map(|dtor| dtor.ctor) + } + _ => unreachable!(), + } + .map_or_else(|| adt_def.is_enum(), |did| tcx.cross_crate_inlinable(did)) }); } if let ty::InstanceKind::ThreadLocalShim(..) = *self { From 7321e791141ddf2ec4d5f8a9893146832118bb19 Mon Sep 17 00:00:00 2001 From: Vonr Date: Tue, 18 Jun 2024 23:25:08 +0800 Subject: [PATCH 27/33] Replace `move||` with `move ||` in `compiler/` and `library/` Edit from #126631 to revert changes on ui tests --- .../rustc_hir_typeck/src/typeck_root_ctxt.rs | 2 +- library/core/src/sync/atomic.rs | 2 +- library/std/src/sync/barrier.rs | 4 ++-- library/std/src/sync/condvar.rs | 16 ++++++++-------- library/std/src/sync/mpsc/mod.rs | 10 +++++----- library/std/src/thread/local.rs | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs b/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs index 28745af3a5309..b6e9000ef9506 100644 --- a/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs +++ b/compiler/rustc_hir_typeck/src/typeck_root_ctxt.rs @@ -22,7 +22,7 @@ use std::ops::Deref; /// e.g. closures defined within the function. For example: /// ```ignore (illustrative) /// fn foo() { -/// bar(move|| { ... }) +/// bar(move || { ... }) /// } /// ``` /// Here, the function `foo()` and the closure passed to diff --git a/library/core/src/sync/atomic.rs b/library/core/src/sync/atomic.rs index 482bd19705c2f..c709ea2a15db1 100644 --- a/library/core/src/sync/atomic.rs +++ b/library/core/src/sync/atomic.rs @@ -183,7 +183,7 @@ //! //! let spinlock_clone = Arc::clone(&spinlock); //! -//! let thread = thread::spawn(move|| { +//! let thread = thread::spawn(move || { //! spinlock_clone.store(0, Ordering::Release); //! }); //! diff --git a/library/std/src/sync/barrier.rs b/library/std/src/sync/barrier.rs index b4bac081e7ab7..82cc13a74b7f1 100644 --- a/library/std/src/sync/barrier.rs +++ b/library/std/src/sync/barrier.rs @@ -20,7 +20,7 @@ use crate::sync::{Condvar, Mutex}; /// let c = Arc::clone(&barrier); /// // The same messages will be printed together. /// // You will NOT see any interleaving. -/// handles.push(thread::spawn(move|| { +/// handles.push(thread::spawn(move || { /// println!("before wait"); /// c.wait(); /// println!("after wait"); @@ -115,7 +115,7 @@ impl Barrier { /// let c = Arc::clone(&barrier); /// // The same messages will be printed together. /// // You will NOT see any interleaving. - /// handles.push(thread::spawn(move|| { + /// handles.push(thread::spawn(move || { /// println!("before wait"); /// c.wait(); /// println!("after wait"); diff --git a/library/std/src/sync/condvar.rs b/library/std/src/sync/condvar.rs index b20574e4f1493..f9f83fb4f63c3 100644 --- a/library/std/src/sync/condvar.rs +++ b/library/std/src/sync/condvar.rs @@ -88,7 +88,7 @@ impl WaitTimeoutResult { /// let pair2 = Arc::clone(&pair); /// /// // Inside of our lock, spawn a new thread, and then wait for it to start. -/// thread::spawn(move|| { +/// thread::spawn(move || { /// let (lock, cvar) = &*pair2; /// let mut started = lock.lock().unwrap(); /// *started = true; @@ -166,7 +166,7 @@ impl Condvar { /// let pair = Arc::new((Mutex::new(false), Condvar::new())); /// let pair2 = Arc::clone(&pair); /// - /// thread::spawn(move|| { + /// thread::spawn(move || { /// let (lock, cvar) = &*pair2; /// let mut started = lock.lock().unwrap(); /// *started = true; @@ -221,7 +221,7 @@ impl Condvar { /// let pair = Arc::new((Mutex::new(true), Condvar::new())); /// let pair2 = Arc::clone(&pair); /// - /// thread::spawn(move|| { + /// thread::spawn(move || { /// let (lock, cvar) = &*pair2; /// let mut pending = lock.lock().unwrap(); /// *pending = false; @@ -280,7 +280,7 @@ impl Condvar { /// let pair = Arc::new((Mutex::new(false), Condvar::new())); /// let pair2 = Arc::clone(&pair); /// - /// thread::spawn(move|| { + /// thread::spawn(move || { /// let (lock, cvar) = &*pair2; /// let mut started = lock.lock().unwrap(); /// *started = true; @@ -352,7 +352,7 @@ impl Condvar { /// let pair = Arc::new((Mutex::new(false), Condvar::new())); /// let pair2 = Arc::clone(&pair); /// - /// thread::spawn(move|| { + /// thread::spawn(move || { /// let (lock, cvar) = &*pair2; /// let mut started = lock.lock().unwrap(); /// *started = true; @@ -420,7 +420,7 @@ impl Condvar { /// let pair = Arc::new((Mutex::new(true), Condvar::new())); /// let pair2 = Arc::clone(&pair); /// - /// thread::spawn(move|| { + /// thread::spawn(move || { /// let (lock, cvar) = &*pair2; /// let mut pending = lock.lock().unwrap(); /// *pending = false; @@ -484,7 +484,7 @@ impl Condvar { /// let pair = Arc::new((Mutex::new(false), Condvar::new())); /// let pair2 = Arc::clone(&pair); /// - /// thread::spawn(move|| { + /// thread::spawn(move || { /// let (lock, cvar) = &*pair2; /// let mut started = lock.lock().unwrap(); /// *started = true; @@ -524,7 +524,7 @@ impl Condvar { /// let pair = Arc::new((Mutex::new(false), Condvar::new())); /// let pair2 = Arc::clone(&pair); /// - /// thread::spawn(move|| { + /// thread::spawn(move || { /// let (lock, cvar) = &*pair2; /// let mut started = lock.lock().unwrap(); /// *started = true; diff --git a/library/std/src/sync/mpsc/mod.rs b/library/std/src/sync/mpsc/mod.rs index d353c7bd5de9e..feee6948db4fd 100644 --- a/library/std/src/sync/mpsc/mod.rs +++ b/library/std/src/sync/mpsc/mod.rs @@ -51,7 +51,7 @@ //! //! // Create a simple streaming channel //! let (tx, rx) = channel(); -//! thread::spawn(move|| { +//! thread::spawn(move || { //! tx.send(10).unwrap(); //! }); //! assert_eq!(rx.recv().unwrap(), 10); @@ -69,7 +69,7 @@ //! let (tx, rx) = channel(); //! for i in 0..10 { //! let tx = tx.clone(); -//! thread::spawn(move|| { +//! thread::spawn(move || { //! tx.send(i).unwrap(); //! }); //! } @@ -99,7 +99,7 @@ //! use std::sync::mpsc::sync_channel; //! //! let (tx, rx) = sync_channel::(0); -//! thread::spawn(move|| { +//! thread::spawn(move || { //! // This will wait for the parent thread to start receiving //! tx.send(53).unwrap(); //! }); @@ -510,7 +510,7 @@ pub enum TrySendError { /// let (sender, receiver) = channel(); /// /// // Spawn off an expensive computation -/// thread::spawn(move|| { +/// thread::spawn(move || { /// # fn expensive_computation() {} /// sender.send(expensive_computation()).unwrap(); /// }); @@ -561,7 +561,7 @@ pub fn channel() -> (Sender, Receiver) { /// // this returns immediately /// sender.send(1).unwrap(); /// -/// thread::spawn(move|| { +/// thread::spawn(move || { /// // this will block until the previous message has been received /// sender.send(2).unwrap(); /// }); diff --git a/library/std/src/thread/local.rs b/library/std/src/thread/local.rs index aed185637fd1f..f147c5fdcd146 100644 --- a/library/std/src/thread/local.rs +++ b/library/std/src/thread/local.rs @@ -62,7 +62,7 @@ use crate::fmt; /// FOO.set(2); /// /// // each thread starts out with the initial value of 1 -/// let t = thread::spawn(move|| { +/// let t = thread::spawn(move || { /// assert_eq!(FOO.get(), 1); /// FOO.set(3); /// }); From de473a5a2b7e15aa45e4c2b0d070c22f7140316a Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Thu, 11 Apr 2024 08:56:10 +0000 Subject: [PATCH 28/33] Test that opaque types can't have themselves as a hidden type with incompatible lifetimes --- .../different_args_considered_equal.rs | 14 ++++++++++++ .../different_args_considered_equal.stderr | 15 +++++++++++++ .../different_args_considered_equal2.rs | 14 ++++++++++++ .../different_args_considered_equal2.stderr | 20 +++++++++++++++++ .../different_args_considered_equal3.rs | 22 +++++++++++++++++++ .../different_args_considered_equal3.stderr | 10 +++++++++ 6 files changed, 95 insertions(+) create mode 100644 tests/ui/type-alias-impl-trait/different_args_considered_equal.rs create mode 100644 tests/ui/type-alias-impl-trait/different_args_considered_equal.stderr create mode 100644 tests/ui/type-alias-impl-trait/different_args_considered_equal2.rs create mode 100644 tests/ui/type-alias-impl-trait/different_args_considered_equal2.stderr create mode 100644 tests/ui/type-alias-impl-trait/different_args_considered_equal3.rs create mode 100644 tests/ui/type-alias-impl-trait/different_args_considered_equal3.stderr diff --git a/tests/ui/type-alias-impl-trait/different_args_considered_equal.rs b/tests/ui/type-alias-impl-trait/different_args_considered_equal.rs new file mode 100644 index 0000000000000..8ce471e395681 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/different_args_considered_equal.rs @@ -0,0 +1,14 @@ +#![feature(type_alias_impl_trait)] + +pub type Opaque<'a> = impl Sized; + +fn get_one<'a>(a: *mut &'a str) -> Opaque<'a> { + a +} + +fn get_iter<'a>() -> impl IntoIterator> { + //~^ ERROR: item does not constrain + None::> +} + +fn main() {} diff --git a/tests/ui/type-alias-impl-trait/different_args_considered_equal.stderr b/tests/ui/type-alias-impl-trait/different_args_considered_equal.stderr new file mode 100644 index 0000000000000..f27f223452523 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/different_args_considered_equal.stderr @@ -0,0 +1,15 @@ +error: item does not constrain `Opaque::{opaque#0}`, but has it in its signature + --> $DIR/different_args_considered_equal.rs:9:4 + | +LL | fn get_iter<'a>() -> impl IntoIterator> { + | ^^^^^^^^ + | + = note: consider moving the opaque type's declaration and defining uses into a separate module +note: this opaque type is in the signature + --> $DIR/different_args_considered_equal.rs:3:23 + | +LL | pub type Opaque<'a> = impl Sized; + | ^^^^^^^^^^ + +error: aborting due to 1 previous error + diff --git a/tests/ui/type-alias-impl-trait/different_args_considered_equal2.rs b/tests/ui/type-alias-impl-trait/different_args_considered_equal2.rs new file mode 100644 index 0000000000000..43dfea97e6dbd --- /dev/null +++ b/tests/ui/type-alias-impl-trait/different_args_considered_equal2.rs @@ -0,0 +1,14 @@ +#![feature(type_alias_impl_trait)] + +pub type Opaque<'a> = impl Sized; + +fn get_one<'a>(a: *mut &'a str) -> impl IntoIterator> { + if a.is_null() { + Some(a) + } else { + None::> + //~^ ERROR hidden type for `Opaque<'static>` captures lifetime that does not appear in bounds + } +} + +fn main() {} diff --git a/tests/ui/type-alias-impl-trait/different_args_considered_equal2.stderr b/tests/ui/type-alias-impl-trait/different_args_considered_equal2.stderr new file mode 100644 index 0000000000000..1104c2c498a5a --- /dev/null +++ b/tests/ui/type-alias-impl-trait/different_args_considered_equal2.stderr @@ -0,0 +1,20 @@ +error[E0700]: hidden type for `Opaque<'static>` captures lifetime that does not appear in bounds + --> $DIR/different_args_considered_equal2.rs:9:9 + | +LL | pub type Opaque<'a> = impl Sized; + | ---------- opaque type defined here +LL | +LL | fn get_one<'a>(a: *mut &'a str) -> impl IntoIterator> { + | -- hidden type `*mut &'a str` captures the lifetime `'a` as defined here +... +LL | None::> + | ^^^^^^^^^^^^^^^^^^^^^^^ + | +help: to declare that `impl IntoIterator>` captures `'a`, you can add an explicit `'a` lifetime bound + | +LL | fn get_one<'a>(a: *mut &'a str) -> impl IntoIterator> + 'a { + | ++++ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0700`. diff --git a/tests/ui/type-alias-impl-trait/different_args_considered_equal3.rs b/tests/ui/type-alias-impl-trait/different_args_considered_equal3.rs new file mode 100644 index 0000000000000..ea69175ba3108 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/different_args_considered_equal3.rs @@ -0,0 +1,22 @@ +//! Test that we don't allow coercing an opaque type with a non-static +//! lifetime to one with a static lifetime. While `get_iter` looks like +//! it would be doing the opposite, the way we're handling projections +//! makes `Opaque<'a>` the hidden type of `Opaque<'static>`. + +#![feature(type_alias_impl_trait)] + +mod defining_scope { + pub type Opaque<'a> = impl Sized; + + fn get_one<'a>(a: *mut &'a str) -> Opaque<'a> { + a + } +} +use defining_scope::Opaque; + +fn get_iter<'a>() -> impl IntoIterator> { + None::> + //~^ ERROR lifetime may not live long enough +} + +fn main() {} diff --git a/tests/ui/type-alias-impl-trait/different_args_considered_equal3.stderr b/tests/ui/type-alias-impl-trait/different_args_considered_equal3.stderr new file mode 100644 index 0000000000000..d8f70e3d77825 --- /dev/null +++ b/tests/ui/type-alias-impl-trait/different_args_considered_equal3.stderr @@ -0,0 +1,10 @@ +error: lifetime may not live long enough + --> $DIR/different_args_considered_equal3.rs:18:5 + | +LL | fn get_iter<'a>() -> impl IntoIterator> { + | -- lifetime `'a` defined here +LL | None::> + | ^^^^^^^^^^^^^^^^^^^^^^^ returning this value requires that `'a` must outlive `'static` + +error: aborting due to 1 previous error + From 83cb760e2c2b9fa3f0bb90ad2941f4cbceba2255 Mon Sep 17 00:00:00 2001 From: Oneirical Date: Thu, 30 May 2024 16:20:49 -0400 Subject: [PATCH 29/33] run_make_support nm implementation + bin-emit-no-symbols rmake rewrite --- library/backtrace | 2 +- src/doc/book | 2 +- src/doc/edition-guide | 2 +- src/doc/reference | 2 +- src/doc/rust-by-example | 2 +- src/doc/rustc-dev-guide | 2 +- src/tools/cargo | 2 +- src/tools/run-make-support/src/nm/mod.rs | 48 +++++++++++++++++++ .../tidy/src/allowed_run_make_makefiles.txt | 1 - .../app.rs | 0 tests/run-make/bin-emit-no-symbols/rmake.rs | 19 ++++++++ tests/run-make/issue-51671/Makefile | 9 ---- 12 files changed, 74 insertions(+), 17 deletions(-) create mode 100644 src/tools/run-make-support/src/nm/mod.rs rename tests/run-make/{issue-51671 => bin-emit-no-symbols}/app.rs (100%) create mode 100644 tests/run-make/bin-emit-no-symbols/rmake.rs delete mode 100644 tests/run-make/issue-51671/Makefile diff --git a/library/backtrace b/library/backtrace index 72265bea21089..5e05efa87905f 160000 --- a/library/backtrace +++ b/library/backtrace @@ -1 +1 @@ -Subproject commit 72265bea210891ae47bbe6d4f17b493ef0606619 +Subproject commit 5e05efa87905fb5b351a2bc5644d60c57d6d9327 diff --git a/src/doc/book b/src/doc/book index 45c1a6d69edfd..5e9051f71638a 160000 --- a/src/doc/book +++ b/src/doc/book @@ -1 +1 @@ -Subproject commit 45c1a6d69edfd1fc91fb7504cb73958dbd09441e +Subproject commit 5e9051f71638aa941cd5dda465e25c61cde9594f diff --git a/src/doc/edition-guide b/src/doc/edition-guide index cb58c430b4e80..bbaabbe088e21 160000 --- a/src/doc/edition-guide +++ b/src/doc/edition-guide @@ -1 +1 @@ -Subproject commit cb58c430b4e8054c2cb81d2d4434092c482a93d8 +Subproject commit bbaabbe088e21a81a0d9ae6757705020d5d7b416 diff --git a/src/doc/reference b/src/doc/reference index 0b805c6580401..6019b76f5b289 160000 --- a/src/doc/reference +++ b/src/doc/reference @@ -1 +1 @@ -Subproject commit 0b805c65804019b0ac8f2fe3117afad82a6069b8 +Subproject commit 6019b76f5b28938565b251bbba0bf5cc5c43d863 diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example index b1d97bd6113ab..4840dca06cadf 160000 --- a/src/doc/rust-by-example +++ b/src/doc/rust-by-example @@ -1 +1 @@ -Subproject commit b1d97bd6113aba732b2091ce093c76f2d05bb8a0 +Subproject commit 4840dca06cadf48b305d3ce0aeafde7f80933f80 diff --git a/src/doc/rustc-dev-guide b/src/doc/rustc-dev-guide index aec82168dd312..6a7374bd87cba 160000 --- a/src/doc/rustc-dev-guide +++ b/src/doc/rustc-dev-guide @@ -1 +1 @@ -Subproject commit aec82168dd3121289a194b381f56076fc789a4d2 +Subproject commit 6a7374bd87cbac0f8be4fd4877d8186d9c313985 diff --git a/src/tools/cargo b/src/tools/cargo index a1f47ec3f7cd0..431db31d0dbed 160000 --- a/src/tools/cargo +++ b/src/tools/cargo @@ -1 +1 @@ -Subproject commit a1f47ec3f7cd076986f1bfcd7061f2e8cb1a726e +Subproject commit 431db31d0dbeda320caf8ef8535ea48eb3093407 diff --git a/src/tools/run-make-support/src/nm/mod.rs b/src/tools/run-make-support/src/nm/mod.rs new file mode 100644 index 0000000000000..c304877eba191 --- /dev/null +++ b/src/tools/run-make-support/src/nm/mod.rs @@ -0,0 +1,48 @@ +use crate::{fs_wrapper, object}; +use object::{Object, ObjectSection}; +use std::path::Path; + +#[derive(Debug)] +pub struct Nm { + file: Option, +} + +pub fn nm() -> Nm { + Nm::new() +} + +impl Nm { + /// Construct a bare `nm` invocation. + pub fn new() -> Self { + Self { file: None } + } + + /// Specify the file to analyze the symbols of. + pub fn input>(&mut self, path: P) -> &mut Self { + &mut Self { + file: Some( + object::File::parse(fs_wrapper::read(path)) + .expect(format!("Failed to parse ELF file at {:?}", path.as_ref().display())), + ), + } + } + + /// Collect all symbols of an object file into a String. + pub fn collect_symbols(&self) -> String { + let object_file = self.file; + let mut symbols_str = String::new(); + for section in object_file.sections() { + if let Ok(ObjectSection::SymbolTable(st)) = section.parse::() { + for symbol in st.symbols() { + symbols_str.push_str(&format!( + "{:016x} {:?} {}\n", + symbol.address(), + symbol.kind(), + symbol.name() + )); + } + } + } + symbols_str + } +} diff --git a/src/tools/tidy/src/allowed_run_make_makefiles.txt b/src/tools/tidy/src/allowed_run_make_makefiles.txt index 1596257747fa9..98438e8004dc1 100644 --- a/src/tools/tidy/src/allowed_run_make_makefiles.txt +++ b/src/tools/tidy/src/allowed_run_make_makefiles.txt @@ -84,7 +84,6 @@ run-make/issue-37839/Makefile run-make/issue-40535/Makefile run-make/issue-47384/Makefile run-make/issue-47551/Makefile -run-make/issue-51671/Makefile run-make/issue-68794-textrel-on-minimal-lib/Makefile run-make/issue-69368/Makefile run-make/issue-83045/Makefile diff --git a/tests/run-make/issue-51671/app.rs b/tests/run-make/bin-emit-no-symbols/app.rs similarity index 100% rename from tests/run-make/issue-51671/app.rs rename to tests/run-make/bin-emit-no-symbols/app.rs diff --git a/tests/run-make/bin-emit-no-symbols/rmake.rs b/tests/run-make/bin-emit-no-symbols/rmake.rs new file mode 100644 index 0000000000000..6d6d6b3296742 --- /dev/null +++ b/tests/run-make/bin-emit-no-symbols/rmake.rs @@ -0,0 +1,19 @@ +// When setting the crate type as a "bin" (in app.rs), +// this could cause a bug where some symbols would not be +// emitted in the object files. This has been fixed, and +// this test checks that the correct symbols have been successfully +// emitted inside the object files. +// See https://github.com/rust-lang/rust/issues/51671 + +use run_make_support::{nm, rustc, tmp_dir}; + +fn main() { + rustc().emit("obj").input("app.rs").run(); + //FIXME(Oneirical): This should eventually be rmake_out_path + let nm = nm(tmp_dir().join("app.o")); + assert!( + nm.contains("rust_begin_unwind") + && nm.contains("rust_eh_personality") + && nm.contains("__rg_oom") + ); +} diff --git a/tests/run-make/issue-51671/Makefile b/tests/run-make/issue-51671/Makefile deleted file mode 100644 index c93645369928c..0000000000000 --- a/tests/run-make/issue-51671/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -include ../tools.mk - -# ignore-windows-msvc - -all: - $(RUSTC) --emit=obj app.rs - nm $(TMPDIR)/app.o | $(CGREP) rust_begin_unwind - nm $(TMPDIR)/app.o | $(CGREP) rust_eh_personality - nm $(TMPDIR)/app.o | $(CGREP) __rg_oom From c1597f90396c0cf38b196b584be49a17e6e881a6 Mon Sep 17 00:00:00 2001 From: Oneirical Date: Wed, 5 Jun 2024 16:29:42 -0400 Subject: [PATCH 30/33] try implementing suggestions --- library/backtrace | 2 +- src/doc/book | 2 +- src/doc/edition-guide | 2 +- src/doc/reference | 2 +- src/doc/rust-by-example | 2 +- src/doc/rustc-dev-guide | 2 +- src/tools/cargo | 2 +- src/tools/run-make-support/src/nm/mod.rs | 8 +++++--- 8 files changed, 12 insertions(+), 10 deletions(-) diff --git a/library/backtrace b/library/backtrace index 5e05efa87905f..72265bea21089 160000 --- a/library/backtrace +++ b/library/backtrace @@ -1 +1 @@ -Subproject commit 5e05efa87905fb5b351a2bc5644d60c57d6d9327 +Subproject commit 72265bea210891ae47bbe6d4f17b493ef0606619 diff --git a/src/doc/book b/src/doc/book index 5e9051f71638a..45c1a6d69edfd 160000 --- a/src/doc/book +++ b/src/doc/book @@ -1 +1 @@ -Subproject commit 5e9051f71638aa941cd5dda465e25c61cde9594f +Subproject commit 45c1a6d69edfd1fc91fb7504cb73958dbd09441e diff --git a/src/doc/edition-guide b/src/doc/edition-guide index bbaabbe088e21..cb58c430b4e80 160000 --- a/src/doc/edition-guide +++ b/src/doc/edition-guide @@ -1 +1 @@ -Subproject commit bbaabbe088e21a81a0d9ae6757705020d5d7b416 +Subproject commit cb58c430b4e8054c2cb81d2d4434092c482a93d8 diff --git a/src/doc/reference b/src/doc/reference index 6019b76f5b289..0b805c6580401 160000 --- a/src/doc/reference +++ b/src/doc/reference @@ -1 +1 @@ -Subproject commit 6019b76f5b28938565b251bbba0bf5cc5c43d863 +Subproject commit 0b805c65804019b0ac8f2fe3117afad82a6069b8 diff --git a/src/doc/rust-by-example b/src/doc/rust-by-example index 4840dca06cadf..b1d97bd6113ab 160000 --- a/src/doc/rust-by-example +++ b/src/doc/rust-by-example @@ -1 +1 @@ -Subproject commit 4840dca06cadf48b305d3ce0aeafde7f80933f80 +Subproject commit b1d97bd6113aba732b2091ce093c76f2d05bb8a0 diff --git a/src/doc/rustc-dev-guide b/src/doc/rustc-dev-guide index 6a7374bd87cba..aec82168dd312 160000 --- a/src/doc/rustc-dev-guide +++ b/src/doc/rustc-dev-guide @@ -1 +1 @@ -Subproject commit 6a7374bd87cbac0f8be4fd4877d8186d9c313985 +Subproject commit aec82168dd3121289a194b381f56076fc789a4d2 diff --git a/src/tools/cargo b/src/tools/cargo index 431db31d0dbed..a1f47ec3f7cd0 160000 --- a/src/tools/cargo +++ b/src/tools/cargo @@ -1 +1 @@ -Subproject commit 431db31d0dbeda320caf8ef8535ea48eb3093407 +Subproject commit a1f47ec3f7cd076986f1bfcd7061f2e8cb1a726e diff --git a/src/tools/run-make-support/src/nm/mod.rs b/src/tools/run-make-support/src/nm/mod.rs index c304877eba191..1f41792921d67 100644 --- a/src/tools/run-make-support/src/nm/mod.rs +++ b/src/tools/run-make-support/src/nm/mod.rs @@ -3,8 +3,8 @@ use object::{Object, ObjectSection}; use std::path::Path; #[derive(Debug)] -pub struct Nm { - file: Option, +pub struct Nm<'a> { + file: Option>, } pub fn nm() -> Nm { @@ -32,7 +32,9 @@ impl Nm { let object_file = self.file; let mut symbols_str = String::new(); for section in object_file.sections() { - if let Ok(ObjectSection::SymbolTable(st)) = section.parse::() { + if let Ok(object::read::elf::SymbolTable(st)) = + section.parse::>() + { for symbol in st.symbols() { symbols_str.push_str(&format!( "{:016x} {:?} {}\n", From 977d3f6f96728b6cd68bb4c36f34fcb659989dcc Mon Sep 17 00:00:00 2001 From: Oneirical Date: Tue, 18 Jun 2024 14:57:00 -0400 Subject: [PATCH 31/33] use llvm_readobj in run-make test instead of nm --- src/tools/run-make-support/src/llvm.rs | 28 +++++++++--- src/tools/run-make-support/src/nm/mod.rs | 50 --------------------- tests/run-make/bin-emit-no-symbols/rmake.rs | 13 +++--- 3 files changed, 28 insertions(+), 63 deletions(-) delete mode 100644 src/tools/run-make-support/src/nm/mod.rs diff --git a/src/tools/run-make-support/src/llvm.rs b/src/tools/run-make-support/src/llvm.rs index fe4131819baec..99bce08fc2387 100644 --- a/src/tools/run-make-support/src/llvm.rs +++ b/src/tools/run-make-support/src/llvm.rs @@ -2,8 +2,8 @@ use std::path::{Path, PathBuf}; use crate::{env_var, Command}; -/// Construct a new `llvm-readobj` invocation. This assumes that `llvm-readobj` is available -/// at `$LLVM_BIN_DIR/llvm-readobj`. +/// Construct a new `llvm-readobj` invocation with the `GNU` output style. +/// This assumes that `llvm-readobj` is available at `$LLVM_BIN_DIR/llvm-readobj`. #[track_caller] pub fn llvm_readobj() -> LlvmReadobj { LlvmReadobj::new() @@ -70,13 +70,24 @@ pub fn llvm_bin_dir() -> PathBuf { } impl LlvmReadobj { - /// Construct a new `llvm-readobj` invocation. This assumes that `llvm-readobj` is available - /// at `$LLVM_BIN_DIR/llvm-readobj`. + /// Construct a new `llvm-readobj` invocation with the `GNU` output style. + /// This assumes that `llvm-readobj` is available at `$LLVM_BIN_DIR/llvm-readobj`. #[track_caller] pub fn new() -> Self { let llvm_readobj = llvm_bin_dir().join("llvm-readobj"); let cmd = Command::new(llvm_readobj); - Self { cmd } + let mut readobj = Self { cmd }; + readobj.elf_output_style("GNU"); + readobj + } + + /// Specify the format of the ELF information. + /// + /// Valid options are `LLVM` (default), `GNU`, and `JSON`. + pub fn elf_output_style(&mut self, style: &str) -> &mut Self { + self.cmd.arg("--elf-output-style"); + self.cmd.arg(style); + self } /// Provide an input file. @@ -90,6 +101,13 @@ impl LlvmReadobj { self.cmd.arg("--file-header"); self } + + /// Specify the section to display. + pub fn section(&mut self, section: &str) -> &mut Self { + self.cmd.arg("--string-dump"); + self.cmd.arg(section); + self + } } impl LlvmProfdata { diff --git a/src/tools/run-make-support/src/nm/mod.rs b/src/tools/run-make-support/src/nm/mod.rs deleted file mode 100644 index 1f41792921d67..0000000000000 --- a/src/tools/run-make-support/src/nm/mod.rs +++ /dev/null @@ -1,50 +0,0 @@ -use crate::{fs_wrapper, object}; -use object::{Object, ObjectSection}; -use std::path::Path; - -#[derive(Debug)] -pub struct Nm<'a> { - file: Option>, -} - -pub fn nm() -> Nm { - Nm::new() -} - -impl Nm { - /// Construct a bare `nm` invocation. - pub fn new() -> Self { - Self { file: None } - } - - /// Specify the file to analyze the symbols of. - pub fn input>(&mut self, path: P) -> &mut Self { - &mut Self { - file: Some( - object::File::parse(fs_wrapper::read(path)) - .expect(format!("Failed to parse ELF file at {:?}", path.as_ref().display())), - ), - } - } - - /// Collect all symbols of an object file into a String. - pub fn collect_symbols(&self) -> String { - let object_file = self.file; - let mut symbols_str = String::new(); - for section in object_file.sections() { - if let Ok(object::read::elf::SymbolTable(st)) = - section.parse::>() - { - for symbol in st.symbols() { - symbols_str.push_str(&format!( - "{:016x} {:?} {}\n", - symbol.address(), - symbol.kind(), - symbol.name() - )); - } - } - } - symbols_str - } -} diff --git a/tests/run-make/bin-emit-no-symbols/rmake.rs b/tests/run-make/bin-emit-no-symbols/rmake.rs index 6d6d6b3296742..5586e53c05084 100644 --- a/tests/run-make/bin-emit-no-symbols/rmake.rs +++ b/tests/run-make/bin-emit-no-symbols/rmake.rs @@ -5,15 +5,12 @@ // emitted inside the object files. // See https://github.com/rust-lang/rust/issues/51671 -use run_make_support::{nm, rustc, tmp_dir}; +use run_make_support::{llvm_readobj, rustc}; fn main() { rustc().emit("obj").input("app.rs").run(); - //FIXME(Oneirical): This should eventually be rmake_out_path - let nm = nm(tmp_dir().join("app.o")); - assert!( - nm.contains("rust_begin_unwind") - && nm.contains("rust_eh_personality") - && nm.contains("__rg_oom") - ); + let out = llvm_readobj().input("app.o").arg("--symbols").run(); + out.assert_stdout_contains("rust_begin_unwind"); + out.assert_stdout_contains("rust_eh_personality"); + out.assert_stdout_contains("__rg_oom"); } From bfe6a26af6669485b4661877583ebe3d22cd0317 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: Tue, 18 Jun 2024 21:05:25 +0000 Subject: [PATCH 32/33] hir_typeck: be more conservative in making "note caller chooses ty param" note - Avoid "caller chooses ty for type param" note if the found type a.k.a. the return expression type *contains* the type parameter, because e.g. `&T` will always be different from `T` (i.e. "well duh"). - Rename `note_caller_chooses_ty_for_ty_param` to `try_note_caller_chooses_ty_for_ty_param` because the note is not always made. Issue: https://github.com/rust-lang/rust/issues/126547 --- .../src/fn_ctxt/suggestions.rs | 32 +++++++++++++------ 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs index 337a92c0d0120..9dd82868adc54 100644 --- a/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs +++ b/compiler/rustc_hir_typeck/src/fn_ctxt/suggestions.rs @@ -859,10 +859,10 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { } else { // Only point to return type if the expected type is the return type, as if they // are not, the expectation must have been caused by something else. - debug!("return type {:?}", hir_ty); + debug!(?hir_ty, "return type"); let ty = self.lowerer().lower_ty(hir_ty); - debug!("return type {:?}", ty); - debug!("expected type {:?}", expected); + debug!(?ty, "return type (lowered)"); + debug!(?expected, "expected type"); let bound_vars = self.tcx.late_bound_vars(hir_ty.hir_id.owner.into()); let ty = Binder::bind_with_vars(ty, bound_vars); let ty = self.normalize(hir_ty.span, ty); @@ -873,7 +873,7 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { expected, }); self.try_suggest_return_impl_trait(err, expected, found, fn_id); - self.note_caller_chooses_ty_for_ty_param(err, expected, found); + self.try_note_caller_chooses_ty_for_ty_param(err, expected, found); return true; } } @@ -883,18 +883,30 @@ impl<'a, 'tcx> FnCtxt<'a, 'tcx> { false } - fn note_caller_chooses_ty_for_ty_param( + fn try_note_caller_chooses_ty_for_ty_param( &self, diag: &mut Diag<'_>, expected: Ty<'tcx>, found: Ty<'tcx>, ) { - if let ty::Param(expected_ty_as_param) = expected.kind() { - diag.subdiagnostic(errors::NoteCallerChoosesTyForTyParam { - ty_param_name: expected_ty_as_param.name, - found_ty: found, - }); + // Only show the note if: + // 1. `expected` ty is a type parameter; + // 2. The `expected` type parameter does *not* occur in the return expression type. This can + // happen for e.g. `fn foo(t: &T) -> T { t }`, where `expected` is `T` but `found` is + // `&T`. Saying "the caller chooses a type for `T` which can be different from `&T`" is + // "well duh" and is only confusing and not helpful. + let ty::Param(expected_ty_as_param) = expected.kind() else { + return; + }; + + if found.contains(expected) { + return; } + + diag.subdiagnostic(errors::NoteCallerChoosesTyForTyParam { + ty_param_name: expected_ty_as_param.name, + found_ty: found, + }); } /// check whether the return type is a generic type with a trait bound From 939026c8fba0d6b9edc2e2cdfbbf6dd6fb575662 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: Sun, 16 Jun 2024 17:57:01 +0000 Subject: [PATCH 33/33] tests: update tests for more conservative return ty mismatch note --- tests/ui/return/return-ty-mismatch-note.rs | 11 +++++++++- .../ui/return/return-ty-mismatch-note.stderr | 21 +++++++++++++++---- ...n-unconstrained-borrowed-type-param.stderr | 1 - 3 files changed, 27 insertions(+), 6 deletions(-) diff --git a/tests/ui/return/return-ty-mismatch-note.rs b/tests/ui/return/return-ty-mismatch-note.rs index 352bc2a163763..36d590519fba2 100644 --- a/tests/ui/return/return-ty-mismatch-note.rs +++ b/tests/ui/return/return-ty-mismatch-note.rs @@ -1,4 +1,5 @@ -// Checks existence of a note for "a caller chooses ty for ty param" upon return ty mismatch. +// Checks existence or absence of a note for "a caller chooses ty for ty param" upon return ty +// mismatch. fn f() -> (T,) { (0,) //~ ERROR mismatched types @@ -14,6 +15,14 @@ fn h() -> u8 { 0u8 } +// This case was reported in where it doesn't +// make sense to make the "note caller chooses ty for ty param" note if the found type contains +// the ty param... +fn k(_t: &T) -> T { + _t + //~^ ERROR mismatched types +} + fn main() { f::<()>(); g::<(), ()>; diff --git a/tests/ui/return/return-ty-mismatch-note.stderr b/tests/ui/return/return-ty-mismatch-note.stderr index 135903da5c263..47ef6863063c7 100644 --- a/tests/ui/return/return-ty-mismatch-note.stderr +++ b/tests/ui/return/return-ty-mismatch-note.stderr @@ -1,5 +1,5 @@ error[E0308]: mismatched types - --> $DIR/return-ty-mismatch-note.rs:4:6 + --> $DIR/return-ty-mismatch-note.rs:5:6 | LL | fn f() -> (T,) { | - expected this type parameter @@ -10,7 +10,7 @@ LL | (0,) found type `{integer}` error[E0308]: mismatched types - --> $DIR/return-ty-mismatch-note.rs:8:6 + --> $DIR/return-ty-mismatch-note.rs:9:6 | LL | fn g() -> (U, V) { | - expected this type parameter @@ -21,7 +21,7 @@ LL | (0, "foo") found type `{integer}` error[E0308]: mismatched types - --> $DIR/return-ty-mismatch-note.rs:8:9 + --> $DIR/return-ty-mismatch-note.rs:9:9 | LL | fn g() -> (U, V) { | - expected this type parameter @@ -31,6 +31,19 @@ LL | (0, "foo") = note: expected type parameter `V` found reference `&'static str` -error: aborting due to 3 previous errors +error[E0308]: mismatched types + --> $DIR/return-ty-mismatch-note.rs:22:5 + | +LL | fn k(_t: &T) -> T { + | - - expected `T` because of return type + | | + | expected this type parameter +LL | _t + | ^^ expected type parameter `T`, found `&T` + | + = note: expected type parameter `_` + found reference `&_` + +error: aborting due to 4 previous errors For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/suggestions/clone-on-unconstrained-borrowed-type-param.stderr b/tests/ui/suggestions/clone-on-unconstrained-borrowed-type-param.stderr index 2c4be26a82b7b..afbb9c32d516e 100644 --- a/tests/ui/suggestions/clone-on-unconstrained-borrowed-type-param.stderr +++ b/tests/ui/suggestions/clone-on-unconstrained-borrowed-type-param.stderr @@ -10,7 +10,6 @@ LL | t.clone() | = note: expected type parameter `_` found reference `&_` - = note: the caller chooses a type for `T` which can be different from `&T` note: `T` does not implement `Clone`, so `&T` was cloned instead --> $DIR/clone-on-unconstrained-borrowed-type-param.rs:3:5 |