From 18c248b6564ea750321cf9765f700def4265e490 Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Sun, 23 Jun 2024 23:53:28 +0200 Subject: [PATCH 01/32] Make `NEVER_TYPE_FALLBACK_FLOWING_INTO_UNSAFE` deny-by-default in e2024 --- compiler/rustc_lint_defs/src/builtin.rs | 1 + ...fallback-flowing-into-unsafe.e2015.stderr} | 20 +-- ...-fallback-flowing-into-unsafe.e2024.stderr | 116 ++++++++++++++++++ ...never-type-fallback-flowing-into-unsafe.rs | 46 +++++-- 4 files changed, 160 insertions(+), 23 deletions(-) rename tests/ui/never_type/{lint-never-type-fallback-flowing-into-unsafe.stderr => lint-never-type-fallback-flowing-into-unsafe.e2015.stderr} (86%) create mode 100644 tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2024.stderr diff --git a/compiler/rustc_lint_defs/src/builtin.rs b/compiler/rustc_lint_defs/src/builtin.rs index 1913b9d6a1c31..e270cbe610772 100644 --- a/compiler/rustc_lint_defs/src/builtin.rs +++ b/compiler/rustc_lint_defs/src/builtin.rs @@ -4197,6 +4197,7 @@ declare_lint! { reason: FutureIncompatibilityReason::FutureReleaseSemanticsChange, reference: "issue #123748 ", }; + @edition Edition2024 => Deny; report_in_external_macro } diff --git a/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.stderr b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2015.stderr similarity index 86% rename from tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.stderr rename to tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2015.stderr index fbd92f8f662bb..a75039b8237a0 100644 --- a/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.stderr +++ b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2015.stderr @@ -1,5 +1,5 @@ warning: never type fallback affects this call to an `unsafe` function - --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:8:18 + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:13:18 | LL | unsafe { mem::zeroed() } | ^^^^^^^^^^^^^ @@ -10,7 +10,7 @@ LL | unsafe { mem::zeroed() } = note: `#[warn(never_type_fallback_flowing_into_unsafe)]` on by default warning: never type fallback affects this call to an `unsafe` function - --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:23:13 + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:30:13 | LL | core::mem::transmute(Zst) | ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -20,7 +20,7 @@ LL | core::mem::transmute(Zst) = help: specify the type explicitly warning: never type fallback affects this union access - --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:39:18 + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:47:18 | LL | unsafe { Union { a: () }.b } | ^^^^^^^^^^^^^^^^^ @@ -30,7 +30,7 @@ LL | unsafe { Union { a: () }.b } = help: specify the type explicitly warning: never type fallback affects this raw pointer dereference - --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:49:18 + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:58:18 | LL | unsafe { *ptr::from_ref(&()).cast() } | ^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -40,7 +40,7 @@ LL | unsafe { *ptr::from_ref(&()).cast() } = help: specify the type explicitly warning: never type fallback affects this call to an `unsafe` function - --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:67:18 + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:79:18 | LL | unsafe { internally_create(x) } | ^^^^^^^^^^^^^^^^^^^^ @@ -50,7 +50,7 @@ LL | unsafe { internally_create(x) } = help: specify the type explicitly warning: never type fallback affects this call to an `unsafe` function - --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:83:18 + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:97:18 | LL | unsafe { zeroed() } | ^^^^^^^^ @@ -60,7 +60,7 @@ LL | unsafe { zeroed() } = help: specify the type explicitly warning: never type fallback affects this `unsafe` function - --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:79:22 + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:92:22 | LL | let zeroed = mem::zeroed; | ^^^^^^^^^^^ @@ -70,7 +70,7 @@ LL | let zeroed = mem::zeroed; = help: specify the type explicitly warning: never type fallback affects this `unsafe` function - --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:98:17 + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:115:17 | LL | let f = internally_create; | ^^^^^^^^^^^^^^^^^ @@ -80,7 +80,7 @@ LL | let f = internally_create; = help: specify the type explicitly warning: never type fallback affects this call to an `unsafe` method - --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:122:13 + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:140:13 | LL | S(marker::PhantomData).create_out_of_thin_air() | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -90,7 +90,7 @@ LL | S(marker::PhantomData).create_out_of_thin_air() = help: specify the type explicitly warning: never type fallback affects this call to an `unsafe` function - --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:139:19 + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:158:19 | LL | match send_message::<_ /* ?0 */>() { | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2024.stderr b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2024.stderr new file mode 100644 index 0000000000000..4138e9f8c8622 --- /dev/null +++ b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.e2024.stderr @@ -0,0 +1,116 @@ +error: never type fallback affects this call to an `unsafe` function + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:13:18 + | +LL | unsafe { mem::zeroed() } + | ^^^^^^^^^^^^^ + | + = warning: this will change its meaning in a future release! + = note: for more information, see issue #123748 + = help: specify the type explicitly + = note: `#[deny(never_type_fallback_flowing_into_unsafe)]` on by default + +error: never type fallback affects this call to an `unsafe` function + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:30:13 + | +LL | core::mem::transmute(Zst) + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this will change its meaning in a future release! + = note: for more information, see issue #123748 + = help: specify the type explicitly + +error: never type fallback affects this union access + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:47:18 + | +LL | unsafe { Union { a: () }.b } + | ^^^^^^^^^^^^^^^^^ + | + = warning: this will change its meaning in a future release! + = note: for more information, see issue #123748 + = help: specify the type explicitly + +error: never type fallback affects this raw pointer dereference + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:58:18 + | +LL | unsafe { *ptr::from_ref(&()).cast() } + | ^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this will change its meaning in a future release! + = note: for more information, see issue #123748 + = help: specify the type explicitly + +error: never type fallback affects this call to an `unsafe` function + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:79:18 + | +LL | unsafe { internally_create(x) } + | ^^^^^^^^^^^^^^^^^^^^ + | + = warning: this will change its meaning in a future release! + = note: for more information, see issue #123748 + = help: specify the type explicitly + +error: never type fallback affects this call to an `unsafe` function + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:97:18 + | +LL | unsafe { zeroed() } + | ^^^^^^^^ + | + = warning: this will change its meaning in a future release! + = note: for more information, see issue #123748 + = help: specify the type explicitly + +error: never type fallback affects this `unsafe` function + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:92:22 + | +LL | let zeroed = mem::zeroed; + | ^^^^^^^^^^^ + | + = warning: this will change its meaning in a future release! + = note: for more information, see issue #123748 + = help: specify the type explicitly + +error: never type fallback affects this `unsafe` function + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:115:17 + | +LL | let f = internally_create; + | ^^^^^^^^^^^^^^^^^ + | + = warning: this will change its meaning in a future release! + = note: for more information, see issue #123748 + = help: specify the type explicitly + +error: never type fallback affects this call to an `unsafe` method + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:140:13 + | +LL | S(marker::PhantomData).create_out_of_thin_air() + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + | + = warning: this will change its meaning in a future release! + = note: for more information, see issue #123748 + = help: specify the type explicitly + +error: never type fallback affects this call to an `unsafe` function + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:158:19 + | +LL | match send_message::<_ /* ?0 */>() { + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | msg_send!(); + | ----------- in this macro invocation + | + = warning: this will change its meaning in a future release! + = note: for more information, see issue #123748 + = help: specify the type explicitly + = note: this error originates in the macro `msg_send` (in Nightly builds, run with -Z macro-backtrace for more info) + +warning: the type `!` does not permit zero-initialization + --> $DIR/lint-never-type-fallback-flowing-into-unsafe.rs:13:18 + | +LL | unsafe { mem::zeroed() } + | ^^^^^^^^^^^^^ this code causes undefined behavior when executed + | + = note: the `!` type has no valid value + = note: `#[warn(invalid_value)]` on by default + +error: aborting due to 10 previous errors; 1 warning emitted + diff --git a/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.rs b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.rs index d65bfee843e3e..c96f4dda3f8ef 100644 --- a/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.rs +++ b/tests/ui/never_type/lint-never-type-fallback-flowing-into-unsafe.rs @@ -1,4 +1,9 @@ -//@ check-pass +//@ revisions: e2015 e2024 +//@[e2015] check-pass +//@[e2024] check-fail +//@[e2024] edition:2024 +//@[e2024] compile-flags: -Zunstable-options + use std::{marker, mem, ptr}; fn main() {} @@ -6,8 +11,10 @@ fn main() {} fn _zero() { if false { unsafe { mem::zeroed() } - //~^ warn: never type fallback affects this call to an `unsafe` function + //[e2015]~^ warn: never type fallback affects this call to an `unsafe` function + //[e2024]~^^ error: never type fallback affects this call to an `unsafe` function //~| warn: this will change its meaning in a future release! + //[e2024]~| warning: the type `!` does not permit zero-initialization } else { return; }; @@ -21,7 +28,8 @@ fn _trans() { unsafe { struct Zst; core::mem::transmute(Zst) - //~^ warn: never type fallback affects this call to an `unsafe` function + //[e2015]~^ warn: never type fallback affects this call to an `unsafe` function + //[e2024]~^^ error: never type fallback affects this call to an `unsafe` function //~| warn: this will change its meaning in a future release! } } else { @@ -37,7 +45,8 @@ fn _union() { } unsafe { Union { a: () }.b } - //~^ warn: never type fallback affects this union access + //[e2015]~^ warn: never type fallback affects this union access + //[e2024]~^^ error: never type fallback affects this union access //~| warn: this will change its meaning in a future release! } else { return; @@ -47,7 +56,8 @@ fn _union() { fn _deref() { if false { unsafe { *ptr::from_ref(&()).cast() } - //~^ warn: never type fallback affects this raw pointer dereference + //[e2015]~^ warn: never type fallback affects this raw pointer dereference + //[e2024]~^^ error: never type fallback affects this raw pointer dereference //~| warn: this will change its meaning in a future release! } else { return; @@ -57,7 +67,9 @@ fn _deref() { fn _only_generics() { if false { unsafe fn internally_create(_: Option) { - let _ = mem::zeroed::(); + unsafe { + let _ = mem::zeroed::(); + } } // We need the option (and unwrap later) to call a function in a way, @@ -65,7 +77,8 @@ fn _only_generics() { let x = None; unsafe { internally_create(x) } - //~^ warn: never type fallback affects this call to an `unsafe` function + //[e2015]~^ warn: never type fallback affects this call to an `unsafe` function + //[e2024]~^^ error: never type fallback affects this call to an `unsafe` function //~| warn: this will change its meaning in a future release! x.unwrap() @@ -77,11 +90,13 @@ fn _only_generics() { fn _stored_function() { if false { let zeroed = mem::zeroed; - //~^ warn: never type fallback affects this `unsafe` function + //[e2015]~^ warn: never type fallback affects this `unsafe` function + //[e2024]~^^ error: never type fallback affects this `unsafe` function //~| warn: this will change its meaning in a future release! unsafe { zeroed() } - //~^ warn: never type fallback affects this call to an `unsafe` function + //[e2015]~^ warn: never type fallback affects this call to an `unsafe` function + //[e2024]~^^ error: never type fallback affects this call to an `unsafe` function //~| warn: this will change its meaning in a future release! } else { return; @@ -91,12 +106,15 @@ fn _stored_function() { fn _only_generics_stored_function() { if false { unsafe fn internally_create(_: Option) { - let _ = mem::zeroed::(); + unsafe { + let _ = mem::zeroed::(); + } } let x = None; let f = internally_create; - //~^ warn: never type fallback affects this `unsafe` function + //[e2015]~^ warn: never type fallback affects this `unsafe` function + //[e2024]~^^ error: never type fallback affects this `unsafe` function //~| warn: this will change its meaning in a future release! unsafe { f(x) } @@ -120,7 +138,8 @@ fn _method() { if false { unsafe { S(marker::PhantomData).create_out_of_thin_air() - //~^ warn: never type fallback affects this call to an `unsafe` method + //[e2015]~^ warn: never type fallback affects this call to an `unsafe` method + //[e2024]~^^ error: never type fallback affects this call to an `unsafe` method //~| warn: this will change its meaning in a future release! } } else { @@ -137,7 +156,8 @@ fn _objc() { macro_rules! msg_send { () => { match send_message::<_ /* ?0 */>() { - //~^ warn: never type fallback affects this call to an `unsafe` function + //[e2015]~^ warn: never type fallback affects this call to an `unsafe` function + //[e2024]~^^ error: never type fallback affects this call to an `unsafe` function //~| warn: this will change its meaning in a future release! Ok(x) => x, Err(_) => loop {}, From 7c7674731524ecbc091293e4f94026ddbf0d7ee7 Mon Sep 17 00:00:00 2001 From: Alona Enraght-Moony Date: Wed, 3 Jul 2024 14:29:26 +0000 Subject: [PATCH 02/32] rustdoc: Remove OpaqueTy It turns out it's never constructed anywhere. --- src/librustdoc/clean/mod.rs | 4 --- src/librustdoc/clean/types.rs | 9 ------- src/librustdoc/fold.rs | 1 - src/librustdoc/formats/cache.rs | 1 - src/librustdoc/formats/item_type.rs | 4 +-- src/librustdoc/html/render/mod.rs | 2 -- src/librustdoc/html/render/print_item.rs | 31 ------------------------ src/librustdoc/json/conversions.rs | 8 ------ src/librustdoc/json/mod.rs | 1 - src/librustdoc/passes/stripper.rs | 3 +-- src/librustdoc/visit.rs | 1 - src/rustdoc-json-types/lib.rs | 10 +------- src/tools/jsondoclint/src/item_kind.rs | 5 ---- src/tools/jsondoclint/src/validator.rs | 12 +++------ 14 files changed, 6 insertions(+), 86 deletions(-) diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index b5660cd849246..bbef3b1dcda74 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -2735,10 +2735,6 @@ fn clean_maybe_renamed_item<'tcx>( Box::new(clean_ty(ty, cx)), Constant { kind: ConstantKind::Local { body: body_id, def_id } }, ), - ItemKind::OpaqueTy(ref ty) => OpaqueTyItem(OpaqueTy { - bounds: ty.bounds.iter().filter_map(|x| clean_generic_bound(x, cx)).collect(), - generics: clean_generics(ty.generics, cx), - }), ItemKind::TyAlias(hir_ty, generics) => { *cx.current_type_aliases.entry(def_id).or_insert(0) += 1; let rustdoc_ty = clean_ty(hir_ty, cx); diff --git a/src/librustdoc/clean/types.rs b/src/librustdoc/clean/types.rs index fe01d17b08a8c..43762106e9235 100644 --- a/src/librustdoc/clean/types.rs +++ b/src/librustdoc/clean/types.rs @@ -828,7 +828,6 @@ pub(crate) enum ItemKind { FunctionItem(Box), ModuleItem(Module), TypeAliasItem(Box), - OpaqueTyItem(OpaqueTy), StaticItem(Static), TraitItem(Box), TraitAliasItem(TraitAlias), @@ -886,7 +885,6 @@ impl ItemKind { | ImportItem(_) | FunctionItem(_) | TypeAliasItem(_) - | OpaqueTyItem(_) | StaticItem(_) | ConstantItem(_, _, _) | TraitAliasItem(_) @@ -920,7 +918,6 @@ impl ItemKind { | ExternCrateItem { .. } | FunctionItem(_) | TypeAliasItem(_) - | OpaqueTyItem(_) | StaticItem(_) | ConstantItem(_, _, _) | TraitAliasItem(_) @@ -2341,12 +2338,6 @@ pub(crate) struct TypeAlias { pub(crate) item_type: Option, } -#[derive(Clone, Debug)] -pub(crate) struct OpaqueTy { - pub(crate) bounds: Vec, - pub(crate) generics: Generics, -} - #[derive(Clone, PartialEq, Eq, Debug, Hash)] pub(crate) struct BareFunctionDecl { pub(crate) safety: hir::Safety, diff --git a/src/librustdoc/fold.rs b/src/librustdoc/fold.rs index 346e9a4e113a5..c905c41e87a80 100644 --- a/src/librustdoc/fold.rs +++ b/src/librustdoc/fold.rs @@ -77,7 +77,6 @@ pub(crate) trait DocFolder: Sized { ExternCrateItem { src: _ } | ImportItem(_) | FunctionItem(_) - | OpaqueTyItem(_) | StaticItem(_) | ConstantItem(_, _, _) | TraitAliasItem(_) diff --git a/src/librustdoc/formats/cache.rs b/src/librustdoc/formats/cache.rs index a3b88a880f2af..624fd1b56ff32 100644 --- a/src/librustdoc/formats/cache.rs +++ b/src/librustdoc/formats/cache.rs @@ -462,7 +462,6 @@ impl<'a, 'tcx> DocFolder for CacheBuilder<'a, 'tcx> { clean::ExternCrateItem { .. } | clean::ImportItem(..) - | clean::OpaqueTyItem(..) | clean::ImplItem(..) | clean::TyMethodItem(..) | clean::MethodItem(..) diff --git a/src/librustdoc/formats/item_type.rs b/src/librustdoc/formats/item_type.rs index d5468798bd397..0b45041d2153d 100644 --- a/src/librustdoc/formats/item_type.rs +++ b/src/librustdoc/formats/item_type.rs @@ -52,7 +52,7 @@ pub(crate) enum ItemType { AssocConst = 19, Union = 20, ForeignType = 21, - OpaqueTy = 22, + // OpaqueTy used to be here, but it was removed in #127276 ProcAttribute = 23, ProcDerive = 24, TraitAlias = 25, @@ -85,7 +85,6 @@ impl<'a> From<&'a clean::Item> for ItemType { clean::EnumItem(..) => ItemType::Enum, clean::FunctionItem(..) => ItemType::Function, clean::TypeAliasItem(..) => ItemType::TypeAlias, - clean::OpaqueTyItem(..) => ItemType::OpaqueTy, clean::StaticItem(..) => ItemType::Static, clean::ConstantItem(..) => ItemType::Constant, clean::TraitItem(..) => ItemType::Trait, @@ -192,7 +191,6 @@ impl ItemType { ItemType::AssocConst => "associatedconstant", ItemType::ForeignType => "foreigntype", ItemType::Keyword => "keyword", - ItemType::OpaqueTy => "opaque", ItemType::ProcAttribute => "attr", ItemType::ProcDerive => "derive", ItemType::TraitAlias => "traitalias", diff --git a/src/librustdoc/html/render/mod.rs b/src/librustdoc/html/render/mod.rs index 877a00e206d11..cd874beab1b20 100644 --- a/src/librustdoc/html/render/mod.rs +++ b/src/librustdoc/html/render/mod.rs @@ -382,7 +382,6 @@ impl AllTypes { ItemType::Macro => self.macros.insert(ItemEntry::new(new_url, name)), ItemType::Function => self.functions.insert(ItemEntry::new(new_url, name)), ItemType::TypeAlias => self.type_aliases.insert(ItemEntry::new(new_url, name)), - ItemType::OpaqueTy => self.opaque_tys.insert(ItemEntry::new(new_url, name)), ItemType::Static => self.statics.insert(ItemEntry::new(new_url, name)), ItemType::Constant => self.constants.insert(ItemEntry::new(new_url, name)), ItemType::ProcAttribute => { @@ -2284,7 +2283,6 @@ fn item_ty_to_section(ty: ItemType) -> ItemSection { ItemType::AssocConst => ItemSection::AssociatedConstants, ItemType::ForeignType => ItemSection::ForeignTypes, ItemType::Keyword => ItemSection::Keywords, - ItemType::OpaqueTy => ItemSection::OpaqueTypes, ItemType::ProcAttribute => ItemSection::AttributeMacros, ItemType::ProcDerive => ItemSection::DeriveMacros, ItemType::TraitAlias => ItemSection::TraitAliases, diff --git a/src/librustdoc/html/render/print_item.rs b/src/librustdoc/html/render/print_item.rs index 0097386591529..9be740c97e933 100644 --- a/src/librustdoc/html/render/print_item.rs +++ b/src/librustdoc/html/render/print_item.rs @@ -202,7 +202,6 @@ pub(super) fn print_item(cx: &mut Context<'_>, item: &clean::Item, buf: &mut Buf clean::ConstantItem(..) => "Constant ", clean::ForeignTypeItem => "Foreign Type ", clean::KeywordItem => "Keyword ", - clean::OpaqueTyItem(..) => "Opaque Type ", clean::TraitAliasItem(..) => "Trait Alias ", _ => { // We don't generate pages for any other type. @@ -270,7 +269,6 @@ pub(super) fn print_item(cx: &mut Context<'_>, item: &clean::Item, buf: &mut Buf clean::ConstantItem(generics, ty, c) => item_constant(buf, cx, item, generics, ty, c), clean::ForeignTypeItem => item_foreign_type(buf, cx, item), clean::KeywordItem => item_keyword(buf, cx, item), - clean::OpaqueTyItem(ref e) => item_opaque_ty(buf, cx, item, e), clean::TraitAliasItem(ref ta) => item_trait_alias(buf, cx, item, ta), _ => { // We don't generate pages for any other type. @@ -1210,35 +1208,6 @@ fn item_trait_alias( .unwrap(); } -fn item_opaque_ty( - w: &mut impl fmt::Write, - cx: &mut Context<'_>, - it: &clean::Item, - t: &clean::OpaqueTy, -) { - wrap_item(w, |w| { - write!( - w, - "{attrs}type {name}{generics}{where_clause} = impl {bounds};", - attrs = render_attributes_in_pre(it, "", cx), - name = it.name.unwrap(), - generics = t.generics.print(cx), - where_clause = print_where_clause(&t.generics, cx, 0, Ending::Newline), - bounds = bounds(&t.bounds, false, cx), - ) - .unwrap(); - }); - - write!(w, "{}", document(cx, it, None, HeadingOffset::H2)).unwrap(); - - // Render any items associated directly to this alias, as otherwise they - // won't be visible anywhere in the docs. It would be nice to also show - // associated items from the aliased type (see discussion in #32077), but - // we need #14072 to make sense of the generics. - write!(w, "{}", render_assoc_items(cx, it, it.item_id.expect_def_id(), AssocItemRender::All)) - .unwrap(); -} - fn item_type_alias(w: &mut Buffer, cx: &mut Context<'_>, it: &clean::Item, t: &clean::TypeAlias) { wrap_item(w, |w| { write!( diff --git a/src/librustdoc/json/conversions.rs b/src/librustdoc/json/conversions.rs index 5111e363c522e..2f96ac6e8a716 100644 --- a/src/librustdoc/json/conversions.rs +++ b/src/librustdoc/json/conversions.rs @@ -323,7 +323,6 @@ fn from_clean_item(item: clean::Item, tcx: TyCtxt<'_>) -> ItemEnum { ForeignStaticItem(s, _) => ItemEnum::Static(s.into_tcx(tcx)), ForeignTypeItem => ItemEnum::ForeignType, TypeAliasItem(t) => ItemEnum::TypeAlias(t.into_tcx(tcx)), - OpaqueTyItem(t) => ItemEnum::OpaqueTy(t.into_tcx(tcx)), // FIXME(generic_const_items): Add support for generic free consts ConstantItem(_generics, t, c) => { ItemEnum::Constant { type_: (*t).into_tcx(tcx), const_: c.into_tcx(tcx) } @@ -819,12 +818,6 @@ impl FromWithTcx> for TypeAlias { } } -impl FromWithTcx for OpaqueTy { - fn from_tcx(opaque: clean::OpaqueTy, tcx: TyCtxt<'_>) -> Self { - OpaqueTy { bounds: opaque.bounds.into_tcx(tcx), generics: opaque.generics.into_tcx(tcx) } - } -} - impl FromWithTcx for Static { fn from_tcx(stat: clean::Static, tcx: TyCtxt<'_>) -> Self { Static { @@ -856,7 +849,6 @@ impl FromWithTcx for ItemKind { Enum => ItemKind::Enum, Function | TyMethod | Method => ItemKind::Function, TypeAlias => ItemKind::TypeAlias, - OpaqueTy => ItemKind::OpaqueTy, Static => ItemKind::Static, Constant => ItemKind::Constant, Trait => ItemKind::Trait, diff --git a/src/librustdoc/json/mod.rs b/src/librustdoc/json/mod.rs index 0ef24818515c6..b3fa52b9db4b4 100644 --- a/src/librustdoc/json/mod.rs +++ b/src/librustdoc/json/mod.rs @@ -185,7 +185,6 @@ impl<'tcx> FormatRenderer<'tcx> for JsonRenderer<'tcx> { | types::ItemEnum::TraitAlias(_) | types::ItemEnum::Impl(_) | types::ItemEnum::TypeAlias(_) - | types::ItemEnum::OpaqueTy(_) | types::ItemEnum::Constant { .. } | types::ItemEnum::Static(_) | types::ItemEnum::ForeignType diff --git a/src/librustdoc/passes/stripper.rs b/src/librustdoc/passes/stripper.rs index 1cf80a0d80c53..9987d3c302583 100644 --- a/src/librustdoc/passes/stripper.rs +++ b/src/librustdoc/passes/stripper.rs @@ -49,8 +49,7 @@ impl<'a, 'tcx> DocFolder for Stripper<'a, 'tcx> { return Some(ret); } // These items can all get re-exported - clean::OpaqueTyItem(..) - | clean::TypeAliasItem(..) + clean::TypeAliasItem(..) | clean::StaticItem(..) | clean::StructItem(..) | clean::EnumItem(..) diff --git a/src/librustdoc/visit.rs b/src/librustdoc/visit.rs index b335dc5bd16b8..0bf0a351c3a58 100644 --- a/src/librustdoc/visit.rs +++ b/src/librustdoc/visit.rs @@ -26,7 +26,6 @@ pub(crate) trait DocVisitor: Sized { | ImportItem(_) | FunctionItem(_) | TypeAliasItem(_) - | OpaqueTyItem(_) | StaticItem(_) | ConstantItem(_, _, _) | TraitAliasItem(_) diff --git a/src/rustdoc-json-types/lib.rs b/src/rustdoc-json-types/lib.rs index 89115d4d7d667..fa19525552773 100644 --- a/src/rustdoc-json-types/lib.rs +++ b/src/rustdoc-json-types/lib.rs @@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize}; use std::path::PathBuf; /// rustdoc format-version. -pub const FORMAT_VERSION: u32 = 31; +pub const FORMAT_VERSION: u32 = 32; /// A `Crate` is the root of the emitted JSON blob. It contains all type/documentation information /// about the language items in the local crate, as well as info about external items to allow @@ -214,7 +214,6 @@ pub enum ItemKind { Variant, Function, TypeAlias, - OpaqueTy, Constant, Trait, TraitAlias, @@ -253,7 +252,6 @@ pub enum ItemEnum { Impl(Impl), TypeAlias(TypeAlias), - OpaqueTy(OpaqueTy), Constant { #[serde(rename = "type")] type_: Type, @@ -725,12 +723,6 @@ pub struct TypeAlias { pub generics: Generics, } -#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] -pub struct OpaqueTy { - pub bounds: Vec, - pub generics: Generics, -} - #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct Static { #[serde(rename = "type")] diff --git a/src/tools/jsondoclint/src/item_kind.rs b/src/tools/jsondoclint/src/item_kind.rs index 525de03bbce33..7d6ec475badd4 100644 --- a/src/tools/jsondoclint/src/item_kind.rs +++ b/src/tools/jsondoclint/src/item_kind.rs @@ -13,7 +13,6 @@ pub(crate) enum Kind { Variant, Function, TypeAlias, - OpaqueTy, Constant, Trait, TraitAlias, @@ -55,7 +54,6 @@ impl Kind { // FIXME(adotinthevoid): I'm not sure if these are correct Keyword => false, - OpaqueTy => false, ProcAttribute => false, ProcDerive => false, @@ -99,7 +97,6 @@ impl Kind { Kind::Enum => false, Kind::Variant => false, Kind::TypeAlias => false, - Kind::OpaqueTy => false, Kind::Constant => false, Kind::Trait => false, Kind::TraitAlias => false, @@ -149,7 +146,6 @@ impl Kind { ItemEnum::TraitAlias(_) => TraitAlias, ItemEnum::Impl(_) => Impl, ItemEnum::TypeAlias(_) => TypeAlias, - ItemEnum::OpaqueTy(_) => OpaqueTy, ItemEnum::Constant { .. } => Constant, ItemEnum::Static(_) => Static, ItemEnum::Macro(_) => Macro, @@ -177,7 +173,6 @@ impl Kind { ItemKind::Keyword => Keyword, ItemKind::Macro => Macro, ItemKind::Module => Module, - ItemKind::OpaqueTy => OpaqueTy, ItemKind::Primitive => Primitive, ItemKind::ProcAttribute => ProcAttribute, ItemKind::ProcDerive => ProcDerive, diff --git a/src/tools/jsondoclint/src/validator.rs b/src/tools/jsondoclint/src/validator.rs index cd011dce7844e..82f44ab51dd90 100644 --- a/src/tools/jsondoclint/src/validator.rs +++ b/src/tools/jsondoclint/src/validator.rs @@ -3,9 +3,9 @@ use std::hash::Hash; use rustdoc_json_types::{ Constant, Crate, DynTrait, Enum, FnDecl, Function, FunctionPointer, GenericArg, GenericArgs, - GenericBound, GenericParamDef, Generics, Id, Impl, Import, ItemEnum, ItemSummary, Module, - OpaqueTy, Path, Primitive, ProcMacro, Static, Struct, StructKind, Term, Trait, TraitAlias, - Type, TypeAlias, TypeBinding, TypeBindingKind, Union, Variant, VariantKind, WherePredicate, + GenericBound, GenericParamDef, Generics, Id, Impl, Import, ItemEnum, ItemSummary, Module, Path, + Primitive, ProcMacro, Static, Struct, StructKind, Term, Trait, TraitAlias, Type, TypeAlias, + TypeBinding, TypeBindingKind, Union, Variant, VariantKind, WherePredicate, }; use serde_json::Value; @@ -100,7 +100,6 @@ impl<'a> Validator<'a> { ItemEnum::TraitAlias(x) => self.check_trait_alias(x), ItemEnum::Impl(x) => self.check_impl(x, id), ItemEnum::TypeAlias(x) => self.check_type_alias(x), - ItemEnum::OpaqueTy(x) => self.check_opaque_ty(x), ItemEnum::Constant { type_, const_ } => { self.check_type(type_); self.check_constant(const_); @@ -229,11 +228,6 @@ impl<'a> Validator<'a> { self.check_type(&x.type_); } - fn check_opaque_ty(&mut self, x: &'a OpaqueTy) { - x.bounds.iter().for_each(|b| self.check_generic_bound(b)); - self.check_generics(&x.generics); - } - fn check_constant(&mut self, _x: &'a Constant) { // nop } From c147805a6ab62ae52df7fb79067a05358d95fe72 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Mon, 24 Jun 2024 13:57:06 -0700 Subject: [PATCH 03/32] library: outline VaList into ffi::va_list and reexport --- library/core/src/ffi/mod.rs | 414 ++------------------------------ library/core/src/ffi/va_list.rs | 407 +++++++++++++++++++++++++++++++ src/tools/tidy/src/pal.rs | 2 + 3 files changed, 423 insertions(+), 400 deletions(-) create mode 100644 library/core/src/ffi/va_list.rs diff --git a/library/core/src/ffi/mod.rs b/library/core/src/ffi/mod.rs index 6d1f10f521176..88adc378477fd 100644 --- a/library/core/src/ffi/mod.rs +++ b/library/core/src/ffi/mod.rs @@ -10,8 +10,6 @@ #![allow(non_camel_case_types)] use crate::fmt; -use crate::marker::PhantomData; -use crate::ops::{Deref, DerefMut}; #[doc(no_inline)] #[stable(feature = "core_c_str", since = "1.64.0")] @@ -28,6 +26,20 @@ pub use self::c_str::CStr; #[unstable(feature = "c_str_module", issue = "112134")] pub mod c_str; +#[unstable( + feature = "c_variadic", + issue = "44930", + reason = "the `c_variadic` feature has not been properly tested on all supported platforms" +)] +pub use self::va_list::{VaList, VaListImpl}; + +#[unstable( + feature = "c_variadic", + issue = "44930", + reason = "the `c_variadic` feature has not been properly tested on all supported platforms" +)] +pub mod va_list; + macro_rules! type_alias { { $Docfile:tt, $Alias:ident = $Real:ty; @@ -205,404 +217,6 @@ impl fmt::Debug for c_void { } } -/// Basic implementation of a `va_list`. -// The name is WIP, using `VaListImpl` for now. -#[cfg(any( - all( - not(target_arch = "aarch64"), - not(target_arch = "powerpc"), - not(target_arch = "s390x"), - not(target_arch = "x86_64") - ), - all(target_arch = "aarch64", target_vendor = "apple"), - target_family = "wasm", - target_os = "uefi", - windows, -))] -#[cfg_attr(not(doc), repr(transparent))] // work around https://github.com/rust-lang/rust/issues/90435 -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -#[lang = "va_list"] -pub struct VaListImpl<'f> { - ptr: *mut c_void, - - // Invariant over `'f`, so each `VaListImpl<'f>` object is tied to - // the region of the function it's defined in - _marker: PhantomData<&'f mut &'f c_void>, -} - -#[cfg(any( - all( - not(target_arch = "aarch64"), - not(target_arch = "powerpc"), - not(target_arch = "s390x"), - not(target_arch = "x86_64") - ), - all(target_arch = "aarch64", target_vendor = "apple"), - target_family = "wasm", - target_os = "uefi", - windows, -))] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -impl<'f> fmt::Debug for VaListImpl<'f> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "va_list* {:p}", self.ptr) - } -} - -/// AArch64 ABI implementation of a `va_list`. See the -/// [AArch64 Procedure Call Standard] for more details. -/// -/// [AArch64 Procedure Call Standard]: -/// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf -#[cfg(all( - target_arch = "aarch64", - not(target_vendor = "apple"), - not(target_os = "uefi"), - not(windows), -))] -#[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 -#[derive(Debug)] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -#[lang = "va_list"] -pub struct VaListImpl<'f> { - stack: *mut c_void, - gr_top: *mut c_void, - vr_top: *mut c_void, - gr_offs: i32, - vr_offs: i32, - _marker: PhantomData<&'f mut &'f c_void>, -} - -/// PowerPC ABI implementation of a `va_list`. -#[cfg(all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)))] -#[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 -#[derive(Debug)] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -#[lang = "va_list"] -pub struct VaListImpl<'f> { - gpr: u8, - fpr: u8, - reserved: u16, - overflow_arg_area: *mut c_void, - reg_save_area: *mut c_void, - _marker: PhantomData<&'f mut &'f c_void>, -} - -/// s390x ABI implementation of a `va_list`. -#[cfg(target_arch = "s390x")] -#[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 -#[derive(Debug)] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -#[lang = "va_list"] -pub struct VaListImpl<'f> { - gpr: i64, - fpr: i64, - overflow_arg_area: *mut c_void, - reg_save_area: *mut c_void, - _marker: PhantomData<&'f mut &'f c_void>, -} - -/// x86_64 ABI implementation of a `va_list`. -#[cfg(all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)))] -#[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 -#[derive(Debug)] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -#[lang = "va_list"] -pub struct VaListImpl<'f> { - gp_offset: i32, - fp_offset: i32, - overflow_arg_area: *mut c_void, - reg_save_area: *mut c_void, - _marker: PhantomData<&'f mut &'f c_void>, -} - -/// A wrapper for a `va_list` -#[cfg_attr(not(doc), repr(transparent))] // work around https://github.com/rust-lang/rust/issues/90435 -#[derive(Debug)] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -pub struct VaList<'a, 'f: 'a> { - #[cfg(any( - all( - not(target_arch = "aarch64"), - not(target_arch = "powerpc"), - not(target_arch = "s390x"), - not(target_arch = "x86_64") - ), - all(target_arch = "aarch64", target_vendor = "apple"), - target_family = "wasm", - target_os = "uefi", - windows, - ))] - inner: VaListImpl<'f>, - - #[cfg(all( - any( - target_arch = "aarch64", - target_arch = "powerpc", - target_arch = "s390x", - target_arch = "x86_64" - ), - any(not(target_arch = "aarch64"), not(target_vendor = "apple")), - not(target_family = "wasm"), - not(target_os = "uefi"), - not(windows), - ))] - inner: &'a mut VaListImpl<'f>, - - _marker: PhantomData<&'a mut VaListImpl<'f>>, -} - -#[cfg(any( - all( - not(target_arch = "aarch64"), - not(target_arch = "powerpc"), - not(target_arch = "s390x"), - not(target_arch = "x86_64") - ), - all(target_arch = "aarch64", target_vendor = "apple"), - target_family = "wasm", - target_os = "uefi", - windows, -))] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -impl<'f> VaListImpl<'f> { - /// Convert a `VaListImpl` into a `VaList` that is binary-compatible with C's `va_list`. - #[inline] - pub fn as_va_list<'a>(&'a mut self) -> VaList<'a, 'f> { - VaList { inner: VaListImpl { ..*self }, _marker: PhantomData } - } -} - -#[cfg(all( - any( - target_arch = "aarch64", - target_arch = "powerpc", - target_arch = "s390x", - target_arch = "x86_64" - ), - any(not(target_arch = "aarch64"), not(target_vendor = "apple")), - not(target_family = "wasm"), - not(target_os = "uefi"), - not(windows), -))] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -impl<'f> VaListImpl<'f> { - /// Convert a `VaListImpl` into a `VaList` that is binary-compatible with C's `va_list`. - #[inline] - pub fn as_va_list<'a>(&'a mut self) -> VaList<'a, 'f> { - VaList { inner: self, _marker: PhantomData } - } -} - -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -impl<'a, 'f: 'a> Deref for VaList<'a, 'f> { - type Target = VaListImpl<'f>; - - #[inline] - fn deref(&self) -> &VaListImpl<'f> { - &self.inner - } -} - -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -impl<'a, 'f: 'a> DerefMut for VaList<'a, 'f> { - #[inline] - fn deref_mut(&mut self) -> &mut VaListImpl<'f> { - &mut self.inner - } -} - -// The VaArgSafe trait needs to be used in public interfaces, however, the trait -// itself must not be allowed to be used outside this module. Allowing users to -// implement the trait for a new type (thereby allowing the va_arg intrinsic to -// be used on a new type) is likely to cause undefined behavior. -// -// FIXME(dlrobertson): In order to use the VaArgSafe trait in a public interface -// but also ensure it cannot be used elsewhere, the trait needs to be public -// within a private module. Once RFC 2145 has been implemented look into -// improving this. -mod sealed_trait { - /// Trait which permits the allowed types to be used with [super::VaListImpl::arg]. - #[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" - )] - pub unsafe trait VaArgSafe {} -} - -macro_rules! impl_va_arg_safe { - ($($t:ty),+) => { - $( - #[unstable(feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930")] - unsafe impl sealed_trait::VaArgSafe for $t {} - )+ - } -} - -impl_va_arg_safe! {i8, i16, i32, i64, usize} -impl_va_arg_safe! {u8, u16, u32, u64, isize} -impl_va_arg_safe! {f64} - -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -unsafe impl sealed_trait::VaArgSafe for *mut T {} - -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -unsafe impl sealed_trait::VaArgSafe for *const T {} - -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -impl<'f> VaListImpl<'f> { - /// Advance to the next arg. - #[inline] - pub unsafe fn arg(&mut self) -> T { - // SAFETY: the caller must uphold the safety contract for `va_arg`. - unsafe { va_arg(self) } - } - - /// Copies the `va_list` at the current location. - pub unsafe fn with_copy(&self, f: F) -> R - where - F: for<'copy> FnOnce(VaList<'copy, 'f>) -> R, - { - let mut ap = self.clone(); - let ret = f(ap.as_va_list()); - // SAFETY: the caller must uphold the safety contract for `va_end`. - unsafe { - va_end(&mut ap); - } - ret - } -} - -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -impl<'f> Clone for VaListImpl<'f> { - #[inline] - fn clone(&self) -> Self { - let mut dest = crate::mem::MaybeUninit::uninit(); - // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal - unsafe { - va_copy(dest.as_mut_ptr(), self); - dest.assume_init() - } - } -} - -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] -impl<'f> Drop for VaListImpl<'f> { - fn drop(&mut self) { - // FIXME: this should call `va_end`, but there's no clean way to - // guarantee that `drop` always gets inlined into its caller, - // so the `va_end` would get directly called from the same function as - // the corresponding `va_copy`. `man va_end` states that C requires this, - // and LLVM basically follows the C semantics, so we need to make sure - // that `va_end` is always called from the same function as `va_copy`. - // For more details, see https://github.com/rust-lang/rust/pull/59625 - // and https://llvm.org/docs/LangRef.html#llvm-va-end-intrinsic. - // - // This works for now, since `va_end` is a no-op on all current LLVM targets. - } -} - -extern "rust-intrinsic" { - /// Destroy the arglist `ap` after initialization with `va_start` or - /// `va_copy`. - #[rustc_nounwind] - fn va_end(ap: &mut VaListImpl<'_>); - - /// Copies the current location of arglist `src` to the arglist `dst`. - #[rustc_nounwind] - fn va_copy<'f>(dest: *mut VaListImpl<'f>, src: &VaListImpl<'f>); - - /// Loads an argument of type `T` from the `va_list` `ap` and increment the - /// argument `ap` points to. - #[rustc_nounwind] - fn va_arg(ap: &mut VaListImpl<'_>) -> T; -} - // Link the MSVC default lib #[cfg(all(windows, target_env = "msvc"))] #[link( diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs new file mode 100644 index 0000000000000..88c5909b1130e --- /dev/null +++ b/library/core/src/ffi/va_list.rs @@ -0,0 +1,407 @@ +//! C's "variable arguments" +//! +//! Better known as "varargs". + +use crate::ffi::c_void; + +#[allow(unused_imports)] +use crate::fmt; +use crate::marker::PhantomData; +use crate::ops::{Deref, DerefMut}; + +/// Basic implementation of a `va_list`. +// The name is WIP, using `VaListImpl` for now. +#[cfg(any( + all( + not(target_arch = "aarch64"), + not(target_arch = "powerpc"), + not(target_arch = "s390x"), + not(target_arch = "x86_64") + ), + all(target_arch = "aarch64", target_vendor = "apple"), + target_family = "wasm", + target_os = "uefi", + windows, +))] +#[cfg_attr(not(doc), repr(transparent))] // work around https://github.com/rust-lang/rust/issues/90435 +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +#[lang = "va_list"] +pub struct VaListImpl<'f> { + ptr: *mut c_void, + + // Invariant over `'f`, so each `VaListImpl<'f>` object is tied to + // the region of the function it's defined in + _marker: PhantomData<&'f mut &'f c_void>, +} + +#[cfg(any( + all( + not(target_arch = "aarch64"), + not(target_arch = "powerpc"), + not(target_arch = "s390x"), + not(target_arch = "x86_64") + ), + all(target_arch = "aarch64", target_vendor = "apple"), + target_family = "wasm", + target_os = "uefi", + windows, +))] +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +impl<'f> fmt::Debug for VaListImpl<'f> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "va_list* {:p}", self.ptr) + } +} + +/// AArch64 ABI implementation of a `va_list`. See the +/// [AArch64 Procedure Call Standard] for more details. +/// +/// [AArch64 Procedure Call Standard]: +/// http://infocenter.arm.com/help/topic/com.arm.doc.ihi0055b/IHI0055B_aapcs64.pdf +#[cfg(all( + target_arch = "aarch64", + not(target_vendor = "apple"), + not(target_os = "uefi"), + not(windows), +))] +#[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 +#[derive(Debug)] +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +#[lang = "va_list"] +pub struct VaListImpl<'f> { + stack: *mut c_void, + gr_top: *mut c_void, + vr_top: *mut c_void, + gr_offs: i32, + vr_offs: i32, + _marker: PhantomData<&'f mut &'f c_void>, +} + +/// PowerPC ABI implementation of a `va_list`. +#[cfg(all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)))] +#[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 +#[derive(Debug)] +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +#[lang = "va_list"] +pub struct VaListImpl<'f> { + gpr: u8, + fpr: u8, + reserved: u16, + overflow_arg_area: *mut c_void, + reg_save_area: *mut c_void, + _marker: PhantomData<&'f mut &'f c_void>, +} + +/// s390x ABI implementation of a `va_list`. +#[cfg(target_arch = "s390x")] +#[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 +#[derive(Debug)] +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +#[lang = "va_list"] +pub struct VaListImpl<'f> { + gpr: i64, + fpr: i64, + overflow_arg_area: *mut c_void, + reg_save_area: *mut c_void, + _marker: PhantomData<&'f mut &'f c_void>, +} + +/// x86_64 ABI implementation of a `va_list`. +#[cfg(all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)))] +#[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 +#[derive(Debug)] +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +#[lang = "va_list"] +pub struct VaListImpl<'f> { + gp_offset: i32, + fp_offset: i32, + overflow_arg_area: *mut c_void, + reg_save_area: *mut c_void, + _marker: PhantomData<&'f mut &'f c_void>, +} + +/// A wrapper for a `va_list` +#[cfg_attr(not(doc), repr(transparent))] // work around https://github.com/rust-lang/rust/issues/90435 +#[derive(Debug)] +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +pub struct VaList<'a, 'f: 'a> { + #[cfg(any( + all( + not(target_arch = "aarch64"), + not(target_arch = "powerpc"), + not(target_arch = "s390x"), + not(target_arch = "x86_64") + ), + all(target_arch = "aarch64", target_vendor = "apple"), + target_family = "wasm", + target_os = "uefi", + windows, + ))] + inner: VaListImpl<'f>, + + #[cfg(all( + any( + target_arch = "aarch64", + target_arch = "powerpc", + target_arch = "s390x", + target_arch = "x86_64" + ), + any(not(target_arch = "aarch64"), not(target_vendor = "apple")), + not(target_family = "wasm"), + not(target_os = "uefi"), + not(windows), + ))] + inner: &'a mut VaListImpl<'f>, + + _marker: PhantomData<&'a mut VaListImpl<'f>>, +} + +#[cfg(any( + all( + not(target_arch = "aarch64"), + not(target_arch = "powerpc"), + not(target_arch = "s390x"), + not(target_arch = "x86_64") + ), + all(target_arch = "aarch64", target_vendor = "apple"), + target_family = "wasm", + target_os = "uefi", + windows, +))] +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +impl<'f> VaListImpl<'f> { + /// Convert a `VaListImpl` into a `VaList` that is binary-compatible with C's `va_list`. + #[inline] + pub fn as_va_list<'a>(&'a mut self) -> VaList<'a, 'f> { + VaList { inner: VaListImpl { ..*self }, _marker: PhantomData } + } +} + +#[cfg(all( + any( + target_arch = "aarch64", + target_arch = "powerpc", + target_arch = "s390x", + target_arch = "x86_64" + ), + any(not(target_arch = "aarch64"), not(target_vendor = "apple")), + not(target_family = "wasm"), + not(target_os = "uefi"), + not(windows), +))] +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +impl<'f> VaListImpl<'f> { + /// Convert a `VaListImpl` into a `VaList` that is binary-compatible with C's `va_list`. + #[inline] + pub fn as_va_list<'a>(&'a mut self) -> VaList<'a, 'f> { + VaList { inner: self, _marker: PhantomData } + } +} + +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +impl<'a, 'f: 'a> Deref for VaList<'a, 'f> { + type Target = VaListImpl<'f>; + + #[inline] + fn deref(&self) -> &VaListImpl<'f> { + &self.inner + } +} + +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +impl<'a, 'f: 'a> DerefMut for VaList<'a, 'f> { + #[inline] + fn deref_mut(&mut self) -> &mut VaListImpl<'f> { + &mut self.inner + } +} + +// The VaArgSafe trait needs to be used in public interfaces, however, the trait +// itself must not be allowed to be used outside this module. Allowing users to +// implement the trait for a new type (thereby allowing the va_arg intrinsic to +// be used on a new type) is likely to cause undefined behavior. +// +// FIXME(dlrobertson): In order to use the VaArgSafe trait in a public interface +// but also ensure it cannot be used elsewhere, the trait needs to be public +// within a private module. Once RFC 2145 has been implemented look into +// improving this. +mod sealed_trait { + /// Trait which permits the allowed types to be used with [super::VaListImpl::arg]. + #[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" + )] + pub unsafe trait VaArgSafe {} +} + +macro_rules! impl_va_arg_safe { + ($($t:ty),+) => { + $( + #[unstable(feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930")] + unsafe impl sealed_trait::VaArgSafe for $t {} + )+ + } +} + +impl_va_arg_safe! {i8, i16, i32, i64, usize} +impl_va_arg_safe! {u8, u16, u32, u64, isize} +impl_va_arg_safe! {f64} + +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +unsafe impl sealed_trait::VaArgSafe for *mut T {} +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +unsafe impl sealed_trait::VaArgSafe for *const T {} + +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +impl<'f> VaListImpl<'f> { + /// Advance to the next arg. + #[inline] + pub unsafe fn arg(&mut self) -> T { + // SAFETY: the caller must uphold the safety contract for `va_arg`. + unsafe { va_arg(self) } + } + + /// Copies the `va_list` at the current location. + pub unsafe fn with_copy(&self, f: F) -> R + where + F: for<'copy> FnOnce(VaList<'copy, 'f>) -> R, + { + let mut ap = self.clone(); + let ret = f(ap.as_va_list()); + // SAFETY: the caller must uphold the safety contract for `va_end`. + unsafe { + va_end(&mut ap); + } + ret + } +} + +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +impl<'f> Clone for VaListImpl<'f> { + #[inline] + fn clone(&self) -> Self { + let mut dest = crate::mem::MaybeUninit::uninit(); + // SAFETY: we write to the `MaybeUninit`, thus it is initialized and `assume_init` is legal + unsafe { + va_copy(dest.as_mut_ptr(), self); + dest.assume_init() + } + } +} + +#[unstable( + feature = "c_variadic", + reason = "the `c_variadic` feature has not been properly tested on \ + all supported platforms", + issue = "44930" +)] +impl<'f> Drop for VaListImpl<'f> { + fn drop(&mut self) { + // FIXME: this should call `va_end`, but there's no clean way to + // guarantee that `drop` always gets inlined into its caller, + // so the `va_end` would get directly called from the same function as + // the corresponding `va_copy`. `man va_end` states that C requires this, + // and LLVM basically follows the C semantics, so we need to make sure + // that `va_end` is always called from the same function as `va_copy`. + // For more details, see https://github.com/rust-lang/rust/pull/59625 + // and https://llvm.org/docs/LangRef.html#llvm-va-end-intrinsic. + // + // This works for now, since `va_end` is a no-op on all current LLVM targets. + } +} + +extern "rust-intrinsic" { + /// Destroy the arglist `ap` after initialization with `va_start` or + /// `va_copy`. + #[rustc_nounwind] + fn va_end(ap: &mut VaListImpl<'_>); + + /// Copies the current location of arglist `src` to the arglist `dst`. + #[rustc_nounwind] + fn va_copy<'f>(dest: *mut VaListImpl<'f>, src: &VaListImpl<'f>); + + /// Loads an argument of type `T` from the `va_list` `ap` and increment the + /// argument `ap` points to. + #[rustc_nounwind] + fn va_arg(ap: &mut VaListImpl<'_>) -> T; +} diff --git a/src/tools/tidy/src/pal.rs b/src/tools/tidy/src/pal.rs index 7e5656926abe2..2f8abc77566cd 100644 --- a/src/tools/tidy/src/pal.rs +++ b/src/tools/tidy/src/pal.rs @@ -45,6 +45,8 @@ const EXCEPTION_PATHS: &[&str] = &[ // pointer regardless of the target architecture. As a result, // we must use `#[cfg(windows)]` to conditionally compile the // correct `VaList` structure for windows. + "library/core/src/ffi/va_list.rs", + // We placed a linkage against Windows libraries here "library/core/src/ffi/mod.rs", "library/std/src/sys", // Platform-specific code for std lives here. "library/std/src/os", // Platform-specific public interfaces From c1a29b30d157689caafa34238e87313cda0fccc5 Mon Sep 17 00:00:00 2001 From: Jubilee Young Date: Mon, 24 Jun 2024 14:09:30 -0700 Subject: [PATCH 04/32] core: erase redundant stability attrs in va_list Now that VaList, et al. have a module, they only need one `#[unstable]`. --- library/core/src/ffi/va_list.rs | 106 -------------------------------- 1 file changed, 106 deletions(-) diff --git a/library/core/src/ffi/va_list.rs b/library/core/src/ffi/va_list.rs index 88c5909b1130e..6a2e8b67d0c2a 100644 --- a/library/core/src/ffi/va_list.rs +++ b/library/core/src/ffi/va_list.rs @@ -24,12 +24,6 @@ use crate::ops::{Deref, DerefMut}; windows, ))] #[cfg_attr(not(doc), repr(transparent))] // work around https://github.com/rust-lang/rust/issues/90435 -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] #[lang = "va_list"] pub struct VaListImpl<'f> { ptr: *mut c_void, @@ -51,12 +45,6 @@ pub struct VaListImpl<'f> { target_os = "uefi", windows, ))] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] impl<'f> fmt::Debug for VaListImpl<'f> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "va_list* {:p}", self.ptr) @@ -76,12 +64,6 @@ impl<'f> fmt::Debug for VaListImpl<'f> { ))] #[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 #[derive(Debug)] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] #[lang = "va_list"] pub struct VaListImpl<'f> { stack: *mut c_void, @@ -96,12 +78,6 @@ pub struct VaListImpl<'f> { #[cfg(all(target_arch = "powerpc", not(target_os = "uefi"), not(windows)))] #[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 #[derive(Debug)] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] #[lang = "va_list"] pub struct VaListImpl<'f> { gpr: u8, @@ -116,12 +92,6 @@ pub struct VaListImpl<'f> { #[cfg(target_arch = "s390x")] #[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 #[derive(Debug)] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] #[lang = "va_list"] pub struct VaListImpl<'f> { gpr: i64, @@ -135,12 +105,6 @@ pub struct VaListImpl<'f> { #[cfg(all(target_arch = "x86_64", not(target_os = "uefi"), not(windows)))] #[cfg_attr(not(doc), repr(C))] // work around https://github.com/rust-lang/rust/issues/66401 #[derive(Debug)] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] #[lang = "va_list"] pub struct VaListImpl<'f> { gp_offset: i32, @@ -153,12 +117,6 @@ pub struct VaListImpl<'f> { /// A wrapper for a `va_list` #[cfg_attr(not(doc), repr(transparent))] // work around https://github.com/rust-lang/rust/issues/90435 #[derive(Debug)] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] pub struct VaList<'a, 'f: 'a> { #[cfg(any( all( @@ -203,12 +161,6 @@ pub struct VaList<'a, 'f: 'a> { target_os = "uefi", windows, ))] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] impl<'f> VaListImpl<'f> { /// Convert a `VaListImpl` into a `VaList` that is binary-compatible with C's `va_list`. #[inline] @@ -229,12 +181,6 @@ impl<'f> VaListImpl<'f> { not(target_os = "uefi"), not(windows), ))] -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] impl<'f> VaListImpl<'f> { /// Convert a `VaListImpl` into a `VaList` that is binary-compatible with C's `va_list`. #[inline] @@ -243,12 +189,6 @@ impl<'f> VaListImpl<'f> { } } -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] impl<'a, 'f: 'a> Deref for VaList<'a, 'f> { type Target = VaListImpl<'f>; @@ -258,12 +198,6 @@ impl<'a, 'f: 'a> Deref for VaList<'a, 'f> { } } -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] impl<'a, 'f: 'a> DerefMut for VaList<'a, 'f> { #[inline] fn deref_mut(&mut self) -> &mut VaListImpl<'f> { @@ -282,22 +216,12 @@ impl<'a, 'f: 'a> DerefMut for VaList<'a, 'f> { // improving this. mod sealed_trait { /// Trait which permits the allowed types to be used with [super::VaListImpl::arg]. - #[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" - )] pub unsafe trait VaArgSafe {} } macro_rules! impl_va_arg_safe { ($($t:ty),+) => { $( - #[unstable(feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930")] unsafe impl sealed_trait::VaArgSafe for $t {} )+ } @@ -307,27 +231,9 @@ impl_va_arg_safe! {i8, i16, i32, i64, usize} impl_va_arg_safe! {u8, u16, u32, u64, isize} impl_va_arg_safe! {f64} -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] unsafe impl sealed_trait::VaArgSafe for *mut T {} -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] unsafe impl sealed_trait::VaArgSafe for *const T {} -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] impl<'f> VaListImpl<'f> { /// Advance to the next arg. #[inline] @@ -351,12 +257,6 @@ impl<'f> VaListImpl<'f> { } } -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] impl<'f> Clone for VaListImpl<'f> { #[inline] fn clone(&self) -> Self { @@ -369,12 +269,6 @@ impl<'f> Clone for VaListImpl<'f> { } } -#[unstable( - feature = "c_variadic", - reason = "the `c_variadic` feature has not been properly tested on \ - all supported platforms", - issue = "44930" -)] impl<'f> Drop for VaListImpl<'f> { fn drop(&mut self) { // FIXME: this should call `va_end`, but there's no clean way to From 7566f412099a6dd68df2d1bca0c73484113b2ab9 Mon Sep 17 00:00:00 2001 From: Chris Denton Date: Fri, 5 Jul 2024 14:48:39 +0000 Subject: [PATCH 05/32] Run alloc sync tests --- library/alloc/src/sync.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/library/alloc/src/sync.rs b/library/alloc/src/sync.rs index 90672164cb932..1983ea8281aa0 100644 --- a/library/alloc/src/sync.rs +++ b/library/alloc/src/sync.rs @@ -197,11 +197,7 @@ macro_rules! acquire { /// /// Sharing some immutable data between threads: /// -// Note that we **do not** run these tests here. The windows builders get super -// unhappy if a thread outlives the main thread and then exits at the same time -// (something deadlocks) so we just avoid this entirely by not running these -// tests. -/// ```no_run +/// ``` /// use std::sync::Arc; /// use std::thread; /// @@ -220,7 +216,7 @@ macro_rules! acquire { /// /// [`AtomicUsize`]: core::sync::atomic::AtomicUsize "sync::atomic::AtomicUsize" /// -/// ```no_run +/// ``` /// use std::sync::Arc; /// use std::sync::atomic::{AtomicUsize, Ordering}; /// use std::thread; From 8c2ea715e9b0df15881392b248576a2ee8c13075 Mon Sep 17 00:00:00 2001 From: Oli Scherer Date: Sat, 6 Jul 2024 19:21:22 +0000 Subject: [PATCH 06/32] Use field ident spans directly instead of the full field span in diagnostics on local fields --- .../rustc_resolve/src/build_reduced_graph.rs | 10 ++++- compiler/rustc_resolve/src/diagnostics.rs | 6 +-- .../rustc_resolve/src/late/diagnostics.rs | 44 ++++++++----------- compiler/rustc_resolve/src/lib.rs | 18 +++++--- ...d-in-self-not-available-in-assoc-fn.stderr | 4 +- tests/ui/resolve/issue-2356.stderr | 4 +- tests/ui/resolve/issue-60057.stderr | 2 +- ...e-with-name-similar-to-struct-field.stderr | 4 +- .../unresolved_static_type_field.stderr | 2 +- 9 files changed, 49 insertions(+), 45 deletions(-) diff --git a/compiler/rustc_resolve/src/build_reduced_graph.rs b/compiler/rustc_resolve/src/build_reduced_graph.rs index 4e0f2792d9749..92cf73870ff13 100644 --- a/compiler/rustc_resolve/src/build_reduced_graph.rs +++ b/compiler/rustc_resolve/src/build_reduced_graph.rs @@ -321,8 +321,14 @@ impl<'a, 'b, 'tcx> BuildReducedGraphVisitor<'a, 'b, 'tcx> { // The fields are not expanded yet. return; } - let def_ids = fields.iter().map(|field| self.r.local_def_id(field.id).to_def_id()); - self.r.field_def_ids.insert(def_id, self.r.tcx.arena.alloc_from_iter(def_ids)); + let fields = fields + .iter() + .enumerate() + .map(|(i, field)| { + field.ident.unwrap_or_else(|| Ident::from_str_and_span(&format!("{i}"), field.span)) + }) + .collect(); + self.r.field_names.insert(def_id, fields); } fn insert_field_visibilities_local(&mut self, def_id: DefId, fields: &[ast::FieldDef]) { diff --git a/compiler/rustc_resolve/src/diagnostics.rs b/compiler/rustc_resolve/src/diagnostics.rs index 50a4e03d233a7..ffd495aa9857e 100644 --- a/compiler/rustc_resolve/src/diagnostics.rs +++ b/compiler/rustc_resolve/src/diagnostics.rs @@ -1726,11 +1726,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { )) = binding.kind { let def_id = self.tcx.parent(ctor_def_id); - return self - .field_def_ids(def_id)? - .iter() - .map(|&field_id| self.def_span(field_id)) - .reduce(Span::to); // None for `struct Foo()` + return self.field_idents(def_id)?.iter().map(|&f| f.span).reduce(Span::to); // None for `struct Foo()` } None } diff --git a/compiler/rustc_resolve/src/late/diagnostics.rs b/compiler/rustc_resolve/src/late/diagnostics.rs index 764cc350182a6..941fb6436df92 100644 --- a/compiler/rustc_resolve/src/late/diagnostics.rs +++ b/compiler/rustc_resolve/src/late/diagnostics.rs @@ -1532,17 +1532,17 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { if !this.has_private_fields(def_id) { // If the fields of the type are private, we shouldn't be suggesting using // the struct literal syntax at all, as that will cause a subsequent error. - let field_ids = this.r.field_def_ids(def_id); - let (fields, applicability) = match field_ids { - Some(field_ids) => { - let fields = field_ids.iter().map(|&id| this.r.tcx.item_name(id)); - + let fields = this.r.field_idents(def_id); + let has_fields = fields.as_ref().is_some_and(|f| !f.is_empty()); + let (fields, applicability) = match fields { + Some(fields) => { let fields = if let Some(old_fields) = old_fields { fields + .iter() .enumerate() .map(|(idx, new)| (new, old_fields.get(idx))) .map(|(new, old)| { - let new = new.to_ident_string(); + let new = new.name.to_ident_string(); if let Some(Some(old)) = old && new != *old { @@ -1553,17 +1553,17 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { }) .collect::>() } else { - fields.map(|f| format!("{f}{tail}")).collect::>() + fields + .iter() + .map(|f| format!("{f}{tail}")) + .collect::>() }; (fields.join(", "), applicability) } None => ("/* fields */".to_string(), Applicability::HasPlaceholders), }; - let pad = match field_ids { - Some([]) => "", - _ => " ", - }; + let pad = if has_fields { " " } else { "" }; err.span_suggestion( span, format!("use struct {descr} syntax instead"), @@ -1723,12 +1723,9 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { &args[..], ); // Use spans of the tuple struct definition. - self.r.field_def_ids(def_id).map(|field_ids| { - field_ids - .iter() - .map(|&field_id| self.r.def_span(field_id)) - .collect::>() - }) + self.r + .field_idents(def_id) + .map(|fields| fields.iter().map(|f| f.span).collect::>()) } _ => None, }; @@ -1791,7 +1788,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { (Res::Def(DefKind::Ctor(_, CtorKind::Fn), ctor_def_id), _) if ns == ValueNS => { let def_id = self.r.tcx.parent(ctor_def_id); err.span_label(self.r.def_span(def_id), format!("`{path_str}` defined here")); - let fields = self.r.field_def_ids(def_id).map_or_else( + let fields = self.r.field_idents(def_id).map_or_else( || "/* fields */".to_string(), |field_ids| vec!["_"; field_ids.len()].join(", "), ); @@ -2017,12 +2014,9 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { if let Some(Res::Def(DefKind::Struct | DefKind::Union, did)) = resolution.full_res() { - if let Some(field_ids) = self.r.field_def_ids(did) { - if let Some(field_id) = field_ids - .iter() - .find(|&&field_id| ident.name == self.r.tcx.item_name(field_id)) - { - return Some(AssocSuggestion::Field(self.r.def_span(*field_id))); + if let Some(fields) = self.r.field_idents(did) { + if let Some(field) = fields.iter().find(|id| ident.name == id.name) { + return Some(AssocSuggestion::Field(field.span)); } } } @@ -2418,7 +2412,7 @@ impl<'a: 'ast, 'ast, 'tcx> LateResolutionVisitor<'a, '_, 'ast, 'tcx> { match kind { CtorKind::Const => false, CtorKind::Fn => { - !self.r.field_def_ids(def_id).is_some_and(|field_ids| field_ids.is_empty()) + !self.r.field_idents(def_id).is_some_and(|field_ids| field_ids.is_empty()) } } }; diff --git a/compiler/rustc_resolve/src/lib.rs b/compiler/rustc_resolve/src/lib.rs index 38963ef4ef092..7bf32a023f1e1 100644 --- a/compiler/rustc_resolve/src/lib.rs +++ b/compiler/rustc_resolve/src/lib.rs @@ -991,7 +991,7 @@ pub struct Resolver<'a, 'tcx> { extern_prelude: FxHashMap>, /// N.B., this is used only for better diagnostics, not name resolution itself. - field_def_ids: LocalDefIdMap<&'tcx [DefId]>, + field_names: LocalDefIdMap>, /// Span of the privacy modifier in fields of an item `DefId` accessible with dot syntax. /// Used for hints during error reporting. @@ -1406,7 +1406,7 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { prelude: None, extern_prelude, - field_def_ids: Default::default(), + field_names: Default::default(), field_visibility_spans: FxHashMap::default(), determined_imports: Vec::new(), @@ -2127,10 +2127,18 @@ impl<'a, 'tcx> Resolver<'a, 'tcx> { } } - fn field_def_ids(&self, def_id: DefId) -> Option<&'tcx [DefId]> { + fn field_idents(&self, def_id: DefId) -> Option> { match def_id.as_local() { - Some(def_id) => self.field_def_ids.get(&def_id).copied(), - None => Some(self.tcx.associated_item_def_ids(def_id)), + Some(def_id) => self.field_names.get(&def_id).cloned(), + None => Some( + self.tcx + .associated_item_def_ids(def_id) + .iter() + .map(|&def_id| { + Ident::new(self.tcx.item_name(def_id), self.tcx.def_span(def_id)) + }) + .collect(), + ), } } diff --git a/tests/ui/resolve/field-and-method-in-self-not-available-in-assoc-fn.stderr b/tests/ui/resolve/field-and-method-in-self-not-available-in-assoc-fn.stderr index 3c44c1c249ce2..2f624f24804c8 100644 --- a/tests/ui/resolve/field-and-method-in-self-not-available-in-assoc-fn.stderr +++ b/tests/ui/resolve/field-and-method-in-self-not-available-in-assoc-fn.stderr @@ -2,7 +2,7 @@ error[E0425]: cannot find value `field` in this scope --> $DIR/field-and-method-in-self-not-available-in-assoc-fn.rs:11:9 | LL | field: u32, - | ---------- a field by that name exists in `Self` + | ----- a field by that name exists in `Self` ... LL | fn field(&self) -> u32 { | ----- a method by that name is available on `Self` here @@ -14,7 +14,7 @@ error[E0425]: cannot find value `field` in this scope --> $DIR/field-and-method-in-self-not-available-in-assoc-fn.rs:12:15 | LL | field: u32, - | ---------- a field by that name exists in `Self` + | ----- a field by that name exists in `Self` ... LL | fn field(&self) -> u32 { | ----- a method by that name is available on `Self` here diff --git a/tests/ui/resolve/issue-2356.stderr b/tests/ui/resolve/issue-2356.stderr index 5f75ae988702f..74a2c9268a2f9 100644 --- a/tests/ui/resolve/issue-2356.stderr +++ b/tests/ui/resolve/issue-2356.stderr @@ -2,7 +2,7 @@ error[E0425]: cannot find value `whiskers` in this scope --> $DIR/issue-2356.rs:39:5 | LL | whiskers: isize, - | --------------- a field by that name exists in `Self` + | -------- a field by that name exists in `Self` ... LL | whiskers -= other; | ^^^^^^^^ @@ -35,7 +35,7 @@ error[E0425]: cannot find value `whiskers` in this scope --> $DIR/issue-2356.rs:84:5 | LL | whiskers: isize, - | --------------- a field by that name exists in `Self` + | -------- a field by that name exists in `Self` ... LL | whiskers = 4; | ^^^^^^^^ diff --git a/tests/ui/resolve/issue-60057.stderr b/tests/ui/resolve/issue-60057.stderr index a2ab86443536c..8737cf770015f 100644 --- a/tests/ui/resolve/issue-60057.stderr +++ b/tests/ui/resolve/issue-60057.stderr @@ -2,7 +2,7 @@ error[E0425]: cannot find value `banana` in this scope --> $DIR/issue-60057.rs:8:21 | LL | banana: u8, - | ---------- a field by that name exists in `Self` + | ------ a field by that name exists in `Self` ... LL | banana: banana | ^^^^^^ diff --git a/tests/ui/resolve/typo-suggestion-for-variable-with-name-similar-to-struct-field.stderr b/tests/ui/resolve/typo-suggestion-for-variable-with-name-similar-to-struct-field.stderr index 0306c8af87d85..5662021a2d523 100644 --- a/tests/ui/resolve/typo-suggestion-for-variable-with-name-similar-to-struct-field.stderr +++ b/tests/ui/resolve/typo-suggestion-for-variable-with-name-similar-to-struct-field.stderr @@ -2,7 +2,7 @@ error[E0425]: cannot find value `config` in this scope --> $DIR/typo-suggestion-for-variable-with-name-similar-to-struct-field.rs:7:16 | LL | config: String, - | -------------- a field by that name exists in `Self` + | ------ a field by that name exists in `Self` ... LL | Self { config } | ^^^^^^ help: a local variable with a similar name exists: `cofig` @@ -11,7 +11,7 @@ error[E0425]: cannot find value `config` in this scope --> $DIR/typo-suggestion-for-variable-with-name-similar-to-struct-field.rs:11:20 | LL | config: String, - | -------------- a field by that name exists in `Self` + | ------ a field by that name exists in `Self` ... LL | println!("{config}"); | ^^^^^^ help: a local variable with a similar name exists: `cofig` diff --git a/tests/ui/resolve/unresolved_static_type_field.stderr b/tests/ui/resolve/unresolved_static_type_field.stderr index e3de0a3fb74b3..f039eef2e06b9 100644 --- a/tests/ui/resolve/unresolved_static_type_field.stderr +++ b/tests/ui/resolve/unresolved_static_type_field.stderr @@ -2,7 +2,7 @@ error[E0425]: cannot find value `cx` in this scope --> $DIR/unresolved_static_type_field.rs:9:11 | LL | cx: bool, - | -------- a field by that name exists in `Self` + | -- a field by that name exists in `Self` ... LL | f(cx); | ^^ From c990e00f15f71cbff847f6988be4f512aca318bc Mon Sep 17 00:00:00 2001 From: Caio Date: Sat, 6 Jul 2024 18:00:04 -0300 Subject: [PATCH 07/32] Add support for literals --- compiler/rustc_expand/src/mbe/metavar_expr.rs | 74 ++++++++---- compiler/rustc_expand/src/mbe/transcribe.rs | 32 +++++- .../allowed-operations.rs | 12 ++ .../raw-identifiers.rs | 6 +- .../raw-identifiers.stderr | 6 +- .../syntax-errors.rs | 78 ++++++++++++- .../syntax-errors.stderr | 107 ++++++++++++++---- .../unicode-expansion.rs | 14 +++ .../syntax-errors.stderr | 2 +- 9 files changed, 272 insertions(+), 59 deletions(-) create mode 100644 tests/ui/macros/macro-metavar-expr-concat/unicode-expansion.rs diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs index 25958e03028f4..dbbd948fd7073 100644 --- a/compiler/rustc_expand/src/mbe/metavar_expr.rs +++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs @@ -1,4 +1,4 @@ -use rustc_ast::token::{self, Delimiter, IdentIsRaw}; +use rustc_ast::token::{self, Delimiter, IdentIsRaw, Lit, Token, TokenKind}; use rustc_ast::tokenstream::{RefTokenTreeCursor, TokenStream, TokenTree}; use rustc_ast::{LitIntType, LitKind}; use rustc_ast_pretty::pprust; @@ -6,9 +6,10 @@ use rustc_errors::{Applicability, PResult}; use rustc_macros::{Decodable, Encodable}; use rustc_session::parse::ParseSess; use rustc_span::symbol::Ident; -use rustc_span::Span; +use rustc_span::{Span, Symbol}; pub(crate) const RAW_IDENT_ERR: &str = "`${concat(..)}` currently does not support raw identifiers"; +pub(crate) const UNSUPPORTED_CONCAT_ELEM_ERR: &str = "expected identifier or string literal"; /// A meta-variable expression, for expansions based on properties of meta-variables. #[derive(Debug, PartialEq, Encodable, Decodable)] @@ -51,11 +52,26 @@ impl MetaVarExpr { let mut result = Vec::new(); loop { let is_var = try_eat_dollar(&mut iter); - let element_ident = parse_ident(&mut iter, psess, outer_span)?; + let token = parse_token(&mut iter, psess, outer_span)?; let element = if is_var { - MetaVarExprConcatElem::Var(element_ident) + MetaVarExprConcatElem::Var(parse_ident_from_token(psess, token)?) + } else if let TokenKind::Literal(Lit { + kind: token::LitKind::Str, + symbol, + suffix: None, + }) = token.kind + { + MetaVarExprConcatElem::Literal(symbol) } else { - MetaVarExprConcatElem::Ident(element_ident) + match parse_ident_from_token(psess, token) { + Err(err) => { + err.cancel(); + return Err(psess + .dcx() + .struct_span_err(token.span, UNSUPPORTED_CONCAT_ELEM_ERR)); + } + Ok(elem) => MetaVarExprConcatElem::Ident(elem), + } }; result.push(element); if iter.look_ahead(0).is_none() { @@ -105,11 +121,13 @@ impl MetaVarExpr { #[derive(Debug, Decodable, Encodable, PartialEq)] pub(crate) enum MetaVarExprConcatElem { - /// There is NO preceding dollar sign, which means that this identifier should be interpreted - /// as a literal. + /// Identifier WITHOUT a preceding dollar sign, which means that this identifier should be + /// interpreted as a literal. Ident(Ident), - /// There is a preceding dollar sign, which means that this identifier should be expanded - /// and interpreted as a variable. + /// For example, a number or a string. + Literal(Symbol), + /// Identifier WITH a preceding dollar sign, which means that this identifier should be + /// expanded and interpreted as a variable. Var(Ident), } @@ -158,7 +176,7 @@ fn parse_depth<'psess>( span: Span, ) -> PResult<'psess, usize> { let Some(tt) = iter.next() else { return Ok(0) }; - let TokenTree::Token(token::Token { kind: token::TokenKind::Literal(lit), .. }, _) = tt else { + let TokenTree::Token(Token { kind: TokenKind::Literal(lit), .. }, _) = tt else { return Err(psess .dcx() .struct_span_err(span, "meta-variable expression depth must be a literal")); @@ -180,12 +198,14 @@ fn parse_ident<'psess>( psess: &'psess ParseSess, fallback_span: Span, ) -> PResult<'psess, Ident> { - let Some(tt) = iter.next() else { - return Err(psess.dcx().struct_span_err(fallback_span, "expected identifier")); - }; - let TokenTree::Token(token, _) = tt else { - return Err(psess.dcx().struct_span_err(tt.span(), "expected identifier")); - }; + let token = parse_token(iter, psess, fallback_span)?; + parse_ident_from_token(psess, token) +} + +fn parse_ident_from_token<'psess>( + psess: &'psess ParseSess, + token: &Token, +) -> PResult<'psess, Ident> { if let Some((elem, is_raw)) = token.ident() { if let IdentIsRaw::Yes = is_raw { return Err(psess.dcx().struct_span_err(elem.span, RAW_IDENT_ERR)); @@ -205,10 +225,24 @@ fn parse_ident<'psess>( Err(err) } +fn parse_token<'psess, 't>( + iter: &mut RefTokenTreeCursor<'t>, + psess: &'psess ParseSess, + fallback_span: Span, +) -> PResult<'psess, &'t Token> { + let Some(tt) = iter.next() else { + return Err(psess.dcx().struct_span_err(fallback_span, UNSUPPORTED_CONCAT_ELEM_ERR)); + }; + let TokenTree::Token(token, _) = tt else { + return Err(psess.dcx().struct_span_err(tt.span(), UNSUPPORTED_CONCAT_ELEM_ERR)); + }; + Ok(token) +} + /// Tries to move the iterator forward returning `true` if there is a comma. If not, then the /// iterator is not modified and the result is `false`. fn try_eat_comma(iter: &mut RefTokenTreeCursor<'_>) -> bool { - if let Some(TokenTree::Token(token::Token { kind: token::Comma, .. }, _)) = iter.look_ahead(0) { + if let Some(TokenTree::Token(Token { kind: token::Comma, .. }, _)) = iter.look_ahead(0) { let _ = iter.next(); return true; } @@ -218,8 +252,7 @@ fn try_eat_comma(iter: &mut RefTokenTreeCursor<'_>) -> bool { /// Tries to move the iterator forward returning `true` if there is a dollar sign. If not, then the /// iterator is not modified and the result is `false`. fn try_eat_dollar(iter: &mut RefTokenTreeCursor<'_>) -> bool { - if let Some(TokenTree::Token(token::Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0) - { + if let Some(TokenTree::Token(Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0) { let _ = iter.next(); return true; } @@ -232,8 +265,7 @@ fn eat_dollar<'psess>( psess: &'psess ParseSess, span: Span, ) -> PResult<'psess, ()> { - if let Some(TokenTree::Token(token::Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0) - { + if let Some(TokenTree::Token(Token { kind: token::Dollar, .. }, _)) = iter.look_ahead(0) { let _ = iter.next(); return Ok(()); } diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index f935f1b77e0b2..9b4dc13c703a1 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -11,11 +11,13 @@ use rustc_ast::token::{self, Delimiter, Token, TokenKind}; use rustc_ast::tokenstream::{DelimSpacing, DelimSpan, Spacing, TokenStream, TokenTree}; use rustc_data_structures::fx::FxHashMap; use rustc_errors::{pluralize, Diag, DiagCtxtHandle, PResult}; +use rustc_parse::lexer::nfc_normalize; use rustc_parse::parser::ParseNtResult; use rustc_session::parse::ParseSess; +use rustc_session::parse::SymbolGallery; use rustc_span::hygiene::{LocalExpnId, Transparency}; use rustc_span::symbol::{sym, Ident, MacroRulesNormalizedIdent}; -use rustc_span::{with_metavar_spans, Span, Symbol, SyntaxContext}; +use rustc_span::{with_metavar_spans, Span, SyntaxContext}; use smallvec::{smallvec, SmallVec}; use std::mem; @@ -312,7 +314,16 @@ pub(super) fn transcribe<'a>( // Replace meta-variable expressions with the result of their expansion. mbe::TokenTree::MetaVarExpr(sp, expr) => { - transcribe_metavar_expr(dcx, expr, interp, &mut marker, &repeats, &mut result, sp)?; + transcribe_metavar_expr( + dcx, + expr, + interp, + &mut marker, + &repeats, + &mut result, + sp, + &psess.symbol_gallery, + )?; } // If we are entering a new delimiter, we push its contents to the `stack` to be @@ -669,6 +680,7 @@ fn transcribe_metavar_expr<'a>( repeats: &[(usize, usize)], result: &mut Vec, sp: &DelimSpan, + symbol_gallery: &SymbolGallery, ) -> PResult<'a, ()> { let mut visited_span = || { let mut span = sp.entire(); @@ -680,16 +692,26 @@ fn transcribe_metavar_expr<'a>( let mut concatenated = String::new(); for element in elements.into_iter() { let string = match element { - MetaVarExprConcatElem::Ident(ident) => ident.to_string(), - MetaVarExprConcatElem::Var(ident) => extract_ident(dcx, *ident, interp)?, + MetaVarExprConcatElem::Ident(elem) => elem.to_string(), + MetaVarExprConcatElem::Literal(elem) => elem.as_str().into(), + MetaVarExprConcatElem::Var(elem) => extract_ident(dcx, *elem, interp)?, }; concatenated.push_str(&string); } + let symbol = nfc_normalize(&concatenated); + let concatenated_span = visited_span(); + if !rustc_lexer::is_ident(symbol.as_str()) { + return Err(dcx.struct_span_err( + concatenated_span, + "`${concat(..)}` is not generating a valid identifier", + )); + } + symbol_gallery.insert(symbol, concatenated_span); // The current implementation marks the span as coming from the macro regardless of // contexts of the concatenated identifiers but this behavior may change in the // future. result.push(TokenTree::Token( - Token::from_ast_ident(Ident::new(Symbol::intern(&concatenated), visited_span())), + Token::from_ast_ident(Ident::new(symbol, concatenated_span)), Spacing::Alone, )); } diff --git a/tests/ui/macros/macro-metavar-expr-concat/allowed-operations.rs b/tests/ui/macros/macro-metavar-expr-concat/allowed-operations.rs index e44eeffb01be8..1acefa314aab8 100644 --- a/tests/ui/macros/macro-metavar-expr-concat/allowed-operations.rs +++ b/tests/ui/macros/macro-metavar-expr-concat/allowed-operations.rs @@ -37,6 +37,16 @@ macro_rules! without_dollar_sign_is_an_ident { }; } +macro_rules! literals { + ($ident:ident) => {{ + let ${concat(_a, "_b")}: () = (); + let ${concat("_b", _a)}: () = (); + + let ${concat($ident, "_b")}: () = (); + let ${concat("_b", $ident)}: () = (); + }}; +} + fn main() { create_things!(behold); behold_separated_idents_in_a_fn(); @@ -55,4 +65,6 @@ fn main() { without_dollar_sign_is_an_ident!(_123); assert_eq!(VARident, 1); assert_eq!(VAR_123, 2); + + literals!(_hello); } diff --git a/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.rs b/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.rs index f72b9baca89f8..b1cb2141cc42f 100644 --- a/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.rs +++ b/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.rs @@ -26,14 +26,14 @@ macro_rules! idents_11 { macro_rules! no_params { () => { let ${concat(r#abc, abc)}: () = (); - //~^ ERROR `${concat(..)}` currently does not support raw identifiers + //~^ ERROR expected identifier or string literal //~| ERROR expected pattern, found `$` let ${concat(abc, r#abc)}: () = (); - //~^ ERROR `${concat(..)}` currently does not support raw identifiers + //~^ ERROR expected identifier or string literal let ${concat(r#abc, r#abc)}: () = (); - //~^ ERROR `${concat(..)}` currently does not support raw identifiers + //~^ ERROR expected identifier or string literal }; } diff --git a/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.stderr b/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.stderr index dd525cf0801be..4e11e20acc551 100644 --- a/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.stderr +++ b/tests/ui/macros/macro-metavar-expr-concat/raw-identifiers.stderr @@ -1,16 +1,16 @@ -error: `${concat(..)}` currently does not support raw identifiers +error: expected identifier or string literal --> $DIR/raw-identifiers.rs:28:22 | LL | let ${concat(r#abc, abc)}: () = (); | ^^^^^ -error: `${concat(..)}` currently does not support raw identifiers +error: expected identifier or string literal --> $DIR/raw-identifiers.rs:32:27 | LL | let ${concat(abc, r#abc)}: () = (); | ^^^^^ -error: `${concat(..)}` currently does not support raw identifiers +error: expected identifier or string literal --> $DIR/raw-identifiers.rs:35:22 | LL | let ${concat(r#abc, r#abc)}: () = (); diff --git a/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.rs b/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.rs index bf47442ea76fb..b2845c8d1c1fc 100644 --- a/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.rs +++ b/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.rs @@ -11,9 +11,6 @@ macro_rules! wrong_concat_declarations { ${concat(aaaa,)} //~^ ERROR expected identifier - ${concat(aaaa, 1)} - //~^ ERROR expected identifier - ${concat(_, aaaa)} ${concat(aaaa aaaa)} @@ -30,9 +27,6 @@ macro_rules! wrong_concat_declarations { ${concat($ex, aaaa,)} //~^ ERROR expected identifier - - ${concat($ex, aaaa, 123)} - //~^ ERROR expected identifier }; } @@ -43,8 +37,80 @@ macro_rules! dollar_sign_without_referenced_ident { }; } +macro_rules! starting_number { + ($ident:ident) => {{ + let ${concat("1", $ident)}: () = (); + //~^ ERROR `${concat(..)}` is not generating a valid identifier + }}; +} + +macro_rules! starting_valid_unicode { + ($ident:ident) => {{ + let ${concat("Ý", $ident)}: () = (); + }}; +} + +macro_rules! starting_invalid_unicode { + ($ident:ident) => {{ + let ${concat("\u{00BD}", $ident)}: () = (); + //~^ ERROR `${concat(..)}` is not generating a valid identifier + }}; +} + +macro_rules! ending_number { + ($ident:ident) => {{ + let ${concat($ident, "1")}: () = (); + }}; +} + +macro_rules! ending_valid_unicode { + ($ident:ident) => {{ + let ${concat($ident, "Ý")}: () = (); + }}; +} + +macro_rules! ending_invalid_unicode { + ($ident:ident) => {{ + let ${concat($ident, "\u{00BD}")}: () = (); + //~^ ERROR `${concat(..)}` is not generating a valid identifier + }}; +} + +macro_rules! empty { + () => {{ + let ${concat("", "")}: () = (); + //~^ ERROR `${concat(..)}` is not generating a valid identifier + }}; +} + +macro_rules! unsupported_literals { + ($ident:ident) => {{ + let ${concat(_a, 'b')}: () = (); + //~^ ERROR expected identifier or string literal + //~| ERROR expected pattern + let ${concat(_a, 1)}: () = (); + //~^ ERROR expected identifier or string literal + + let ${concat($ident, 'b')}: () = (); + //~^ ERROR expected identifier or string literal + let ${concat($ident, 1)}: () = (); + //~^ ERROR expected identifier or string literal + }}; +} + fn main() { wrong_concat_declarations!(1); dollar_sign_without_referenced_ident!(VAR); + + starting_number!(_abc); + starting_valid_unicode!(_abc); + starting_invalid_unicode!(_abc); + + ending_number!(_abc); + ending_valid_unicode!(_abc); + ending_invalid_unicode!(_abc); + unsupported_literals!(_abc); + + empty!(); } diff --git a/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.stderr b/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.stderr index b216a86d59abe..2fe5842b39eb5 100644 --- a/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.stderr +++ b/tests/ui/macros/macro-metavar-expr-concat/syntax-errors.stderr @@ -1,4 +1,4 @@ -error: expected identifier +error: expected identifier or string literal --> $DIR/syntax-errors.rs:5:10 | LL | ${concat()} @@ -10,59 +10,126 @@ error: `concat` must have at least two elements LL | ${concat(aaaa)} | ^^^^^^ -error: expected identifier +error: expected identifier or string literal --> $DIR/syntax-errors.rs:11:10 | LL | ${concat(aaaa,)} | ^^^^^^^^^^^^^^^ -error: expected identifier, found `1` - --> $DIR/syntax-errors.rs:14:24 - | -LL | ${concat(aaaa, 1)} - | ^ help: try removing `1` - error: expected comma - --> $DIR/syntax-errors.rs:19:10 + --> $DIR/syntax-errors.rs:16:10 | LL | ${concat(aaaa aaaa)} | ^^^^^^^^^^^^^^^^^^^ error: `concat` must have at least two elements - --> $DIR/syntax-errors.rs:22:11 + --> $DIR/syntax-errors.rs:19:11 | LL | ${concat($ex)} | ^^^^^^ error: expected comma - --> $DIR/syntax-errors.rs:28:10 + --> $DIR/syntax-errors.rs:25:10 | LL | ${concat($ex, aaaa 123)} | ^^^^^^^^^^^^^^^^^^^^^^^ -error: expected identifier - --> $DIR/syntax-errors.rs:31:10 +error: expected identifier or string literal + --> $DIR/syntax-errors.rs:28:10 | LL | ${concat($ex, aaaa,)} | ^^^^^^^^^^^^^^^^^^^^ -error: expected identifier, found `123` - --> $DIR/syntax-errors.rs:34:29 +error: expected identifier or string literal + --> $DIR/syntax-errors.rs:88:26 | -LL | ${concat($ex, aaaa, 123)} - | ^^^ help: try removing `123` +LL | let ${concat(_a, 'b')}: () = (); + | ^^^ + +error: expected identifier or string literal + --> $DIR/syntax-errors.rs:91:26 + | +LL | let ${concat(_a, 1)}: () = (); + | ^ + +error: expected identifier or string literal + --> $DIR/syntax-errors.rs:94:30 + | +LL | let ${concat($ident, 'b')}: () = (); + | ^^^ + +error: expected identifier or string literal + --> $DIR/syntax-errors.rs:96:30 + | +LL | let ${concat($ident, 1)}: () = (); + | ^ error: `${concat(..)}` currently only accepts identifiers or meta-variables as parameters - --> $DIR/syntax-errors.rs:25:19 + --> $DIR/syntax-errors.rs:22:19 | LL | ${concat($ex, aaaa)} | ^^ error: variable `foo` is not recognized in meta-variable expression - --> $DIR/syntax-errors.rs:41:30 + --> $DIR/syntax-errors.rs:35:30 | LL | const ${concat(FOO, $foo)}: i32 = 2; | ^^^ -error: aborting due to 11 previous errors +error: `${concat(..)}` is not generating a valid identifier + --> $DIR/syntax-errors.rs:42:14 + | +LL | let ${concat("1", $ident)}: () = (); + | ^^^^^^^^^^^^^^^^^^^^^ +... +LL | starting_number!(_abc); + | ---------------------- in this macro invocation + | + = note: this error originates in the macro `starting_number` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `${concat(..)}` is not generating a valid identifier + --> $DIR/syntax-errors.rs:55:14 + | +LL | let ${concat("\u{00BD}", $ident)}: () = (); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | starting_invalid_unicode!(_abc); + | ------------------------------- in this macro invocation + | + = note: this error originates in the macro `starting_invalid_unicode` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `${concat(..)}` is not generating a valid identifier + --> $DIR/syntax-errors.rs:74:14 + | +LL | let ${concat($ident, "\u{00BD}")}: () = (); + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +... +LL | ending_invalid_unicode!(_abc); + | ----------------------------- in this macro invocation + | + = note: this error originates in the macro `ending_invalid_unicode` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected pattern, found `$` + --> $DIR/syntax-errors.rs:88:13 + | +LL | let ${concat(_a, 'b')}: () = (); + | ^ expected pattern +... +LL | unsupported_literals!(_abc); + | --------------------------- in this macro invocation + | + = note: this error originates in the macro `unsupported_literals` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: `${concat(..)}` is not generating a valid identifier + --> $DIR/syntax-errors.rs:81:14 + | +LL | let ${concat("", "")}: () = (); + | ^^^^^^^^^^^^^^^^ +... +LL | empty!(); + | -------- in this macro invocation + | + = note: this error originates in the macro `empty` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 18 previous errors diff --git a/tests/ui/macros/macro-metavar-expr-concat/unicode-expansion.rs b/tests/ui/macros/macro-metavar-expr-concat/unicode-expansion.rs new file mode 100644 index 0000000000000..b2cfb211e2d1e --- /dev/null +++ b/tests/ui/macros/macro-metavar-expr-concat/unicode-expansion.rs @@ -0,0 +1,14 @@ +//@ run-pass + +#![feature(macro_metavar_expr_concat)] + +macro_rules! turn_to_page { + ($ident:ident) => { + const ${concat("Ḧ", $ident)}: i32 = 394; + }; +} + +fn main() { + turn_to_page!(P); + assert_eq!(ḦP, 394); +} diff --git a/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr b/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr index 8e4ba192d79f5..2c44ad2e0a4ad 100644 --- a/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr +++ b/tests/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr @@ -190,7 +190,7 @@ error: unrecognized meta-variable expression LL | ( $( $i:ident ),* ) => { ${ aaaaaaaaaaaaaa(i) } }; | ^^^^^^^^^^^^^^ help: supported expressions are count, ignore, index and len -error: expected identifier +error: expected identifier or string literal --> $DIR/syntax-errors.rs:118:33 | LL | ( $( $i:ident ),* ) => { ${ {} } }; From 58aad3c72c32936b49f92f552e0157b9c8c862ee Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 6 Jul 2024 11:54:22 -0400 Subject: [PATCH 08/32] iter_identity is a better name --- .../rustc_hir_analysis/src/check/wfcheck.rs | 23 +++++++++---------- compiler/rustc_hir_typeck/src/coercion.rs | 9 ++++---- .../src/opaque_hidden_inferred_bound.rs | 4 +--- compiler/rustc_lint/src/unused.rs | 4 +--- compiler/rustc_middle/src/ty/generics.rs | 2 +- .../src/traits/object_safety.rs | 2 +- compiler/rustc_ty_utils/src/opaque_types.rs | 6 ++--- compiler/rustc_ty_utils/src/sig_types.rs | 2 +- compiler/rustc_type_ir/src/binder.rs | 6 ++--- src/librustdoc/clean/mod.rs | 3 +-- src/tools/clippy/clippy_utils/src/ty.rs | 2 +- 11 files changed, 26 insertions(+), 37 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/check/wfcheck.rs b/compiler/rustc_hir_analysis/src/check/wfcheck.rs index 2230528a5ae11..c31f7487dcdfb 100644 --- a/compiler/rustc_hir_analysis/src/check/wfcheck.rs +++ b/compiler/rustc_hir_analysis/src/check/wfcheck.rs @@ -477,7 +477,7 @@ fn check_gat_where_clauses(tcx: TyCtxt<'_>, trait_def_id: LocalDefId) { param_env, item_def_id, tcx.explicit_item_bounds(item_def_id) - .instantiate_identity_iter_copied() + .iter_identity_copied() .collect::>(), &FxIndexSet::default(), gat_def_id, @@ -1204,17 +1204,16 @@ fn check_associated_type_bounds(wfcx: &WfCheckingCtxt<'_, '_>, item: ty::AssocIt let bounds = wfcx.tcx().explicit_item_bounds(item.def_id); debug!("check_associated_type_bounds: bounds={:?}", bounds); - let wf_obligations = - bounds.instantiate_identity_iter_copied().flat_map(|(bound, bound_span)| { - let normalized_bound = wfcx.normalize(span, None, bound); - traits::wf::clause_obligations( - wfcx.infcx, - wfcx.param_env, - wfcx.body_def_id, - normalized_bound, - bound_span, - ) - }); + let wf_obligations = bounds.iter_identity_copied().flat_map(|(bound, bound_span)| { + let normalized_bound = wfcx.normalize(span, None, bound); + traits::wf::clause_obligations( + wfcx.infcx, + wfcx.param_env, + wfcx.body_def_id, + normalized_bound, + bound_span, + ) + }); wfcx.register_obligations(wf_obligations); } diff --git a/compiler/rustc_hir_typeck/src/coercion.rs b/compiler/rustc_hir_typeck/src/coercion.rs index f72e8a4afdef6..4f0a089ee956f 100644 --- a/compiler/rustc_hir_typeck/src/coercion.rs +++ b/compiler/rustc_hir_typeck/src/coercion.rs @@ -1752,10 +1752,8 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { fcx.probe(|_| { let ocx = ObligationCtxt::new(fcx); ocx.register_obligations( - fcx.tcx - .item_super_predicates(rpit_def_id) - .instantiate_identity_iter() - .filter_map(|clause| { + fcx.tcx.item_super_predicates(rpit_def_id).iter_identity().filter_map( + |clause| { let predicate = clause .kind() .map_bound(|clause| match clause { @@ -1776,7 +1774,8 @@ impl<'tcx, 'exprs, E: AsCoercionSite> CoerceMany<'tcx, 'exprs, E> { fcx.param_env, predicate, )) - }), + }, + ), ); ocx.select_where_possible().is_empty() }) diff --git a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs index 8b669bcc13f0b..5ee73dbfdc658 100644 --- a/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs +++ b/compiler/rustc_lint/src/opaque_hidden_inferred_bound.rs @@ -76,9 +76,7 @@ impl<'tcx> LateLintPass<'tcx> for OpaqueHiddenInferredBound { // For every projection predicate in the opaque type's explicit bounds, // check that the type that we're assigning actually satisfies the bounds // of the associated type. - for (pred, pred_span) in - cx.tcx.explicit_item_bounds(def_id).instantiate_identity_iter_copied() - { + for (pred, pred_span) in cx.tcx.explicit_item_bounds(def_id).iter_identity_copied() { infcx.enter_forall(pred.kind(), |predicate| { let ty::ClauseKind::Projection(proj) = predicate else { return; diff --git a/compiler/rustc_lint/src/unused.rs b/compiler/rustc_lint/src/unused.rs index 195a0f72475f0..65d42ed8054f9 100644 --- a/compiler/rustc_lint/src/unused.rs +++ b/compiler/rustc_lint/src/unused.rs @@ -298,9 +298,7 @@ impl<'tcx> LateLintPass<'tcx> for UnusedResults { ty::Alias(ty::Opaque | ty::Projection, ty::AliasTy { def_id: def, .. }) => { elaborate( cx.tcx, - cx.tcx - .explicit_item_super_predicates(def) - .instantiate_identity_iter_copied(), + cx.tcx.explicit_item_super_predicates(def).iter_identity_copied(), ) // We only care about self bounds for the impl-trait .filter_only_self() diff --git a/compiler/rustc_middle/src/ty/generics.rs b/compiler/rustc_middle/src/ty/generics.rs index 844023df1e315..11ed0bdaa7028 100644 --- a/compiler/rustc_middle/src/ty/generics.rs +++ b/compiler/rustc_middle/src/ty/generics.rs @@ -394,7 +394,7 @@ impl<'tcx> GenericPredicates<'tcx> { } pub fn instantiate_own_identity(&self) -> impl Iterator, Span)> { - EarlyBinder::bind(self.predicates).instantiate_identity_iter_copied() + EarlyBinder::bind(self.predicates).iter_identity_copied() } #[instrument(level = "debug", skip(self, tcx))] diff --git a/compiler/rustc_trait_selection/src/traits/object_safety.rs b/compiler/rustc_trait_selection/src/traits/object_safety.rs index 5b2c8fb1950d8..1c6993bdd3729 100644 --- a/compiler/rustc_trait_selection/src/traits/object_safety.rs +++ b/compiler/rustc_trait_selection/src/traits/object_safety.rs @@ -203,7 +203,7 @@ fn bounds_reference_self(tcx: TyCtxt<'_>, trait_def_id: DefId) -> SmallVec<[Span tcx.associated_items(trait_def_id) .in_definition_order() .filter(|item| item.kind == ty::AssocKind::Type) - .flat_map(|item| tcx.explicit_item_bounds(item.def_id).instantiate_identity_iter_copied()) + .flat_map(|item| tcx.explicit_item_bounds(item.def_id).iter_identity_copied()) .filter_map(|c| predicate_references_self(tcx, c)) .collect() } diff --git a/compiler/rustc_ty_utils/src/opaque_types.rs b/compiler/rustc_ty_utils/src/opaque_types.rs index 686f2f04ad9a7..5e91320f89753 100644 --- a/compiler/rustc_ty_utils/src/opaque_types.rs +++ b/compiler/rustc_ty_utils/src/opaque_types.rs @@ -169,10 +169,8 @@ impl<'tcx> OpaqueTypeCollector<'tcx> { // Collect opaque types nested within the associated type bounds of this opaque type. // We use identity args here, because we already know that the opaque type uses // only generic parameters, and thus instantiating would not give us more information. - for (pred, span) in self - .tcx - .explicit_item_bounds(alias_ty.def_id) - .instantiate_identity_iter_copied() + for (pred, span) in + self.tcx.explicit_item_bounds(alias_ty.def_id).iter_identity_copied() { trace!(?pred); self.visit_spanned(span, pred); diff --git a/compiler/rustc_ty_utils/src/sig_types.rs b/compiler/rustc_ty_utils/src/sig_types.rs index 97a1b94263e90..eb6cb369974fd 100644 --- a/compiler/rustc_ty_utils/src/sig_types.rs +++ b/compiler/rustc_ty_utils/src/sig_types.rs @@ -62,7 +62,7 @@ pub fn walk_types<'tcx, V: SpannedTypeVisitor<'tcx>>( } } DefKind::OpaqueTy => { - for (pred, span) in tcx.explicit_item_bounds(item).instantiate_identity_iter_copied() { + for (pred, span) in tcx.explicit_item_bounds(item).iter_identity_copied() { try_visit!(visitor.visit(span, pred)); } } diff --git a/compiler/rustc_type_ir/src/binder.rs b/compiler/rustc_type_ir/src/binder.rs index 491ef34430c15..2531219baecc8 100644 --- a/compiler/rustc_type_ir/src/binder.rs +++ b/compiler/rustc_type_ir/src/binder.rs @@ -448,7 +448,7 @@ where /// Similar to [`instantiate_identity`](EarlyBinder::instantiate_identity), /// but on an iterator of `TypeFoldable` values. - pub fn instantiate_identity_iter(self) -> Iter::IntoIter { + pub fn iter_identity(self) -> Iter::IntoIter { self.value.into_iter() } } @@ -515,9 +515,7 @@ where /// Similar to [`instantiate_identity`](EarlyBinder::instantiate_identity), /// but on an iterator of values that deref to a `TypeFoldable`. - pub fn instantiate_identity_iter_copied( - self, - ) -> impl Iterator::Target> { + pub fn iter_identity_copied(self) -> impl Iterator::Target> { self.value.into_iter().map(|v| *v) } } diff --git a/src/librustdoc/clean/mod.rs b/src/librustdoc/clean/mod.rs index b5660cd849246..aa596897fc42f 100644 --- a/src/librustdoc/clean/mod.rs +++ b/src/librustdoc/clean/mod.rs @@ -1404,8 +1404,7 @@ pub(crate) fn clean_middle_assoc_item<'tcx>( let mut predicates = tcx.explicit_predicates_of(assoc_item.def_id).predicates; if let ty::TraitContainer = assoc_item.container { - let bounds = - tcx.explicit_item_bounds(assoc_item.def_id).instantiate_identity_iter_copied(); + let bounds = tcx.explicit_item_bounds(assoc_item.def_id).iter_identity_copied(); predicates = tcx.arena.alloc_from_iter(bounds.chain(predicates.iter().copied())); } let mut generics = clean_ty_generics( diff --git a/src/tools/clippy/clippy_utils/src/ty.rs b/src/tools/clippy/clippy_utils/src/ty.rs index e5d2056419681..acaeb93f44a6e 100644 --- a/src/tools/clippy/clippy_utils/src/ty.rs +++ b/src/tools/clippy/clippy_utils/src/ty.rs @@ -99,7 +99,7 @@ pub fn contains_ty_adt_constructor_opaque<'tcx>(cx: &LateContext<'tcx>, ty: Ty<' for (predicate, _span) in cx .tcx .explicit_item_super_predicates(def_id) - .instantiate_identity_iter_copied() + .iter_identity_copied() { match predicate.kind().skip_binder() { // For `impl Trait`, it will register a predicate of `T: Trait`, so we go through From 39bb14e34a5670603386a7e6d4029b5e6699de6c Mon Sep 17 00:00:00 2001 From: Guillaume Gomez Date: Sun, 7 Jul 2024 13:05:07 +0200 Subject: [PATCH 09/32] Improve `run-make/output-type-permutations` code and improve `filename_not_in_denylist` API --- src/tools/run-make-support/src/lib.rs | 3 ++- tests/run-make/output-type-permutations/rmake.rs | 5 ++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/tools/run-make-support/src/lib.rs b/src/tools/run-make-support/src/lib.rs index 7bb89106de1b9..f464a109e7711 100644 --- a/src/tools/run-make-support/src/lib.rs +++ b/src/tools/run-make-support/src/lib.rs @@ -296,7 +296,8 @@ pub fn not_contains>(path: P, expected: &str) -> bool { } /// Returns true if the filename at `path` is not in `expected`. -pub fn filename_not_in_denylist>(path: P, expected: &[String]) -> bool { +pub fn filename_not_in_denylist, V: AsRef<[String]>>(path: P, expected: V) -> bool { + let expected = expected.as_ref(); path.as_ref() .file_name() .is_some_and(|name| !expected.contains(&name.to_str().unwrap().to_owned())) diff --git a/tests/run-make/output-type-permutations/rmake.rs b/tests/run-make/output-type-permutations/rmake.rs index 30036dc7eeacd..1d1637a744ec0 100644 --- a/tests/run-make/output-type-permutations/rmake.rs +++ b/tests/run-make/output-type-permutations/rmake.rs @@ -17,9 +17,8 @@ use std::path::PathBuf; // `rustc_invocation`: the rustc command being tested // Any unexpected output files not listed in `must_exist` or `can_exist` will cause a failure. fn assert_expected_output_files(expectations: Expectations, rustc_invocation: impl Fn()) { - let must_exist = expectations.expected_files; - let can_exist = expectations.allowed_files; - let dir = expectations.test_dir; + let Expectations { expected_files: must_exist, allowed_files: can_exist, test_dir: dir } = + expectations; fs_wrapper::create_dir(&dir); rustc_invocation(); From 4f54193ccfd67149c3fe6aa5bda10983b98a3982 Mon Sep 17 00:00:00 2001 From: Deadbeef Date: Sun, 7 Jul 2024 11:18:35 +0000 Subject: [PATCH 10/32] Fix intrinsic const parameter counting with `effects` --- .../rustc_hir_analysis/src/check/intrinsic.rs | 18 ++++--- tests/ui/intrinsics/not-overridden.rs | 2 +- .../safe-intrinsic-mismatch.effects.stderr | 52 +++++++++++++++++++ .../ui/intrinsics/safe-intrinsic-mismatch.rs | 7 ++- ...r => safe-intrinsic-mismatch.stock.stderr} | 12 ++--- .../effects/minicore.rs | 42 +++++++++++++-- .../effects/minicore.stderr | 24 ++++----- 7 files changed, 126 insertions(+), 31 deletions(-) create mode 100644 tests/ui/intrinsics/safe-intrinsic-mismatch.effects.stderr rename tests/ui/intrinsics/{safe-intrinsic-mismatch.stderr => safe-intrinsic-mismatch.stock.stderr} (86%) diff --git a/compiler/rustc_hir_analysis/src/check/intrinsic.rs b/compiler/rustc_hir_analysis/src/check/intrinsic.rs index f21aeb4c0b98c..6282499883ba4 100644 --- a/compiler/rustc_hir_analysis/src/check/intrinsic.rs +++ b/compiler/rustc_hir_analysis/src/check/intrinsic.rs @@ -26,15 +26,12 @@ fn equate_intrinsic_type<'tcx>( n_cts: usize, sig: ty::PolyFnSig<'tcx>, ) { - let (own_counts, span) = match tcx.hir_node_by_def_id(def_id) { + let (generics, span) = match tcx.hir_node_by_def_id(def_id) { hir::Node::Item(hir::Item { kind: hir::ItemKind::Fn(_, generics, _), .. }) | hir::Node::ForeignItem(hir::ForeignItem { kind: hir::ForeignItemKind::Fn(.., generics, _), .. - }) => { - let own_counts = tcx.generics_of(def_id).own_counts(); - (own_counts, generics.span) - } + }) => (tcx.generics_of(def_id), generics.span), _ => { struct_span_code_err!(tcx.dcx(), span, E0622, "intrinsic must be a function") .with_span_label(span, "expected a function") @@ -42,6 +39,7 @@ fn equate_intrinsic_type<'tcx>( return; } }; + let own_counts = generics.own_counts(); let gen_count_ok = |found: usize, expected: usize, descr: &str| -> bool { if found != expected { @@ -57,9 +55,17 @@ fn equate_intrinsic_type<'tcx>( } }; + // the host effect param should be invisible as it shouldn't matter + // whether effects is enabled for the intrinsic provider crate. + let consts_count = if generics.host_effect_index.is_some() { + own_counts.consts - 1 + } else { + own_counts.consts + }; + if gen_count_ok(own_counts.lifetimes, n_lts, "lifetime") && gen_count_ok(own_counts.types, n_tps, "type") - && gen_count_ok(own_counts.consts, n_cts, "const") + && gen_count_ok(consts_count, n_cts, "const") { let _ = check_function_signature( tcx, diff --git a/tests/ui/intrinsics/not-overridden.rs b/tests/ui/intrinsics/not-overridden.rs index a53071e304d89..93b408331b862 100644 --- a/tests/ui/intrinsics/not-overridden.rs +++ b/tests/ui/intrinsics/not-overridden.rs @@ -1,6 +1,6 @@ //! Check that intrinsics that do not get overridden, but are marked as such, //! cause an error instead of silently invoking the body. -#![feature(rustc_attrs/* , effects*/)] // FIXME(effects) +#![feature(rustc_attrs)] //@ build-fail //@ failure-status:101 //@ normalize-stderr-test ".*note: .*\n\n" -> "" diff --git a/tests/ui/intrinsics/safe-intrinsic-mismatch.effects.stderr b/tests/ui/intrinsics/safe-intrinsic-mismatch.effects.stderr new file mode 100644 index 0000000000000..d9a4960feec4d --- /dev/null +++ b/tests/ui/intrinsics/safe-intrinsic-mismatch.effects.stderr @@ -0,0 +1,52 @@ +error: using `#![feature(effects)]` without enabling next trait solver globally + | + = note: the next trait solver must be enabled globally for the effects feature to work correctly + = help: use `-Znext-solver` to enable + +error: intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `size_of` + --> $DIR/safe-intrinsic-mismatch.rs:11:5 + | +LL | fn size_of() -> usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + +error: intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `size_of` + --> $DIR/safe-intrinsic-mismatch.rs:11:5 + | +LL | fn size_of() -> usize; + | ^^^^^^^^^^^^^^^^^^^^^^^^ + | + = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` + +error: intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `assume` + --> $DIR/safe-intrinsic-mismatch.rs:16:1 + | +LL | const fn assume(_b: bool) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0308]: intrinsic has wrong type + --> $DIR/safe-intrinsic-mismatch.rs:16:16 + | +LL | const fn assume(_b: bool) {} + | ^ expected unsafe fn, found safe fn + | + = note: expected signature `unsafe fn(_)` + found signature `fn(_)` + +error: intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `const_deallocate` + --> $DIR/safe-intrinsic-mismatch.rs:20:1 + | +LL | const fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) {} + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +error[E0308]: intrinsic has wrong type + --> $DIR/safe-intrinsic-mismatch.rs:20:26 + | +LL | const fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) {} + | ^ expected unsafe fn, found safe fn + | + = note: expected signature `unsafe fn(_, _, _)` + found signature `fn(_, _, _)` + +error: aborting due to 7 previous errors + +For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/intrinsics/safe-intrinsic-mismatch.rs b/tests/ui/intrinsics/safe-intrinsic-mismatch.rs index c116ba7a62eeb..af563e996c1df 100644 --- a/tests/ui/intrinsics/safe-intrinsic-mismatch.rs +++ b/tests/ui/intrinsics/safe-intrinsic-mismatch.rs @@ -1,6 +1,11 @@ +//@ revisions: stock effects #![feature(intrinsics)] #![feature(rustc_attrs)] -// FIXME(effects) do this with revisions #![feature(effects)] +// as effects insert a const generic param to const intrinsics, +// check here that it doesn't report a const param mismatch either +// enabling or disabling effects. +#![cfg_attr(effects, feature(effects))] +#![allow(incomplete_features)] extern "rust-intrinsic" { fn size_of() -> usize; //~ ERROR intrinsic safety mismatch diff --git a/tests/ui/intrinsics/safe-intrinsic-mismatch.stderr b/tests/ui/intrinsics/safe-intrinsic-mismatch.stock.stderr similarity index 86% rename from tests/ui/intrinsics/safe-intrinsic-mismatch.stderr rename to tests/ui/intrinsics/safe-intrinsic-mismatch.stock.stderr index 7f37e0f821118..6864c0f36ded0 100644 --- a/tests/ui/intrinsics/safe-intrinsic-mismatch.stderr +++ b/tests/ui/intrinsics/safe-intrinsic-mismatch.stock.stderr @@ -1,11 +1,11 @@ error: intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `size_of` - --> $DIR/safe-intrinsic-mismatch.rs:6:5 + --> $DIR/safe-intrinsic-mismatch.rs:11:5 | LL | fn size_of() -> usize; | ^^^^^^^^^^^^^^^^^^^^^^^^ error: intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `size_of` - --> $DIR/safe-intrinsic-mismatch.rs:6:5 + --> $DIR/safe-intrinsic-mismatch.rs:11:5 | LL | fn size_of() -> usize; | ^^^^^^^^^^^^^^^^^^^^^^^^ @@ -13,13 +13,13 @@ LL | fn size_of() -> usize; = note: duplicate diagnostic emitted due to `-Z deduplicate-diagnostics=no` error: intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `assume` - --> $DIR/safe-intrinsic-mismatch.rs:11:1 + --> $DIR/safe-intrinsic-mismatch.rs:16:1 | LL | const fn assume(_b: bool) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0308]: intrinsic has wrong type - --> $DIR/safe-intrinsic-mismatch.rs:11:16 + --> $DIR/safe-intrinsic-mismatch.rs:16:16 | LL | const fn assume(_b: bool) {} | ^ expected unsafe fn, found safe fn @@ -28,13 +28,13 @@ LL | const fn assume(_b: bool) {} found signature `fn(_)` error: intrinsic safety mismatch between list of intrinsics within the compiler and core library intrinsics for intrinsic `const_deallocate` - --> $DIR/safe-intrinsic-mismatch.rs:15:1 + --> $DIR/safe-intrinsic-mismatch.rs:20:1 | LL | const fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) {} | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ error[E0308]: intrinsic has wrong type - --> $DIR/safe-intrinsic-mismatch.rs:15:26 + --> $DIR/safe-intrinsic-mismatch.rs:20:26 | LL | const fn const_deallocate(_ptr: *mut u8, _size: usize, _align: usize) {} | ^ expected unsafe fn, found safe fn diff --git a/tests/ui/rfcs/rfc-2632-const-trait-impl/effects/minicore.rs b/tests/ui/rfcs/rfc-2632-const-trait-impl/effects/minicore.rs index af88a73b4d692..63c353c7d6627 100644 --- a/tests/ui/rfcs/rfc-2632-const-trait-impl/effects/minicore.rs +++ b/tests/ui/rfcs/rfc-2632-const-trait-impl/effects/minicore.rs @@ -1,12 +1,16 @@ //@ known-bug: #110395 +//@ failure-status: 101 +//@ normalize-stderr-test ".*note: .*\n\n" -> "" +//@ normalize-stderr-test "thread 'rustc' panicked.*:\n.*\n" -> "" +//@ rustc-env:RUST_BACKTRACE=0 // FIXME(effects) check-pass -// FIXME(effects) fix intrinsics const parameter counting +//@ compile-flags: -Znext-solver #![crate_type = "lib"] #![feature(no_core, lang_items, unboxed_closures, auto_traits, intrinsics, rustc_attrs, staged_api)] -#![feature(fundamental)] +#![feature(fundamental, marker_trait_attr)] #![feature(const_trait_impl, effects, const_mut_refs)] -#![allow(internal_features)] +#![allow(internal_features, incomplete_features)] #![no_std] #![no_core] #![stable(feature = "minicore", since = "1.0.0")] @@ -532,3 +536,35 @@ fn test_const_eval_select() { const_eval_select((), const_fn, rt_fn); } + +mod effects { + use super::Sized; + + #[lang = "EffectsNoRuntime"] + pub struct NoRuntime; + #[lang = "EffectsMaybe"] + pub struct Maybe; + #[lang = "EffectsRuntime"] + pub struct Runtime; + + #[lang = "EffectsCompat"] + pub trait Compat<#[rustc_runtime] const RUNTIME: bool> {} + + impl Compat for NoRuntime {} + impl Compat for Runtime {} + impl<#[rustc_runtime] const RUNTIME: bool> Compat for Maybe {} + + #[lang = "EffectsTyCompat"] + #[marker] + pub trait TyCompat {} + + impl TyCompat for T {} + impl TyCompat for Maybe {} + impl TyCompat for T {} + + #[lang = "EffectsIntersection"] + pub trait Intersection { + #[lang = "EffectsIntersectionOutput"] + type Output: ?Sized; + } +} diff --git a/tests/ui/rfcs/rfc-2632-const-trait-impl/effects/minicore.stderr b/tests/ui/rfcs/rfc-2632-const-trait-impl/effects/minicore.stderr index 1963332b856de..823ab69df9cb5 100644 --- a/tests/ui/rfcs/rfc-2632-const-trait-impl/effects/minicore.stderr +++ b/tests/ui/rfcs/rfc-2632-const-trait-impl/effects/minicore.stderr @@ -1,17 +1,13 @@ -warning: the feature `effects` is incomplete and may not be safe to use and/or cause compiler crashes - --> $DIR/minicore.rs:8:30 - | -LL | #![feature(const_trait_impl, effects, const_mut_refs)] - | ^^^^^^^ - | - = note: see issue #102090 for more information - = note: `#[warn(incomplete_features)]` on by default +error: the compiler unexpectedly panicked. this is a bug. -error: requires `EffectsCompat` lang_item - --> $DIR/minicore.rs:455:9 - | -LL | impl Clone for RefCell { - | ^^^^^ +query stack during panic: +#0 [check_well_formed] checking that `` is well-formed +#1 [check_mod_type_wf] checking that types are well-formed in top-level module +end of query stack -error: aborting due to 1 previous error; 1 warning emitted +error: the compiler unexpectedly panicked. this is a bug. +query stack during panic: +#0 [check_well_formed] checking that `drop` is well-formed +#1 [check_mod_type_wf] checking that types are well-formed in top-level module +end of query stack From 8e7314f6e06a15c6cdced279008f37d0ff5374c5 Mon Sep 17 00:00:00 2001 From: schvv31n Date: Sun, 7 Jul 2024 15:02:30 +0100 Subject: [PATCH 11/32] rustdoc-json: add trait/type alias tests --- tests/rustdoc-json/trait_alias.rs | 18 ++++++++++++++++++ tests/rustdoc-json/type_alias.rs | 15 +++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 tests/rustdoc-json/trait_alias.rs create mode 100644 tests/rustdoc-json/type_alias.rs diff --git a/tests/rustdoc-json/trait_alias.rs b/tests/rustdoc-json/trait_alias.rs new file mode 100644 index 0000000000000..dc930550ef1ec --- /dev/null +++ b/tests/rustdoc-json/trait_alias.rs @@ -0,0 +1,18 @@ +// ignore-tidy-linelength +#![feature(trait_alias)] + +// @set StrLike = "$.index[*][?(@.name=='StrLike')].id" +// @is "$.index[*][?(@.name=='StrLike')].visibility" \"public\" +// @has "$.index[*][?(@.name=='StrLike')].inner.trait_alias" +// @is "$.index[*][?(@.name=='StrLike')].span.filename" $FILE +pub trait StrLike = AsRef; + +// @is "$.index[*][?(@.name=='f')].inner.function.decl.output.impl_trait[0].trait_bound.trait.id" $StrLike +pub fn f() -> impl StrLike { + "heya" +} + +// @!is "$.index[*][?(@.name=='g')].inner.function.decl.output.impl_trait[0].trait_bound.trait.id" $StrLike +pub fn g() -> impl AsRef { + "heya" +} diff --git a/tests/rustdoc-json/type_alias.rs b/tests/rustdoc-json/type_alias.rs new file mode 100644 index 0000000000000..7a938c50ba0b2 --- /dev/null +++ b/tests/rustdoc-json/type_alias.rs @@ -0,0 +1,15 @@ +// @set IntVec = "$.index[*][?(@.name=='IntVec')].id" +// @is "$.index[*][?(@.name=='IntVec')].visibility" \"public\" +// @has "$.index[*][?(@.name=='IntVec')].inner.type_alias" +// @is "$.index[*][?(@.name=='IntVec')].span.filename" $FILE +pub type IntVec = Vec; + +// @is "$.index[*][?(@.name=='f')].inner.function.decl.output.resolved_path.id" $IntVec +pub fn f() -> IntVec { + vec![0; 32] +} + +// @!is "$.index[*][?(@.name=='g')].inner.function.decl.output.resolved_path.id" $IntVec +pub fn g() -> Vec { + vec![0; 32] +} From b2e30bdec480d38d050b7a8a3281cbd71fdcb075 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 6 Jul 2024 18:16:02 -0400 Subject: [PATCH 12/32] Add fundamental to trait def --- compiler/rustc_hir_analysis/src/collect.rs | 2 ++ compiler/rustc_middle/src/ty/trait_def.rs | 7 ++++++- compiler/rustc_trait_selection/src/traits/coherence.rs | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/compiler/rustc_hir_analysis/src/collect.rs b/compiler/rustc_hir_analysis/src/collect.rs index 41fbef489405c..843e4d41e001e 100644 --- a/compiler/rustc_hir_analysis/src/collect.rs +++ b/compiler/rustc_hir_analysis/src/collect.rs @@ -1201,6 +1201,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { let is_marker = tcx.has_attr(def_id, sym::marker); let rustc_coinductive = tcx.has_attr(def_id, sym::rustc_coinductive); + let is_fundamental = tcx.has_attr(def_id, sym::fundamental); // FIXME: We could probably do way better attribute validation here. let mut skip_array_during_method_dispatch = false; @@ -1352,6 +1353,7 @@ fn trait_def(tcx: TyCtxt<'_>, def_id: LocalDefId) -> ty::TraitDef { has_auto_impl: is_auto, is_marker, is_coinductive: rustc_coinductive || is_auto, + is_fundamental, skip_array_during_method_dispatch, skip_boxed_slice_during_method_dispatch, specialization_kind, diff --git a/compiler/rustc_middle/src/ty/trait_def.rs b/compiler/rustc_middle/src/ty/trait_def.rs index 4dba97c3b5b83..076a74ca6f8d7 100644 --- a/compiler/rustc_middle/src/ty/trait_def.rs +++ b/compiler/rustc_middle/src/ty/trait_def.rs @@ -31,7 +31,7 @@ pub struct TraitDef { /// and thus `impl`s of it are allowed to overlap. pub is_marker: bool, - /// If `true`, then this trait has to `#[rustc_coinductive]` attribute or + /// If `true`, then this trait has the `#[rustc_coinductive]` attribute or /// is an auto trait. This indicates that trait solver cycles involving an /// `X: ThisTrait` goal are accepted. /// @@ -40,6 +40,11 @@ pub struct TraitDef { /// also have already switched to the new trait solver. pub is_coinductive: bool, + /// If `true`, then this trait has the `#[fundamental]` attribute. This + /// affects how conherence computes whether a trait may have trait implementations + /// added in the future. + pub is_fundamental: bool, + /// If `true`, then this trait has the `#[rustc_skip_during_method_dispatch(array)]` /// attribute, indicating that editions before 2021 should not consider this trait /// during method dispatch if the receiver is an array. diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs index 9f0d84e7d4526..57ba6c33ac5a5 100644 --- a/compiler/rustc_trait_selection/src/traits/coherence.rs +++ b/compiler/rustc_trait_selection/src/traits/coherence.rs @@ -666,7 +666,7 @@ pub fn trait_ref_is_local_or_fundamental<'tcx>( tcx: TyCtxt<'tcx>, trait_ref: ty::TraitRef<'tcx>, ) -> bool { - trait_ref.def_id.is_local() || tcx.has_attr(trait_ref.def_id, sym::fundamental) + trait_ref.def_id.is_local() || tcx.trait_def(trait_ref.def_id).is_fundamental } #[derive(Debug, Copy, Clone)] From a982471e07a87f1a379682d3b6241f61b4c9f135 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 6 Jul 2024 18:24:51 -0400 Subject: [PATCH 13/32] Uplift trait_ref_is_knowable and friends --- .../src/coherence/orphan.rs | 15 +- compiler/rustc_hir_analysis/src/lib.rs | 1 + compiler/rustc_infer/src/infer/context.rs | 4 + compiler/rustc_middle/src/ty/adt.rs | 4 + compiler/rustc_middle/src/ty/context.rs | 8 + .../rustc_next_trait_solver/src/coherence.rs | 469 ++++++++++++++++++ compiler/rustc_next_trait_solver/src/lib.rs | 1 + compiler/rustc_trait_selection/src/lib.rs | 1 + .../src/traits/coherence.rs | 450 +---------------- .../src/traits/select/mod.rs | 2 +- compiler/rustc_type_ir/src/infer_ctxt.rs | 5 + compiler/rustc_type_ir/src/inherent.rs | 4 + compiler/rustc_type_ir/src/interner.rs | 2 + 13 files changed, 508 insertions(+), 458 deletions(-) create mode 100644 compiler/rustc_next_trait_solver/src/coherence.rs diff --git a/compiler/rustc_hir_analysis/src/coherence/orphan.rs b/compiler/rustc_hir_analysis/src/coherence/orphan.rs index 5cb91603fd067..16f72f38d6088 100644 --- a/compiler/rustc_hir_analysis/src/coherence/orphan.rs +++ b/compiler/rustc_hir_analysis/src/coherence/orphan.rs @@ -286,7 +286,7 @@ fn orphan_check<'tcx>( tcx: TyCtxt<'tcx>, impl_def_id: LocalDefId, mode: OrphanCheckMode, -) -> Result<(), OrphanCheckErr<'tcx, FxIndexSet>> { +) -> Result<(), OrphanCheckErr, FxIndexSet>> { // We only accept this routine to be invoked on implementations // of a trait, not inherent implementations. let trait_ref = tcx.impl_trait_ref(impl_def_id).unwrap(); @@ -326,17 +326,16 @@ fn orphan_check<'tcx>( ty }; - Ok(ty) + Ok::<_, !>(ty) }; - let Ok(result) = traits::orphan_check_trait_ref::( + let result = traits::orphan_check_trait_ref( &infcx, trait_ref, traits::InCrate::Local { mode }, lazily_normalize_ty, - ) else { - unreachable!() - }; + ) + .into_ok(); // (2) Try to map the remaining inference vars back to generic params. result.map_err(|err| match err { @@ -369,7 +368,7 @@ fn emit_orphan_check_error<'tcx>( tcx: TyCtxt<'tcx>, trait_ref: ty::TraitRef<'tcx>, impl_def_id: LocalDefId, - err: traits::OrphanCheckErr<'tcx, FxIndexSet>, + err: traits::OrphanCheckErr, FxIndexSet>, ) -> ErrorGuaranteed { match err { traits::OrphanCheckErr::NonLocalInputType(tys) => { @@ -482,7 +481,7 @@ fn emit_orphan_check_error<'tcx>( fn lint_uncovered_ty_params<'tcx>( tcx: TyCtxt<'tcx>, - UncoveredTyParams { uncovered, local_ty }: UncoveredTyParams<'tcx, FxIndexSet>, + UncoveredTyParams { uncovered, local_ty }: UncoveredTyParams, FxIndexSet>, impl_def_id: LocalDefId, ) { let hir_id = tcx.local_def_id_to_hir_id(impl_def_id); diff --git a/compiler/rustc_hir_analysis/src/lib.rs b/compiler/rustc_hir_analysis/src/lib.rs index 44f1830a3b17b..2f6b0a582e58c 100644 --- a/compiler/rustc_hir_analysis/src/lib.rs +++ b/compiler/rustc_hir_analysis/src/lib.rs @@ -71,6 +71,7 @@ This API is completely unstable and subject to change. #![feature(rustdoc_internals)] #![feature(slice_partition_dedup)] #![feature(try_blocks)] +#![feature(unwrap_infallible)] // tidy-alphabetical-end #[macro_use] diff --git a/compiler/rustc_infer/src/infer/context.rs b/compiler/rustc_infer/src/infer/context.rs index 209996b12e2ed..f35a8162d96f1 100644 --- a/compiler/rustc_infer/src/infer/context.rs +++ b/compiler/rustc_infer/src/infer/context.rs @@ -151,6 +151,10 @@ impl<'tcx> InferCtxtLike for InferCtxt<'tcx> { .eq_structurally_relating_aliases_no_trace(lhs, rhs) } + fn shallow_resolve(&self, ty: Ty<'tcx>) -> Ty<'tcx> { + self.shallow_resolve(ty) + } + fn resolve_vars_if_possible(&self, value: T) -> T where T: TypeFoldable>, diff --git a/compiler/rustc_middle/src/ty/adt.rs b/compiler/rustc_middle/src/ty/adt.rs index 8e221cdc603ba..88ee32eae9529 100644 --- a/compiler/rustc_middle/src/ty/adt.rs +++ b/compiler/rustc_middle/src/ty/adt.rs @@ -229,6 +229,10 @@ impl<'tcx> rustc_type_ir::inherent::AdtDef> for AdtDef<'tcx> { fn sized_constraint(self, tcx: TyCtxt<'tcx>) -> Option>> { self.sized_constraint(tcx) } + + fn is_fundamental(self) -> bool { + self.is_fundamental() + } } #[derive(Copy, Clone, Debug, Eq, PartialEq, HashStable, TyEncodable, TyDecodable)] diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index dca48069974f0..dab3d3dbae452 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -524,6 +524,10 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.is_object_safe(trait_def_id) } + fn trait_is_fundamental(self, def_id: DefId) -> bool { + self.trait_def(def_id).is_fundamental + } + fn trait_may_be_implemented_via_object(self, trait_def_id: DefId) -> bool { self.trait_def(trait_def_id).implement_via_object } @@ -635,6 +639,10 @@ bidirectional_lang_item_map! { } impl<'tcx> rustc_type_ir::inherent::DefId> for DefId { + fn is_local(self) -> bool { + self.is_local() + } + fn as_local(self) -> Option { self.as_local() } diff --git a/compiler/rustc_next_trait_solver/src/coherence.rs b/compiler/rustc_next_trait_solver/src/coherence.rs new file mode 100644 index 0000000000000..55f602d907bbd --- /dev/null +++ b/compiler/rustc_next_trait_solver/src/coherence.rs @@ -0,0 +1,469 @@ +use std::fmt::Debug; +use std::ops::ControlFlow; + +use rustc_type_ir::inherent::*; +use rustc_type_ir::visit::{TypeVisitable, TypeVisitableExt, TypeVisitor}; +use rustc_type_ir::{self as ty, InferCtxtLike, Interner}; +use tracing::instrument; + +/// Whether we do the orphan check relative to this crate or to some remote crate. +#[derive(Copy, Clone, Debug)] +pub enum InCrate { + Local { mode: OrphanCheckMode }, + Remote, +} + +#[derive(Copy, Clone, Debug)] +pub enum OrphanCheckMode { + /// Proper orphan check. + Proper, + /// Improper orphan check for backward compatibility. + /// + /// In this mode, type params inside projections are considered to be covered + /// even if the projection may normalize to a type that doesn't actually cover + /// them. This is unsound. See also [#124559] and [#99554]. + /// + /// [#124559]: https://github.com/rust-lang/rust/issues/124559 + /// [#99554]: https://github.com/rust-lang/rust/issues/99554 + Compat, +} + +#[derive(Debug, Copy, Clone)] +pub enum Conflict { + Upstream, + Downstream, +} + +/// Returns whether all impls which would apply to the `trait_ref` +/// e.g. `Ty: Trait` are already known in the local crate. +/// +/// This both checks whether any downstream or sibling crates could +/// implement it and whether an upstream crate can add this impl +/// without breaking backwards compatibility. +#[instrument(level = "debug", skip(infcx, lazily_normalize_ty), ret)] +pub fn trait_ref_is_knowable( + infcx: &Infcx, + trait_ref: ty::TraitRef, + mut lazily_normalize_ty: impl FnMut(I::Ty) -> Result, +) -> Result, E> +where + Infcx: InferCtxtLike, + I: Interner, + E: Debug, +{ + if orphan_check_trait_ref(infcx, trait_ref, InCrate::Remote, &mut lazily_normalize_ty)?.is_ok() + { + // A downstream or cousin crate is allowed to implement some + // generic parameters of this trait-ref. + return Ok(Err(Conflict::Downstream)); + } + + if trait_ref_is_local_or_fundamental(infcx.cx(), trait_ref) { + // This is a local or fundamental trait, so future-compatibility + // is no concern. We know that downstream/cousin crates are not + // allowed to implement a generic parameter of this trait ref, + // which means impls could only come from dependencies of this + // crate, which we already know about. + return Ok(Ok(())); + } + + // This is a remote non-fundamental trait, so if another crate + // can be the "final owner" of the generic parameters of this trait-ref, + // they are allowed to implement it future-compatibly. + // + // However, if we are a final owner, then nobody else can be, + // and if we are an intermediate owner, then we don't care + // about future-compatibility, which means that we're OK if + // we are an owner. + if orphan_check_trait_ref( + infcx, + trait_ref, + InCrate::Local { mode: OrphanCheckMode::Proper }, + &mut lazily_normalize_ty, + )? + .is_ok() + { + Ok(Ok(())) + } else { + Ok(Err(Conflict::Upstream)) + } +} + +pub fn trait_ref_is_local_or_fundamental(tcx: I, trait_ref: ty::TraitRef) -> bool { + trait_ref.def_id.is_local() || tcx.trait_is_fundamental(trait_ref.def_id) +} + +#[derive(Debug, Copy, Clone)] +pub enum IsFirstInputType { + No, + Yes, +} + +impl From for IsFirstInputType { + fn from(b: bool) -> IsFirstInputType { + match b { + false => IsFirstInputType::No, + true => IsFirstInputType::Yes, + } + } +} + +#[derive(derivative::Derivative)] +#[derivative(Debug(bound = "T: Debug"))] +pub enum OrphanCheckErr { + NonLocalInputType(Vec<(I::Ty, IsFirstInputType)>), + UncoveredTyParams(UncoveredTyParams), +} + +#[derive(derivative::Derivative)] +#[derivative(Debug(bound = "T: Debug"))] +pub struct UncoveredTyParams { + pub uncovered: T, + pub local_ty: Option, +} + +/// Checks whether a trait-ref is potentially implementable by a crate. +/// +/// The current rule is that a trait-ref orphan checks in a crate C: +/// +/// 1. Order the parameters in the trait-ref in generic parameters order +/// - Self first, others linearly (e.g., `>` is U < V < W). +/// 2. Of these type parameters, there is at least one type parameter +/// in which, walking the type as a tree, you can reach a type local +/// to C where all types in-between are fundamental types. Call the +/// first such parameter the "local key parameter". +/// - e.g., `Box` is OK, because you can visit LocalType +/// going through `Box`, which is fundamental. +/// - similarly, `FundamentalPair, Box>` is OK for +/// the same reason. +/// - but (knowing that `Vec` is non-fundamental, and assuming it's +/// not local), `Vec` is bad, because `Vec<->` is between +/// the local type and the type parameter. +/// 3. Before this local type, no generic type parameter of the impl must +/// be reachable through fundamental types. +/// - e.g. `impl Trait for Vec` is fine, as `Vec` is not fundamental. +/// - while `impl Trait for Box` results in an error, as `T` is +/// reachable through the fundamental type `Box`. +/// 4. Every type in the local key parameter not known in C, going +/// through the parameter's type tree, must appear only as a subtree of +/// a type local to C, with only fundamental types between the type +/// local to C and the local key parameter. +/// - e.g., `Vec>>` (or equivalently `Box>>`) +/// is bad, because the only local type with `T` as a subtree is +/// `LocalType`, and `Vec<->` is between it and the type parameter. +/// - similarly, `FundamentalPair, T>` is bad, because +/// the second occurrence of `T` is not a subtree of *any* local type. +/// - however, `LocalType>` is OK, because `T` is a subtree of +/// `LocalType>`, which is local and has no types between it and +/// the type parameter. +/// +/// The orphan rules actually serve several different purposes: +/// +/// 1. They enable link-safety - i.e., 2 mutually-unknowing crates (where +/// every type local to one crate is unknown in the other) can't implement +/// the same trait-ref. This follows because it can be seen that no such +/// type can orphan-check in 2 such crates. +/// +/// To check that a local impl follows the orphan rules, we check it in +/// InCrate::Local mode, using type parameters for the "generic" types. +/// +/// In InCrate::Local mode the orphan check succeeds if the current crate +/// is definitely allowed to implement the given trait (no false positives). +/// +/// 2. They ground negative reasoning for coherence. If a user wants to +/// write both a conditional blanket impl and a specific impl, we need to +/// make sure they do not overlap. For example, if we write +/// ```ignore (illustrative) +/// impl IntoIterator for Vec +/// impl IntoIterator for T +/// ``` +/// We need to be able to prove that `Vec<$0>: !Iterator` for every type $0. +/// We can observe that this holds in the current crate, but we need to make +/// sure this will also hold in all unknown crates (both "independent" crates, +/// which we need for link-safety, and also child crates, because we don't want +/// child crates to get error for impl conflicts in a *dependency*). +/// +/// For that, we only allow negative reasoning if, for every assignment to the +/// inference variables, every unknown crate would get an orphan error if they +/// try to implement this trait-ref. To check for this, we use InCrate::Remote +/// mode. That is sound because we already know all the impls from known crates. +/// +/// In InCrate::Remote mode the orphan check succeeds if a foreign crate +/// *could* implement the given trait (no false negatives). +/// +/// 3. For non-`#[fundamental]` traits, they guarantee that parent crates can +/// add "non-blanket" impls without breaking negative reasoning in dependent +/// crates. This is the "rebalancing coherence" (RFC 1023) restriction. +/// +/// For that, we only allow a crate to perform negative reasoning on +/// non-local-non-`#[fundamental]` if there's a local key parameter as per (2). +/// +/// Because we never perform negative reasoning generically (coherence does +/// not involve type parameters), this can be interpreted as doing the full +/// orphan check (using InCrate::Local mode), instantiating non-local known +/// types for all inference variables. +/// +/// This allows for crates to future-compatibly add impls as long as they +/// can't apply to types with a key parameter in a child crate - applying +/// the rules, this basically means that every type parameter in the impl +/// must appear behind a non-fundamental type (because this is not a +/// type-system requirement, crate owners might also go for "semantic +/// future-compatibility" involving things such as sealed traits, but +/// the above requirement is sufficient, and is necessary in "open world" +/// cases). +/// +/// Note that this function is never called for types that have both type +/// parameters and inference variables. +#[instrument(level = "trace", skip(infcx, lazily_normalize_ty), ret)] +pub fn orphan_check_trait_ref( + infcx: &Infcx, + trait_ref: ty::TraitRef, + in_crate: InCrate, + lazily_normalize_ty: impl FnMut(I::Ty) -> Result, +) -> Result>, E> +where + Infcx: InferCtxtLike, + I: Interner, + E: Debug, +{ + if trait_ref.has_param() { + panic!("orphan check only expects inference variables: {trait_ref:?}"); + } + + let mut checker = OrphanChecker::new(infcx, in_crate, lazily_normalize_ty); + Ok(match trait_ref.visit_with(&mut checker) { + ControlFlow::Continue(()) => Err(OrphanCheckErr::NonLocalInputType(checker.non_local_tys)), + ControlFlow::Break(residual) => match residual { + OrphanCheckEarlyExit::NormalizationFailure(err) => return Err(err), + OrphanCheckEarlyExit::UncoveredTyParam(ty) => { + // Does there exist some local type after the `ParamTy`. + checker.search_first_local_ty = true; + let local_ty = match trait_ref.visit_with(&mut checker) { + ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(local_ty)) => Some(local_ty), + _ => None, + }; + Err(OrphanCheckErr::UncoveredTyParams(UncoveredTyParams { + uncovered: ty, + local_ty, + })) + } + OrphanCheckEarlyExit::LocalTy(_) => Ok(()), + }, + }) +} + +struct OrphanChecker<'a, Infcx, I: Interner, F> { + infcx: &'a Infcx, + in_crate: InCrate, + in_self_ty: bool, + lazily_normalize_ty: F, + /// Ignore orphan check failures and exclusively search for the first local type. + search_first_local_ty: bool, + non_local_tys: Vec<(I::Ty, IsFirstInputType)>, +} + +impl<'a, Infcx, I, F, E> OrphanChecker<'a, Infcx, I, F> +where + Infcx: InferCtxtLike, + I: Interner, + F: FnOnce(I::Ty) -> Result, +{ + fn new(infcx: &'a Infcx, in_crate: InCrate, lazily_normalize_ty: F) -> Self { + OrphanChecker { + infcx, + in_crate, + in_self_ty: true, + lazily_normalize_ty, + search_first_local_ty: false, + non_local_tys: Vec::new(), + } + } + + fn found_non_local_ty(&mut self, t: I::Ty) -> ControlFlow> { + self.non_local_tys.push((t, self.in_self_ty.into())); + ControlFlow::Continue(()) + } + + fn found_uncovered_ty_param(&mut self, ty: I::Ty) -> ControlFlow> { + if self.search_first_local_ty { + return ControlFlow::Continue(()); + } + + ControlFlow::Break(OrphanCheckEarlyExit::UncoveredTyParam(ty)) + } + + fn def_id_is_local(&mut self, def_id: I::DefId) -> bool { + match self.in_crate { + InCrate::Local { .. } => def_id.is_local(), + InCrate::Remote => false, + } + } +} + +enum OrphanCheckEarlyExit { + NormalizationFailure(E), + UncoveredTyParam(I::Ty), + LocalTy(I::Ty), +} + +impl<'a, Infcx, I, F, E> TypeVisitor for OrphanChecker<'a, Infcx, I, F> +where + Infcx: InferCtxtLike, + I: Interner, + F: FnMut(I::Ty) -> Result, +{ + type Result = ControlFlow>; + + fn visit_region(&mut self, _r: I::Region) -> Self::Result { + ControlFlow::Continue(()) + } + + fn visit_ty(&mut self, ty: I::Ty) -> Self::Result { + let ty = self.infcx.shallow_resolve(ty); + let ty = match (self.lazily_normalize_ty)(ty) { + Ok(norm_ty) if norm_ty.is_ty_var() => ty, + Ok(norm_ty) => norm_ty, + Err(err) => return ControlFlow::Break(OrphanCheckEarlyExit::NormalizationFailure(err)), + }; + + let result = match ty.kind() { + ty::Bool + | ty::Char + | ty::Int(..) + | ty::Uint(..) + | ty::Float(..) + | ty::Str + | ty::FnDef(..) + | ty::Pat(..) + | ty::FnPtr(_) + | ty::Array(..) + | ty::Slice(..) + | ty::RawPtr(..) + | ty::Never + | ty::Tuple(..) => self.found_non_local_ty(ty), + + ty::Param(..) => panic!("unexpected ty param"), + + ty::Placeholder(..) | ty::Bound(..) | ty::Infer(..) => { + match self.in_crate { + InCrate::Local { .. } => self.found_uncovered_ty_param(ty), + // The inference variable might be unified with a local + // type in that remote crate. + InCrate::Remote => ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)), + } + } + + // A rigid alias may normalize to anything. + // * If it references an infer var, placeholder or bound ty, it may + // normalize to that, so we have to treat it as an uncovered ty param. + // * Otherwise it may normalize to any non-type-generic type + // be it local or non-local. + ty::Alias(kind, _) => { + if ty.has_type_flags( + ty::TypeFlags::HAS_TY_PLACEHOLDER + | ty::TypeFlags::HAS_TY_BOUND + | ty::TypeFlags::HAS_TY_INFER, + ) { + match self.in_crate { + InCrate::Local { mode } => match kind { + ty::Projection => { + if let OrphanCheckMode::Compat = mode { + ControlFlow::Continue(()) + } else { + self.found_uncovered_ty_param(ty) + } + } + _ => self.found_uncovered_ty_param(ty), + }, + InCrate::Remote => { + // The inference variable might be unified with a local + // type in that remote crate. + ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)) + } + } + } else { + // Regarding *opaque types* specifically, we choose to treat them as non-local, + // even those that appear within the same crate. This seems somewhat surprising + // at first, but makes sense when you consider that opaque types are supposed + // to hide the underlying type *within the same crate*. When an opaque type is + // used from outside the module where it is declared, it should be impossible to + // observe anything about it other than the traits that it implements. + // + // The alternative would be to look at the underlying type to determine whether + // or not the opaque type itself should be considered local. + // + // However, this could make it a breaking change to switch the underlying hidden + // type from a local type to a remote type. This would violate the rule that + // opaque types should be completely opaque apart from the traits that they + // implement, so we don't use this behavior. + // Addendum: Moreover, revealing the underlying type is likely to cause cycle + // errors as we rely on coherence / the specialization graph during typeck. + + self.found_non_local_ty(ty) + } + } + + // For fundamental types, we just look inside of them. + ty::Ref(_, ty, _) => ty.visit_with(self), + ty::Adt(def, args) => { + if self.def_id_is_local(def.def_id()) { + ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)) + } else if def.is_fundamental() { + args.visit_with(self) + } else { + self.found_non_local_ty(ty) + } + } + ty::Foreign(def_id) => { + if self.def_id_is_local(def_id) { + ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)) + } else { + self.found_non_local_ty(ty) + } + } + ty::Dynamic(tt, ..) => { + let principal = tt.principal().map(|p| p.def_id()); + if principal.is_some_and(|p| self.def_id_is_local(p)) { + ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)) + } else { + self.found_non_local_ty(ty) + } + } + ty::Error(_) => ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)), + ty::Closure(did, ..) | ty::CoroutineClosure(did, ..) | ty::Coroutine(did, ..) => { + if self.def_id_is_local(did) { + ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)) + } else { + self.found_non_local_ty(ty) + } + } + // This should only be created when checking whether we have to check whether some + // auto trait impl applies. There will never be multiple impls, so we can just + // act as if it were a local type here. + ty::CoroutineWitness(..) => ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)), + }; + // A bit of a hack, the `OrphanChecker` is only used to visit a `TraitRef`, so + // the first type we visit is always the self type. + self.in_self_ty = false; + result + } + + /// All possible values for a constant parameter already exist + /// in the crate defining the trait, so they are always non-local[^1]. + /// + /// Because there's no way to have an impl where the first local + /// generic argument is a constant, we also don't have to fail + /// the orphan check when encountering a parameter or a generic constant. + /// + /// This means that we can completely ignore constants during the orphan check. + /// + /// See `tests/ui/coherence/const-generics-orphan-check-ok.rs` for examples. + /// + /// [^1]: This might not hold for function pointers or trait objects in the future. + /// As these should be quite rare as const arguments and especially rare as impl + /// parameters, allowing uncovered const parameters in impls seems more useful + /// than allowing `impl Trait for i32` to compile. + fn visit_const(&mut self, _c: I::Const) -> Self::Result { + ControlFlow::Continue(()) + } +} diff --git a/compiler/rustc_next_trait_solver/src/lib.rs b/compiler/rustc_next_trait_solver/src/lib.rs index 761475d3d6b08..0a5b427805865 100644 --- a/compiler/rustc_next_trait_solver/src/lib.rs +++ b/compiler/rustc_next_trait_solver/src/lib.rs @@ -5,6 +5,7 @@ //! So if you got to this crate from the old solver, it's totally normal. pub mod canonicalizer; +pub mod coherence; pub mod delegate; pub mod relate; pub mod resolve; diff --git a/compiler/rustc_trait_selection/src/lib.rs b/compiler/rustc_trait_selection/src/lib.rs index 50c618bb3bde5..37008baca2899 100644 --- a/compiler/rustc_trait_selection/src/lib.rs +++ b/compiler/rustc_trait_selection/src/lib.rs @@ -26,6 +26,7 @@ #![feature(never_type)] #![feature(rustdoc_internals)] #![feature(type_alias_impl_trait)] +#![feature(unwrap_infallible)] #![recursion_limit = "512"] // For rustdoc // tidy-alphabetical-end diff --git a/compiler/rustc_trait_selection/src/traits/coherence.rs b/compiler/rustc_trait_selection/src/traits/coherence.rs index 57ba6c33ac5a5..fc390bf318dc1 100644 --- a/compiler/rustc_trait_selection/src/traits/coherence.rs +++ b/compiler/rustc_trait_selection/src/traits/coherence.rs @@ -25,42 +25,14 @@ use rustc_middle::traits::specialization_graph::OverlapMode; use rustc_middle::ty::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_middle::ty::visit::{TypeSuperVisitable, TypeVisitable, TypeVisitableExt, TypeVisitor}; use rustc_middle::ty::{self, Ty, TyCtxt}; +pub use rustc_next_trait_solver::coherence::*; use rustc_span::symbol::sym; use rustc_span::{Span, DUMMY_SP}; use std::fmt::Debug; -use std::ops::ControlFlow; use super::error_reporting::suggest_new_overflow_limit; use super::ObligationCtxt; -/// Whether we do the orphan check relative to this crate or to some remote crate. -#[derive(Copy, Clone, Debug)] -pub enum InCrate { - Local { mode: OrphanCheckMode }, - Remote, -} - -#[derive(Copy, Clone, Debug)] -pub enum OrphanCheckMode { - /// Proper orphan check. - Proper, - /// Improper orphan check for backward compatibility. - /// - /// In this mode, type params inside projections are considered to be covered - /// even if the projection may normalize to a type that doesn't actually cover - /// them. This is unsound. See also [#124559] and [#99554]. - /// - /// [#124559]: https://github.com/rust-lang/rust/issues/124559 - /// [#99554]: https://github.com/rust-lang/rust/issues/99554 - Compat, -} - -#[derive(Debug, Copy, Clone)] -pub enum Conflict { - Upstream, - Downstream, -} - pub struct OverlapResult<'tcx> { pub impl_header: ty::ImplHeader<'tcx>, pub intercrate_ambiguity_causes: FxIndexSet>, @@ -612,426 +584,6 @@ fn try_prove_negated_where_clause<'tcx>( true } -/// Returns whether all impls which would apply to the `trait_ref` -/// e.g. `Ty: Trait` are already known in the local crate. -/// -/// This both checks whether any downstream or sibling crates could -/// implement it and whether an upstream crate can add this impl -/// without breaking backwards compatibility. -#[instrument(level = "debug", skip(infcx, lazily_normalize_ty), ret)] -pub fn trait_ref_is_knowable<'tcx, E: Debug>( - infcx: &InferCtxt<'tcx>, - trait_ref: ty::TraitRef<'tcx>, - mut lazily_normalize_ty: impl FnMut(Ty<'tcx>) -> Result, E>, -) -> Result, E> { - if orphan_check_trait_ref(infcx, trait_ref, InCrate::Remote, &mut lazily_normalize_ty)?.is_ok() - { - // A downstream or cousin crate is allowed to implement some - // generic parameters of this trait-ref. - return Ok(Err(Conflict::Downstream)); - } - - if trait_ref_is_local_or_fundamental(infcx.tcx, trait_ref) { - // This is a local or fundamental trait, so future-compatibility - // is no concern. We know that downstream/cousin crates are not - // allowed to implement a generic parameter of this trait ref, - // which means impls could only come from dependencies of this - // crate, which we already know about. - return Ok(Ok(())); - } - - // This is a remote non-fundamental trait, so if another crate - // can be the "final owner" of the generic parameters of this trait-ref, - // they are allowed to implement it future-compatibly. - // - // However, if we are a final owner, then nobody else can be, - // and if we are an intermediate owner, then we don't care - // about future-compatibility, which means that we're OK if - // we are an owner. - if orphan_check_trait_ref( - infcx, - trait_ref, - InCrate::Local { mode: OrphanCheckMode::Proper }, - &mut lazily_normalize_ty, - )? - .is_ok() - { - Ok(Ok(())) - } else { - Ok(Err(Conflict::Upstream)) - } -} - -pub fn trait_ref_is_local_or_fundamental<'tcx>( - tcx: TyCtxt<'tcx>, - trait_ref: ty::TraitRef<'tcx>, -) -> bool { - trait_ref.def_id.is_local() || tcx.trait_def(trait_ref.def_id).is_fundamental -} - -#[derive(Debug, Copy, Clone)] -pub enum IsFirstInputType { - No, - Yes, -} - -impl From for IsFirstInputType { - fn from(b: bool) -> IsFirstInputType { - match b { - false => IsFirstInputType::No, - true => IsFirstInputType::Yes, - } - } -} - -#[derive(Debug)] -pub enum OrphanCheckErr<'tcx, T> { - NonLocalInputType(Vec<(Ty<'tcx>, IsFirstInputType)>), - UncoveredTyParams(UncoveredTyParams<'tcx, T>), -} - -#[derive(Debug)] -pub struct UncoveredTyParams<'tcx, T> { - pub uncovered: T, - pub local_ty: Option>, -} - -/// Checks whether a trait-ref is potentially implementable by a crate. -/// -/// The current rule is that a trait-ref orphan checks in a crate C: -/// -/// 1. Order the parameters in the trait-ref in generic parameters order -/// - Self first, others linearly (e.g., `>` is U < V < W). -/// 2. Of these type parameters, there is at least one type parameter -/// in which, walking the type as a tree, you can reach a type local -/// to C where all types in-between are fundamental types. Call the -/// first such parameter the "local key parameter". -/// - e.g., `Box` is OK, because you can visit LocalType -/// going through `Box`, which is fundamental. -/// - similarly, `FundamentalPair, Box>` is OK for -/// the same reason. -/// - but (knowing that `Vec` is non-fundamental, and assuming it's -/// not local), `Vec` is bad, because `Vec<->` is between -/// the local type and the type parameter. -/// 3. Before this local type, no generic type parameter of the impl must -/// be reachable through fundamental types. -/// - e.g. `impl Trait for Vec` is fine, as `Vec` is not fundamental. -/// - while `impl Trait for Box` results in an error, as `T` is -/// reachable through the fundamental type `Box`. -/// 4. Every type in the local key parameter not known in C, going -/// through the parameter's type tree, must appear only as a subtree of -/// a type local to C, with only fundamental types between the type -/// local to C and the local key parameter. -/// - e.g., `Vec>>` (or equivalently `Box>>`) -/// is bad, because the only local type with `T` as a subtree is -/// `LocalType`, and `Vec<->` is between it and the type parameter. -/// - similarly, `FundamentalPair, T>` is bad, because -/// the second occurrence of `T` is not a subtree of *any* local type. -/// - however, `LocalType>` is OK, because `T` is a subtree of -/// `LocalType>`, which is local and has no types between it and -/// the type parameter. -/// -/// The orphan rules actually serve several different purposes: -/// -/// 1. They enable link-safety - i.e., 2 mutually-unknowing crates (where -/// every type local to one crate is unknown in the other) can't implement -/// the same trait-ref. This follows because it can be seen that no such -/// type can orphan-check in 2 such crates. -/// -/// To check that a local impl follows the orphan rules, we check it in -/// InCrate::Local mode, using type parameters for the "generic" types. -/// -/// In InCrate::Local mode the orphan check succeeds if the current crate -/// is definitely allowed to implement the given trait (no false positives). -/// -/// 2. They ground negative reasoning for coherence. If a user wants to -/// write both a conditional blanket impl and a specific impl, we need to -/// make sure they do not overlap. For example, if we write -/// ```ignore (illustrative) -/// impl IntoIterator for Vec -/// impl IntoIterator for T -/// ``` -/// We need to be able to prove that `Vec<$0>: !Iterator` for every type $0. -/// We can observe that this holds in the current crate, but we need to make -/// sure this will also hold in all unknown crates (both "independent" crates, -/// which we need for link-safety, and also child crates, because we don't want -/// child crates to get error for impl conflicts in a *dependency*). -/// -/// For that, we only allow negative reasoning if, for every assignment to the -/// inference variables, every unknown crate would get an orphan error if they -/// try to implement this trait-ref. To check for this, we use InCrate::Remote -/// mode. That is sound because we already know all the impls from known crates. -/// -/// In InCrate::Remote mode the orphan check succeeds if a foreign crate -/// *could* implement the given trait (no false negatives). -/// -/// 3. For non-`#[fundamental]` traits, they guarantee that parent crates can -/// add "non-blanket" impls without breaking negative reasoning in dependent -/// crates. This is the "rebalancing coherence" (RFC 1023) restriction. -/// -/// For that, we only allow a crate to perform negative reasoning on -/// non-local-non-`#[fundamental]` if there's a local key parameter as per (2). -/// -/// Because we never perform negative reasoning generically (coherence does -/// not involve type parameters), this can be interpreted as doing the full -/// orphan check (using InCrate::Local mode), instantiating non-local known -/// types for all inference variables. -/// -/// This allows for crates to future-compatibly add impls as long as they -/// can't apply to types with a key parameter in a child crate - applying -/// the rules, this basically means that every type parameter in the impl -/// must appear behind a non-fundamental type (because this is not a -/// type-system requirement, crate owners might also go for "semantic -/// future-compatibility" involving things such as sealed traits, but -/// the above requirement is sufficient, and is necessary in "open world" -/// cases). -/// -/// Note that this function is never called for types that have both type -/// parameters and inference variables. -#[instrument(level = "trace", skip(infcx, lazily_normalize_ty), ret)] -pub fn orphan_check_trait_ref<'tcx, E: Debug>( - infcx: &InferCtxt<'tcx>, - trait_ref: ty::TraitRef<'tcx>, - in_crate: InCrate, - lazily_normalize_ty: impl FnMut(Ty<'tcx>) -> Result, E>, -) -> Result>>, E> { - if trait_ref.has_param() { - bug!("orphan check only expects inference variables: {trait_ref:?}"); - } - - let mut checker = OrphanChecker::new(infcx, in_crate, lazily_normalize_ty); - Ok(match trait_ref.visit_with(&mut checker) { - ControlFlow::Continue(()) => Err(OrphanCheckErr::NonLocalInputType(checker.non_local_tys)), - ControlFlow::Break(residual) => match residual { - OrphanCheckEarlyExit::NormalizationFailure(err) => return Err(err), - OrphanCheckEarlyExit::UncoveredTyParam(ty) => { - // Does there exist some local type after the `ParamTy`. - checker.search_first_local_ty = true; - let local_ty = match trait_ref.visit_with(&mut checker).break_value() { - Some(OrphanCheckEarlyExit::LocalTy(local_ty)) => Some(local_ty), - _ => None, - }; - Err(OrphanCheckErr::UncoveredTyParams(UncoveredTyParams { - uncovered: ty, - local_ty, - })) - } - OrphanCheckEarlyExit::LocalTy(_) => Ok(()), - }, - }) -} - -struct OrphanChecker<'a, 'tcx, F> { - infcx: &'a InferCtxt<'tcx>, - in_crate: InCrate, - in_self_ty: bool, - lazily_normalize_ty: F, - /// Ignore orphan check failures and exclusively search for the first local type. - search_first_local_ty: bool, - non_local_tys: Vec<(Ty<'tcx>, IsFirstInputType)>, -} - -impl<'a, 'tcx, F, E> OrphanChecker<'a, 'tcx, F> -where - F: FnOnce(Ty<'tcx>) -> Result, E>, -{ - fn new(infcx: &'a InferCtxt<'tcx>, in_crate: InCrate, lazily_normalize_ty: F) -> Self { - OrphanChecker { - infcx, - in_crate, - in_self_ty: true, - lazily_normalize_ty, - search_first_local_ty: false, - non_local_tys: Vec::new(), - } - } - - fn found_non_local_ty(&mut self, t: Ty<'tcx>) -> ControlFlow> { - self.non_local_tys.push((t, self.in_self_ty.into())); - ControlFlow::Continue(()) - } - - fn found_uncovered_ty_param( - &mut self, - ty: Ty<'tcx>, - ) -> ControlFlow> { - if self.search_first_local_ty { - return ControlFlow::Continue(()); - } - - ControlFlow::Break(OrphanCheckEarlyExit::UncoveredTyParam(ty)) - } - - fn def_id_is_local(&mut self, def_id: DefId) -> bool { - match self.in_crate { - InCrate::Local { .. } => def_id.is_local(), - InCrate::Remote => false, - } - } -} - -enum OrphanCheckEarlyExit<'tcx, E> { - NormalizationFailure(E), - UncoveredTyParam(Ty<'tcx>), - LocalTy(Ty<'tcx>), -} - -impl<'a, 'tcx, F, E> TypeVisitor> for OrphanChecker<'a, 'tcx, F> -where - F: FnMut(Ty<'tcx>) -> Result, E>, -{ - type Result = ControlFlow>; - - fn visit_region(&mut self, _r: ty::Region<'tcx>) -> Self::Result { - ControlFlow::Continue(()) - } - - fn visit_ty(&mut self, ty: Ty<'tcx>) -> Self::Result { - let ty = self.infcx.shallow_resolve(ty); - let ty = match (self.lazily_normalize_ty)(ty) { - Ok(norm_ty) if norm_ty.is_ty_var() => ty, - Ok(norm_ty) => norm_ty, - Err(err) => return ControlFlow::Break(OrphanCheckEarlyExit::NormalizationFailure(err)), - }; - - let result = match *ty.kind() { - ty::Bool - | ty::Char - | ty::Int(..) - | ty::Uint(..) - | ty::Float(..) - | ty::Str - | ty::FnDef(..) - | ty::Pat(..) - | ty::FnPtr(_) - | ty::Array(..) - | ty::Slice(..) - | ty::RawPtr(..) - | ty::Never - | ty::Tuple(..) => self.found_non_local_ty(ty), - - ty::Param(..) => bug!("unexpected ty param"), - - ty::Placeholder(..) | ty::Bound(..) | ty::Infer(..) => { - match self.in_crate { - InCrate::Local { .. } => self.found_uncovered_ty_param(ty), - // The inference variable might be unified with a local - // type in that remote crate. - InCrate::Remote => ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)), - } - } - - // A rigid alias may normalize to anything. - // * If it references an infer var, placeholder or bound ty, it may - // normalize to that, so we have to treat it as an uncovered ty param. - // * Otherwise it may normalize to any non-type-generic type - // be it local or non-local. - ty::Alias(kind, _) => { - if ty.has_type_flags( - ty::TypeFlags::HAS_TY_PLACEHOLDER - | ty::TypeFlags::HAS_TY_BOUND - | ty::TypeFlags::HAS_TY_INFER, - ) { - match self.in_crate { - InCrate::Local { mode } => match kind { - ty::Projection if let OrphanCheckMode::Compat = mode => { - ControlFlow::Continue(()) - } - _ => self.found_uncovered_ty_param(ty), - }, - InCrate::Remote => { - // The inference variable might be unified with a local - // type in that remote crate. - ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)) - } - } - } else { - // Regarding *opaque types* specifically, we choose to treat them as non-local, - // even those that appear within the same crate. This seems somewhat surprising - // at first, but makes sense when you consider that opaque types are supposed - // to hide the underlying type *within the same crate*. When an opaque type is - // used from outside the module where it is declared, it should be impossible to - // observe anything about it other than the traits that it implements. - // - // The alternative would be to look at the underlying type to determine whether - // or not the opaque type itself should be considered local. - // - // However, this could make it a breaking change to switch the underlying hidden - // type from a local type to a remote type. This would violate the rule that - // opaque types should be completely opaque apart from the traits that they - // implement, so we don't use this behavior. - // Addendum: Moreover, revealing the underlying type is likely to cause cycle - // errors as we rely on coherence / the specialization graph during typeck. - - self.found_non_local_ty(ty) - } - } - - // For fundamental types, we just look inside of them. - ty::Ref(_, ty, _) => ty.visit_with(self), - ty::Adt(def, args) => { - if self.def_id_is_local(def.did()) { - ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)) - } else if def.is_fundamental() { - args.visit_with(self) - } else { - self.found_non_local_ty(ty) - } - } - ty::Foreign(def_id) => { - if self.def_id_is_local(def_id) { - ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)) - } else { - self.found_non_local_ty(ty) - } - } - ty::Dynamic(tt, ..) => { - let principal = tt.principal().map(|p| p.def_id()); - if principal.is_some_and(|p| self.def_id_is_local(p)) { - ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)) - } else { - self.found_non_local_ty(ty) - } - } - ty::Error(_) => ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)), - ty::Closure(did, ..) | ty::CoroutineClosure(did, ..) | ty::Coroutine(did, ..) => { - if self.def_id_is_local(did) { - ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)) - } else { - self.found_non_local_ty(ty) - } - } - // This should only be created when checking whether we have to check whether some - // auto trait impl applies. There will never be multiple impls, so we can just - // act as if it were a local type here. - ty::CoroutineWitness(..) => ControlFlow::Break(OrphanCheckEarlyExit::LocalTy(ty)), - }; - // A bit of a hack, the `OrphanChecker` is only used to visit a `TraitRef`, so - // the first type we visit is always the self type. - self.in_self_ty = false; - result - } - - /// All possible values for a constant parameter already exist - /// in the crate defining the trait, so they are always non-local[^1]. - /// - /// Because there's no way to have an impl where the first local - /// generic argument is a constant, we also don't have to fail - /// the orphan check when encountering a parameter or a generic constant. - /// - /// This means that we can completely ignore constants during the orphan check. - /// - /// See `tests/ui/coherence/const-generics-orphan-check-ok.rs` for examples. - /// - /// [^1]: This might not hold for function pointers or trait objects in the future. - /// As these should be quite rare as const arguments and especially rare as impl - /// parameters, allowing uncovered const parameters in impls seems more useful - /// than allowing `impl Trait for i32` to compile. - fn visit_const(&mut self, _c: ty::Const<'tcx>) -> Self::Result { - ControlFlow::Continue(()) - } -} - /// Compute the `intercrate_ambiguity_causes` for the new solver using /// "proof trees". /// diff --git a/compiler/rustc_trait_selection/src/traits/select/mod.rs b/compiler/rustc_trait_selection/src/traits/select/mod.rs index 68cc04bc8e63d..460c4c3cbb3cb 100644 --- a/compiler/rustc_trait_selection/src/traits/select/mod.rs +++ b/compiler/rustc_trait_selection/src/traits/select/mod.rs @@ -1523,7 +1523,7 @@ impl<'cx, 'tcx> SelectionContext<'cx, 'tcx> { // bound regions. let trait_ref = predicate.skip_binder().trait_ref; - coherence::trait_ref_is_knowable::(self.infcx, trait_ref, |ty| Ok(ty)).unwrap() + coherence::trait_ref_is_knowable(self.infcx, trait_ref, |ty| Ok::<_, !>(ty)).into_ok() } /// Returns `true` if the global caches can be used. diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index 2c80ee0a73d4a..a1070d985ab97 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -73,6 +73,11 @@ pub trait InferCtxtLike { rhs: T, ) -> Result::Predicate>>, NoSolution>; + fn shallow_resolve( + &self, + ty: ::Ty, + ) -> ::Ty; + fn resolve_vars_if_possible(&self, value: T) -> T where T: TypeFoldable; diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index 68c2575258d2f..97414473737f4 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -514,6 +514,8 @@ pub trait AdtDef: Copy + Debug + Hash + Eq { fn all_field_tys(self, interner: I) -> ty::EarlyBinder>; fn sized_constraint(self, interner: I) -> Option>; + + fn is_fundamental(self) -> bool; } pub trait ParamEnv: Copy + Debug + Hash + Eq + TypeFoldable { @@ -558,6 +560,8 @@ pub trait EvaluationCache { } pub trait DefId: Copy + Debug + Hash + Eq + TypeFoldable { + fn is_local(self) -> bool; + fn as_local(self) -> Option; } diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 6e013768c3ea8..8386c30716e06 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -246,6 +246,8 @@ pub trait Interner: fn trait_is_object_safe(self, trait_def_id: Self::DefId) -> bool; + fn trait_is_fundamental(self, def_id: Self::DefId) -> bool; + fn trait_may_be_implemented_via_object(self, trait_def_id: Self::DefId) -> bool; fn supertrait_def_ids(self, trait_def_id: Self::DefId) From ab27c2fa771f589f333eb798b892c506e1f436fa Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 6 Jul 2024 18:32:06 -0400 Subject: [PATCH 14/32] Get rid of trait_ref_is_knowable from delegate --- compiler/rustc_next_trait_solver/src/delegate.rs | 9 --------- .../rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs | 4 +++- compiler/rustc_trait_selection/src/solve/delegate.rs | 10 ---------- compiler/rustc_type_ir/src/infer_ctxt.rs | 2 +- 4 files changed, 4 insertions(+), 21 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/delegate.rs b/compiler/rustc_next_trait_solver/src/delegate.rs index 31032dc679ac3..21c976dec5276 100644 --- a/compiler/rustc_next_trait_solver/src/delegate.rs +++ b/compiler/rustc_next_trait_solver/src/delegate.rs @@ -1,4 +1,3 @@ -use std::fmt::Debug; use std::ops::Deref; use rustc_type_ir::fold::TypeFoldable; @@ -99,14 +98,6 @@ pub trait SolverDelegate: fn reset_opaque_types(&self); - fn trait_ref_is_knowable( - &self, - trait_ref: ty::TraitRef, - lazily_normalize_ty: impl FnMut( - ::Ty, - ) -> Result<::Ty, E>, - ) -> Result; - fn fetch_eligible_assoc_item( &self, param_env: ::ParamEnv, diff --git a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs index d8a3acc655a45..c90f8e761633b 100644 --- a/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/eval_ctxt/mod.rs @@ -11,6 +11,7 @@ use rustc_type_ir::{self as ty, CanonicalVarValues, InferCtxtLike, Interner}; use rustc_type_ir_macros::{Lift_Generic, TypeFoldable_Generic, TypeVisitable_Generic}; use tracing::{instrument, trace}; +use crate::coherence; use crate::delegate::SolverDelegate; use crate::solve::inspect::{self, ProofTreeBuilder}; use crate::solve::search_graph::SearchGraph; @@ -906,7 +907,8 @@ where ) -> Result { let delegate = self.delegate; let lazily_normalize_ty = |ty| self.structurally_normalize_ty(param_env, ty); - delegate.trait_ref_is_knowable(trait_ref, lazily_normalize_ty) + coherence::trait_ref_is_knowable(&**delegate, trait_ref, lazily_normalize_ty) + .map(|is_knowable| is_knowable.is_ok()) } pub(super) fn fetch_eligible_assoc_item( diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index f98744e906f05..5fad7129c6427 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -15,7 +15,6 @@ use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt as _}; use rustc_span::{ErrorGuaranteed, Span, DUMMY_SP}; use rustc_type_ir::solve::{Certainty, NoSolution, SolverMode}; -use crate::traits::coherence::trait_ref_is_knowable; use crate::traits::specialization_graph; #[repr(transparent)] @@ -200,15 +199,6 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< let _ = self.take_opaque_types(); } - fn trait_ref_is_knowable( - &self, - trait_ref: ty::TraitRef<'tcx>, - lazily_normalize_ty: impl FnMut(Ty<'tcx>) -> Result, E>, - ) -> Result { - trait_ref_is_knowable(&self.0, trait_ref, lazily_normalize_ty) - .map(|is_knowable| is_knowable.is_ok()) - } - fn fetch_eligible_assoc_item( &self, param_env: ty::ParamEnv<'tcx>, diff --git a/compiler/rustc_type_ir/src/infer_ctxt.rs b/compiler/rustc_type_ir/src/infer_ctxt.rs index a1070d985ab97..1d0b2345b805f 100644 --- a/compiler/rustc_type_ir/src/infer_ctxt.rs +++ b/compiler/rustc_type_ir/src/infer_ctxt.rs @@ -3,7 +3,7 @@ use crate::relate::Relate; use crate::solve::{Goal, NoSolution, SolverMode}; use crate::{self as ty, Interner}; -pub trait InferCtxtLike { +pub trait InferCtxtLike: Sized { type Interner: Interner; fn cx(&self) -> Self::Interner; From 484152d562f6babaacb3fae08cc5f70ee550e9ee Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Thu, 15 Feb 2024 19:54:37 +0000 Subject: [PATCH 15/32] Support tail calls in mir via `TerminatorKind::TailCall` --- compiler/rustc_borrowck/src/lib.rs | 9 ++- .../src/polonius/loan_invalidations.rs | 6 ++ compiler/rustc_borrowck/src/type_check/mod.rs | 20 ++++- compiler/rustc_codegen_cranelift/src/base.rs | 5 ++ .../rustc_codegen_cranelift/src/constant.rs | 1 + compiler/rustc_codegen_ssa/src/mir/analyze.rs | 1 + compiler/rustc_codegen_ssa/src/mir/block.rs | 7 ++ .../src/check_consts/check.rs | 15 +++- .../src/check_consts/post_drop_elaboration.rs | 1 + .../src/interpret/terminator.rs | 2 + compiler/rustc_middle/src/mir/pretty.rs | 17 +++- compiler/rustc_middle/src/mir/syntax.rs | 31 ++++++++ compiler/rustc_middle/src/mir/terminator.rs | 13 +++- compiler/rustc_middle/src/mir/visit.rs | 11 +++ .../rustc_mir_build/src/build/expr/stmt.rs | 35 ++++++++- compiler/rustc_mir_build/src/build/scope.rs | 1 + compiler/rustc_mir_build/src/lints.rs | 2 + .../src/impls/borrowed_locals.rs | 1 + .../src/impls/storage_liveness.rs | 2 + .../src/move_paths/builder.rs | 6 ++ .../rustc_mir_dataflow/src/value_analysis.rs | 3 + compiler/rustc_mir_transform/src/coroutine.rs | 5 ++ .../rustc_mir_transform/src/coverage/graph.rs | 9 ++- .../src/coverage/spans/from_mir.rs | 3 +- compiler/rustc_mir_transform/src/dest_prop.rs | 6 ++ compiler/rustc_mir_transform/src/inline.rs | 9 +++ .../rustc_mir_transform/src/jump_threading.rs | 1 + .../src/known_panics_lint.rs | 1 + .../src/mentioned_items.rs | 2 +- .../src/remove_noop_landing_pads.rs | 1 + compiler/rustc_mir_transform/src/validate.rs | 77 +++++++++++-------- compiler/rustc_monomorphize/src/collector.rs | 3 +- .../rustc_smir/src/rustc_smir/convert/mir.rs | 1 + tests/ui/explicit-tail-calls/constck.rs | 22 ++++++ tests/ui/explicit-tail-calls/constck.stderr | 19 +++++ .../explicit-tail-calls/return-mismatches.rs | 2 +- .../return-mismatches.stderr | 13 +--- tests/ui/explicit-tail-calls/unsafeck.rs | 11 +++ tests/ui/explicit-tail-calls/unsafeck.stderr | 11 +++ tests/ui/parser/bad-let-else-statement.rs | 16 ++-- tests/ui/parser/bad-let-else-statement.stderr | 15 +--- 41 files changed, 328 insertions(+), 88 deletions(-) create mode 100644 tests/ui/explicit-tail-calls/constck.rs create mode 100644 tests/ui/explicit-tail-calls/constck.stderr create mode 100644 tests/ui/explicit-tail-calls/unsafeck.rs create mode 100644 tests/ui/explicit-tail-calls/unsafeck.stderr diff --git a/compiler/rustc_borrowck/src/lib.rs b/compiler/rustc_borrowck/src/lib.rs index e6f8ffd428d06..df532c739509e 100644 --- a/compiler/rustc_borrowck/src/lib.rs +++ b/compiler/rustc_borrowck/src/lib.rs @@ -728,6 +728,12 @@ impl<'a, 'mir, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'mir, 'tcx, R> } self.mutate_place(loc, (*destination, span), Deep, flow_state); } + TerminatorKind::TailCall { func, args, fn_span: _ } => { + self.consume_operand(loc, (func, span), flow_state); + for arg in args { + self.consume_operand(loc, (&arg.node, arg.span), flow_state); + } + } TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => { self.consume_operand(loc, (cond, span), flow_state); if let AssertKind::BoundsCheck { len, index } = &**msg { @@ -814,9 +820,8 @@ impl<'a, 'mir, 'tcx, R> rustc_mir_dataflow::ResultsVisitor<'mir, 'tcx, R> TerminatorKind::UnwindResume | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::CoroutineDrop => { - // Returning from the function implicitly kills storage for all locals and statics. - // Often, the storage will already have been killed by an explicit // StorageDead, but we don't always emit those (notably on unwind paths), // so this "extra check" serves as a kind of backup. let borrow_set = self.borrow_set.clone(); diff --git a/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs b/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs index 6979910a02d73..30dfc4c21b06a 100644 --- a/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs +++ b/compiler/rustc_borrowck/src/polonius/loan_invalidations.rs @@ -125,6 +125,12 @@ impl<'cx, 'tcx> Visitor<'tcx> for LoanInvalidationsGenerator<'cx, 'tcx> { } self.mutate_place(location, *destination, Deep); } + TerminatorKind::TailCall { func, args, .. } => { + self.consume_operand(location, func); + for arg in args { + self.consume_operand(location, &arg.node); + } + } TerminatorKind::Assert { cond, expected: _, msg, target: _, unwind: _ } => { self.consume_operand(location, cond); use rustc_middle::mir::AssertKind; diff --git a/compiler/rustc_borrowck/src/type_check/mod.rs b/compiler/rustc_borrowck/src/type_check/mod.rs index aa25e3adf28af..8bba7ef425520 100644 --- a/compiler/rustc_borrowck/src/type_check/mod.rs +++ b/compiler/rustc_borrowck/src/type_check/mod.rs @@ -1352,7 +1352,14 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { } // FIXME: check the values } - TerminatorKind::Call { func, args, destination, call_source, target, .. } => { + TerminatorKind::Call { func, args, .. } + | TerminatorKind::TailCall { func, args, .. } => { + let call_source = match term.kind { + TerminatorKind::Call { call_source, .. } => call_source, + TerminatorKind::TailCall { .. } => CallSource::Normal, + _ => unreachable!(), + }; + self.check_operand(func, term_location); for arg in args { self.check_operand(&arg.node, term_location); @@ -1425,7 +1432,9 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { ); } - self.check_call_dest(body, term, &sig, *destination, *target, term_location); + if let TerminatorKind::Call { destination, target, .. } = term.kind { + self.check_call_dest(body, term, &sig, destination, target, term_location); + } // The ordinary liveness rules will ensure that all // regions in the type of the callee are live here. We @@ -1443,7 +1452,7 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { .add_location(region_vid, term_location); } - self.check_call_inputs(body, term, func, &sig, args, term_location, *call_source); + self.check_call_inputs(body, term, func, &sig, args, term_location, call_source); } TerminatorKind::Assert { cond, msg, .. } => { self.check_operand(cond, term_location); @@ -1675,6 +1684,11 @@ impl<'a, 'tcx> TypeChecker<'a, 'tcx> { span_mirbug!(self, block_data, "return on cleanup block") } } + TerminatorKind::TailCall { .. } => { + if is_cleanup { + span_mirbug!(self, block_data, "tailcall on cleanup block") + } + } TerminatorKind::CoroutineDrop { .. } => { if is_cleanup { span_mirbug!(self, block_data, "coroutine_drop in cleanup block") diff --git a/compiler/rustc_codegen_cranelift/src/base.rs b/compiler/rustc_codegen_cranelift/src/base.rs index c5b4277015a9e..5adbbb09ac85f 100644 --- a/compiler/rustc_codegen_cranelift/src/base.rs +++ b/compiler/rustc_codegen_cranelift/src/base.rs @@ -491,6 +491,11 @@ fn codegen_fn_body(fx: &mut FunctionCx<'_, '_, '_>, start_block: Block) { ) }); } + // FIXME(explicit_tail_calls): add support for tail calls to the cranelift backend, once cranelift supports tail calls + TerminatorKind::TailCall { fn_span, .. } => span_bug!( + *fn_span, + "tail calls are not yet supported in `rustc_codegen_cranelift` backend" + ), TerminatorKind::InlineAsm { template, operands, diff --git a/compiler/rustc_codegen_cranelift/src/constant.rs b/compiler/rustc_codegen_cranelift/src/constant.rs index 87c5da3b7c3ed..fc12f0ff738ba 100644 --- a/compiler/rustc_codegen_cranelift/src/constant.rs +++ b/compiler/rustc_codegen_cranelift/src/constant.rs @@ -565,6 +565,7 @@ pub(crate) fn mir_operand_get_const_val<'tcx>( { return None; } + TerminatorKind::TailCall { .. } => return None, TerminatorKind::Call { .. } => {} } } diff --git a/compiler/rustc_codegen_ssa/src/mir/analyze.rs b/compiler/rustc_codegen_ssa/src/mir/analyze.rs index 0577ba32ffdcc..ac2b6ca4e9520 100644 --- a/compiler/rustc_codegen_ssa/src/mir/analyze.rs +++ b/compiler/rustc_codegen_ssa/src/mir/analyze.rs @@ -281,6 +281,7 @@ pub fn cleanup_kinds(mir: &mir::Body<'_>) -> IndexVec> FunctionCx<'a, 'tcx, Bx> { fn_span, mergeable_succ(), ), + mir::TerminatorKind::TailCall { .. } => { + // FIXME(explicit_tail_calls): implement tail calls in ssa backend + span_bug!( + terminator.source_info.span, + "`TailCall` terminator is not yet supported by `rustc_codegen_ssa`" + ) + } mir::TerminatorKind::CoroutineDrop | mir::TerminatorKind::Yield { .. } => { bug!("coroutine ops in codegen") } diff --git a/compiler/rustc_const_eval/src/check_consts/check.rs b/compiler/rustc_const_eval/src/check_consts/check.rs index 0d8a17775dd3d..412effba32dc6 100644 --- a/compiler/rustc_const_eval/src/check_consts/check.rs +++ b/compiler/rustc_const_eval/src/check_consts/check.rs @@ -135,6 +135,8 @@ impl<'mir, 'tcx> Qualifs<'mir, 'tcx> { ccx: &'mir ConstCx<'mir, 'tcx>, tainted_by_errors: Option, ) -> ConstQualifs { + // FIXME(explicit_tail_calls): uhhhh I think we can return without return now, does it change anything + // Find the `Return` terminator if one exists. // // If no `Return` terminator exists, this MIR is divergent. Just return the conservative @@ -711,7 +713,14 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { self.super_terminator(terminator, location); match &terminator.kind { - TerminatorKind::Call { func, args, fn_span, call_source, .. } => { + TerminatorKind::Call { func, args, fn_span, .. } + | TerminatorKind::TailCall { func, args, fn_span, .. } => { + let call_source = match terminator.kind { + TerminatorKind::Call { call_source, .. } => call_source, + TerminatorKind::TailCall { .. } => CallSource::Normal, + _ => unreachable!(), + }; + let ConstCx { tcx, body, param_env, .. } = *self.ccx; let caller = self.def_id(); @@ -783,7 +792,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { callee, args: fn_args, span: *fn_span, - call_source: *call_source, + call_source, feature: Some(if tcx.features().const_trait_impl { sym::effects } else { @@ -830,7 +839,7 @@ impl<'tcx> Visitor<'tcx> for Checker<'_, 'tcx> { callee, args: fn_args, span: *fn_span, - call_source: *call_source, + call_source, feature: None, }); return; diff --git a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs index f0763f1e490ff..f5e745454ab84 100644 --- a/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs +++ b/compiler/rustc_const_eval/src/check_consts/post_drop_elaboration.rs @@ -108,6 +108,7 @@ impl<'tcx> Visitor<'tcx> for CheckLiveDrops<'_, 'tcx> { mir::TerminatorKind::UnwindTerminate(_) | mir::TerminatorKind::Call { .. } + | mir::TerminatorKind::TailCall { .. } | mir::TerminatorKind::Assert { .. } | mir::TerminatorKind::FalseEdge { .. } | mir::TerminatorKind::FalseUnwind { .. } diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 68acddf63d86e..7d1a48d6ded6f 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -172,6 +172,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } + TailCall { func: _, args: _, fn_span: _ } => todo!(), + Drop { place, target, unwind, replace: _ } => { let place = self.eval_place(place)?; let instance = Instance::resolve_drop_in_place(*self.tcx, place.layout.ty); diff --git a/compiler/rustc_middle/src/mir/pretty.rs b/compiler/rustc_middle/src/mir/pretty.rs index 4657f4dcf8132..23251d7b7bf61 100644 --- a/compiler/rustc_middle/src/mir/pretty.rs +++ b/compiler/rustc_middle/src/mir/pretty.rs @@ -845,6 +845,16 @@ impl<'tcx> TerminatorKind<'tcx> { } write!(fmt, ")") } + TailCall { func, args, .. } => { + write!(fmt, "tailcall {func:?}(")?; + for (index, arg) in args.iter().enumerate() { + if index > 0 { + write!(fmt, ", ")?; + } + write!(fmt, "{:?}", arg)?; + } + write!(fmt, ")") + } Assert { cond, expected, msg, .. } => { write!(fmt, "assert(")?; if !expected { @@ -912,7 +922,12 @@ impl<'tcx> TerminatorKind<'tcx> { pub fn fmt_successor_labels(&self) -> Vec> { use self::TerminatorKind::*; match *self { - Return | UnwindResume | UnwindTerminate(_) | Unreachable | CoroutineDrop => vec![], + Return + | TailCall { .. } + | UnwindResume + | UnwindTerminate(_) + | Unreachable + | CoroutineDrop => vec![], Goto { .. } => vec!["".into()], SwitchInt { ref targets, .. } => targets .values diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index 2c2884f189743..deaa1259f6b73 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -744,6 +744,36 @@ pub enum TerminatorKind<'tcx> { fn_span: Span, }, + /// Tail call. + /// + /// Roughly speaking this is a chimera of [`Call`] and [`Return`], with some caveats. + /// Semantically tail calls consists of two actions: + /// - pop of the current stack frame + /// - a call to the `func`, with the return address of the **current** caller + /// - so that a `return` inside `func` returns to the caller of the caller + /// of the function that is currently being executed + /// + /// Note that in difference with [`Call`] this is missing + /// - `destination` (because it's always the return place) + /// - `target` (because it's always taken from the current stack frame) + /// - `unwind` (because it's always taken from the current stack frame) + /// + /// [`Call`]: TerminatorKind::Call + /// [`Return`]: TerminatorKind::Return + TailCall { + /// The function that’s being called. + func: Operand<'tcx>, + /// Arguments the function is called with. + /// These are owned by the callee, which is free to modify them. + /// This allows the memory occupied by "by-value" arguments to be + /// reused across function calls without duplicating the contents. + args: Vec>>, + // FIXME(explicit_tail_calls): should we have the span for `become`? is this span accurate? do we need it? + /// This `Span` is the span of the function, without the dot and receiver + /// (e.g. `foo(a, b)` in `x.foo(a, b)` + fn_span: Span, + }, + /// Evaluates the operand, which must have type `bool`. If it is not equal to `expected`, /// initiates a panic. Initiating a panic corresponds to a `Call` terminator with some /// unspecified constant as the function to call, all the operands stored in the `AssertMessage` @@ -870,6 +900,7 @@ impl TerminatorKind<'_> { TerminatorKind::Unreachable => "Unreachable", TerminatorKind::Drop { .. } => "Drop", TerminatorKind::Call { .. } => "Call", + TerminatorKind::TailCall { .. } => "TailCall", TerminatorKind::Assert { .. } => "Assert", TerminatorKind::Yield { .. } => "Yield", TerminatorKind::CoroutineDrop => "CoroutineDrop", diff --git a/compiler/rustc_middle/src/mir/terminator.rs b/compiler/rustc_middle/src/mir/terminator.rs index ed592612358be..5b035d9579d76 100644 --- a/compiler/rustc_middle/src/mir/terminator.rs +++ b/compiler/rustc_middle/src/mir/terminator.rs @@ -439,6 +439,7 @@ mod helper { | CoroutineDrop | Return | Unreachable + | TailCall { .. } | Call { target: None, unwind: _, .. } => (&[]).into_iter().copied().chain(None), InlineAsm { ref targets, unwind: UnwindAction::Cleanup(u), .. } => { targets.iter().copied().chain(Some(u)) @@ -479,6 +480,7 @@ mod helper { | CoroutineDrop | Return | Unreachable + | TailCall { .. } | Call { target: None, unwind: _, .. } => (&mut []).into_iter().chain(None), InlineAsm { ref mut targets, unwind: UnwindAction::Cleanup(ref mut u), .. } => { targets.iter_mut().chain(Some(u)) @@ -501,6 +503,7 @@ impl<'tcx> TerminatorKind<'tcx> { | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::CoroutineDrop | TerminatorKind::Yield { .. } @@ -521,6 +524,7 @@ impl<'tcx> TerminatorKind<'tcx> { | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::CoroutineDrop | TerminatorKind::Yield { .. } @@ -606,9 +610,12 @@ impl<'tcx> TerminatorKind<'tcx> { pub fn edges(&self) -> TerminatorEdges<'_, 'tcx> { use TerminatorKind::*; match *self { - Return | UnwindResume | UnwindTerminate(_) | CoroutineDrop | Unreachable => { - TerminatorEdges::None - } + Return + | TailCall { .. } + | UnwindResume + | UnwindTerminate(_) + | CoroutineDrop + | Unreachable => TerminatorEdges::None, Goto { target } => TerminatorEdges::Single(target), diff --git a/compiler/rustc_middle/src/mir/visit.rs b/compiler/rustc_middle/src/mir/visit.rs index 7628a1ed2fe2b..0d3c419748b7a 100644 --- a/compiler/rustc_middle/src/mir/visit.rs +++ b/compiler/rustc_middle/src/mir/visit.rs @@ -540,6 +540,17 @@ macro_rules! make_mir_visitor { ); } + TerminatorKind::TailCall { + func, + args, + fn_span: _, + } => { + self.visit_operand(func, location); + for arg in args { + self.visit_operand(&$($mutability)? arg.node, location); + } + }, + TerminatorKind::Assert { cond, expected: _, diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index 2bdeb579a02de..60ab843257d5d 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -2,7 +2,9 @@ use crate::build::scope::BreakableTarget; use crate::build::{BlockAnd, BlockAndExtension, BlockFrame, Builder}; use rustc_middle::middle::region; use rustc_middle::mir::*; +use rustc_middle::span_bug; use rustc_middle::thir::*; +use rustc_span::source_map::Spanned; use tracing::debug; impl<'a, 'tcx> Builder<'a, 'tcx> { @@ -91,9 +93,38 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { ExprKind::Return { value } => { this.break_scope(block, value, BreakableTarget::Return, source_info) } - // FIXME(explicit_tail_calls): properly lower tail calls here ExprKind::Become { value } => { - this.break_scope(block, Some(value), BreakableTarget::Return, source_info) + let v = &this.thir[value]; + let ExprKind::Scope { value, .. } = v.kind else { + span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") + }; + + let v = &this.thir[value]; + let ExprKind::Call { ref args, fun, fn_span, .. } = v.kind else { + span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") + }; + + let fun = unpack!(block = this.as_local_operand(block, fun)); + let args: Vec<_> = args + .into_iter() + .copied() + .map(|arg| Spanned { + node: unpack!(block = this.as_local_call_operand(block, arg)), + span: this.thir.exprs[arg].span, + }) + .collect(); + + this.record_operands_moved(&args); + + debug!("expr_into_dest: fn_span={:?}", fn_span); + + this.cfg.terminate( + block, + source_info, + TerminatorKind::TailCall { func: fun, args, fn_span }, + ); + + this.cfg.start_new_block().unit() } _ => { assert!( diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index 5b6de39bb2e78..9e7534a283d9d 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -1523,6 +1523,7 @@ impl<'tcx> DropTreeBuilder<'tcx> for Unwind { | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } | TerminatorKind::CoroutineDrop diff --git a/compiler/rustc_mir_build/src/lints.rs b/compiler/rustc_mir_build/src/lints.rs index 1c7aa9f9ed095..3cb83a48ffe1b 100644 --- a/compiler/rustc_mir_build/src/lints.rs +++ b/compiler/rustc_mir_build/src/lints.rs @@ -196,6 +196,8 @@ impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor ControlFlow::Break(NonRecursive), diff --git a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs index 706bb796349fb..574da949b0ed3 100644 --- a/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs +++ b/compiler/rustc_mir_dataflow/src/impls/borrowed_locals.rs @@ -145,6 +145,7 @@ where | TerminatorKind::InlineAsm { .. } | TerminatorKind::UnwindResume | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Unreachable | TerminatorKind::Yield { .. } => {} diff --git a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs index 29169c31263bd..f850a71027739 100644 --- a/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs +++ b/compiler/rustc_mir_dataflow/src/impls/storage_liveness.rs @@ -288,6 +288,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> { | TerminatorKind::Goto { .. } | TerminatorKind::UnwindResume | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Unreachable => {} } @@ -325,6 +326,7 @@ impl<'tcx> crate::GenKillAnalysis<'tcx> for MaybeRequiresStorage<'_, 'tcx> { | TerminatorKind::Goto { .. } | TerminatorKind::UnwindResume | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::SwitchInt { .. } | TerminatorKind::Unreachable => {} } diff --git a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs index 1fb77bef3d410..7b39db821d839 100644 --- a/compiler/rustc_mir_dataflow/src/move_paths/builder.rs +++ b/compiler/rustc_mir_dataflow/src/move_paths/builder.rs @@ -489,6 +489,12 @@ impl<'b, 'a, 'tcx, F: Fn(Ty<'tcx>) -> bool> Gatherer<'b, 'a, 'tcx, F> { self.gather_init(destination.as_ref(), InitKind::NonPanicPathOnly); } } + TerminatorKind::TailCall { ref func, ref args, .. } => { + self.gather_operand(func); + for arg in args { + self.gather_operand(&arg.node); + } + } TerminatorKind::InlineAsm { template: _, ref operands, diff --git a/compiler/rustc_mir_dataflow/src/value_analysis.rs b/compiler/rustc_mir_dataflow/src/value_analysis.rs index 7c1ff6fda53f7..1582c2e8a9061 100644 --- a/compiler/rustc_mir_dataflow/src/value_analysis.rs +++ b/compiler/rustc_mir_dataflow/src/value_analysis.rs @@ -269,6 +269,9 @@ pub trait ValueAnalysis<'tcx> { TerminatorKind::SwitchInt { discr, targets } => { return self.handle_switch_int(discr, targets, state); } + TerminatorKind::TailCall { .. } => { + // FIXME(explicit_tail_calls): determine if we need to do something here (probably not) + } TerminatorKind::Goto { .. } | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) diff --git a/compiler/rustc_mir_transform/src/coroutine.rs b/compiler/rustc_mir_transform/src/coroutine.rs index 9d44001f91512..fa5da89e8ba56 100644 --- a/compiler/rustc_mir_transform/src/coroutine.rs +++ b/compiler/rustc_mir_transform/src/coroutine.rs @@ -1367,6 +1367,10 @@ fn can_unwind<'tcx>(tcx: TyCtxt<'tcx>, body: &Body<'tcx>) -> bool { | TerminatorKind::Call { .. } | TerminatorKind::InlineAsm { .. } | TerminatorKind::Assert { .. } => return true, + + TerminatorKind::TailCall { .. } => { + unreachable!("tail calls can't be present in generators") + } } } @@ -1916,6 +1920,7 @@ impl<'tcx> Visitor<'tcx> for EnsureCoroutineFieldAssignmentsNeverAlias<'_> { | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::Drop { .. } | TerminatorKind::Assert { .. } diff --git a/compiler/rustc_mir_transform/src/coverage/graph.rs b/compiler/rustc_mir_transform/src/coverage/graph.rs index 360dccb240dc6..83fb9ff974369 100644 --- a/compiler/rustc_mir_transform/src/coverage/graph.rs +++ b/compiler/rustc_mir_transform/src/coverage/graph.rs @@ -358,9 +358,12 @@ fn bcb_filtered_successors<'a, 'tcx>(terminator: &'a Terminator<'tcx>) -> Covera } // These terminators have no coverage-relevant successors. - CoroutineDrop | Return | Unreachable | UnwindResume | UnwindTerminate(_) => { - CoverageSuccessors::NotChainable(&[]) - } + CoroutineDrop + | Return + | TailCall { .. } + | Unreachable + | UnwindResume + | UnwindTerminate(_) => CoverageSuccessors::NotChainable(&[]), } } diff --git a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs index 2ca166929eec8..a0f8f580b1d38 100644 --- a/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs +++ b/compiler/rustc_mir_transform/src/coverage/spans/from_mir.rs @@ -193,7 +193,8 @@ fn filtered_terminator_span(terminator: &Terminator<'_>) -> Option { | TerminatorKind::Goto { .. } => None, // Call `func` operand can have a more specific span when part of a chain of calls - | TerminatorKind::Call { ref func, .. } => { + TerminatorKind::Call { ref func, .. } + | TerminatorKind::TailCall { ref func, .. } => { let mut span = terminator.source_info.span; if let mir::Operand::Constant(box constant) = func { if constant.span.lo() > span.lo() { diff --git a/compiler/rustc_mir_transform/src/dest_prop.rs b/compiler/rustc_mir_transform/src/dest_prop.rs index b1016c0867c62..ab73a8af317a7 100644 --- a/compiler/rustc_mir_transform/src/dest_prop.rs +++ b/compiler/rustc_mir_transform/src/dest_prop.rs @@ -628,6 +628,12 @@ impl WriteInfo { self.add_operand(&arg.node); } } + TerminatorKind::TailCall { func, args, .. } => { + self.add_operand(func); + for arg in args { + self.add_operand(&arg.node); + } + } TerminatorKind::InlineAsm { operands, .. } => { for asm_operand in operands { match asm_operand { diff --git a/compiler/rustc_mir_transform/src/inline.rs b/compiler/rustc_mir_transform/src/inline.rs index 5075e0727548f..fe73715480f8b 100644 --- a/compiler/rustc_mir_transform/src/inline.rs +++ b/compiler/rustc_mir_transform/src/inline.rs @@ -383,6 +383,8 @@ impl<'tcx> Inliner<'tcx> { ) -> Option> { // Only consider direct calls to functions let terminator = bb_data.terminator(); + + // FIXME(explicit_tail_calls): figure out if we can inline tail calls if let TerminatorKind::Call { ref func, fn_span, .. } = terminator.kind { let func_ty = func.ty(caller_body, self.tcx); if let ty::FnDef(def_id, args) = *func_ty.kind() { @@ -550,6 +552,9 @@ impl<'tcx> Inliner<'tcx> { // inline-asm is detected. LLVM will still possibly do an inline later on // if the no-attribute function ends up with the same instruction set anyway. return Err("Cannot move inline-asm across instruction sets"); + } else if let TerminatorKind::TailCall { .. } = term.kind { + // FIXME(explicit_tail_calls): figure out how exactly functions containing tail calls can be inlined (and if they even should) + return Err("can't inline functions with tail calls"); } else { work_list.extend(term.successors()) } @@ -1038,6 +1043,10 @@ impl<'tcx> MutVisitor<'tcx> for Integrator<'_, 'tcx> { *target = self.map_block(*target); *unwind = self.map_unwind(*unwind); } + TerminatorKind::TailCall { .. } => { + // check_mir_body forbids tail calls + unreachable!() + } TerminatorKind::Call { ref mut target, ref mut unwind, .. } => { if let Some(ref mut tgt) = *target { *tgt = self.map_block(*tgt); diff --git a/compiler/rustc_mir_transform/src/jump_threading.rs b/compiler/rustc_mir_transform/src/jump_threading.rs index 85a61f95ca36f..97ec0cb39ded3 100644 --- a/compiler/rustc_mir_transform/src/jump_threading.rs +++ b/compiler/rustc_mir_transform/src/jump_threading.rs @@ -596,6 +596,7 @@ impl<'tcx, 'a> TOFinder<'tcx, 'a> { TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::CoroutineDrop => bug!("{term:?} has no terminators"), // Disallowed during optimizations. diff --git a/compiler/rustc_mir_transform/src/known_panics_lint.rs b/compiler/rustc_mir_transform/src/known_panics_lint.rs index 8d6c00bbedbae..82ad8879d17b3 100644 --- a/compiler/rustc_mir_transform/src/known_panics_lint.rs +++ b/compiler/rustc_mir_transform/src/known_panics_lint.rs @@ -799,6 +799,7 @@ impl<'tcx> Visitor<'tcx> for ConstPropagator<'_, 'tcx> { | TerminatorKind::UnwindResume | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Return + | TerminatorKind::TailCall { .. } | TerminatorKind::Unreachable | TerminatorKind::Drop { .. } | TerminatorKind::Yield { .. } diff --git a/compiler/rustc_mir_transform/src/mentioned_items.rs b/compiler/rustc_mir_transform/src/mentioned_items.rs index db2bb60bdac34..d928d7cf76445 100644 --- a/compiler/rustc_mir_transform/src/mentioned_items.rs +++ b/compiler/rustc_mir_transform/src/mentioned_items.rs @@ -38,7 +38,7 @@ impl<'tcx> Visitor<'tcx> for MentionedItemsVisitor<'_, 'tcx> { self.super_terminator(terminator, location); let span = || self.body.source_info(location).span; match &terminator.kind { - mir::TerminatorKind::Call { func, .. } => { + mir::TerminatorKind::Call { func, .. } | mir::TerminatorKind::TailCall { func, .. } => { let callee_ty = func.ty(self.body, self.tcx); self.mentioned_items .push(Spanned { node: MentionedItem::Fn(callee_ty), span: span() }); diff --git a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs index fb52bfa468a01..1df5737e85974 100644 --- a/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs +++ b/compiler/rustc_mir_transform/src/remove_noop_landing_pads.rs @@ -75,6 +75,7 @@ impl RemoveNoopLandingPads { | TerminatorKind::UnwindTerminate(_) | TerminatorKind::Unreachable | TerminatorKind::Call { .. } + | TerminatorKind::TailCall { .. } | TerminatorKind::Assert { .. } | TerminatorKind::Drop { .. } | TerminatorKind::InlineAsm { .. } => false, diff --git a/compiler/rustc_mir_transform/src/validate.rs b/compiler/rustc_mir_transform/src/validate.rs index 2018a8fe667d7..ab5c25c493773 100644 --- a/compiler/rustc_mir_transform/src/validate.rs +++ b/compiler/rustc_mir_transform/src/validate.rs @@ -400,40 +400,44 @@ impl<'a, 'tcx> Visitor<'tcx> for CfgChecker<'a, 'tcx> { self.check_edge(location, *target, EdgeKind::Normal); self.check_unwind_edge(location, *unwind); } - TerminatorKind::Call { args, destination, target, unwind, .. } => { - if let Some(target) = target { - self.check_edge(location, *target, EdgeKind::Normal); - } - self.check_unwind_edge(location, *unwind); + TerminatorKind::Call { args, .. } | TerminatorKind::TailCall { args, .. } => { + // FIXME(explicit_tail_calls): refactor this & add tail-call specific checks + if let TerminatorKind::Call { target, unwind, destination, .. } = terminator.kind { + if let Some(target) = target { + self.check_edge(location, target, EdgeKind::Normal); + } + self.check_unwind_edge(location, unwind); + + // The code generation assumes that there are no critical call edges. The assumption + // is used to simplify inserting code that should be executed along the return edge + // from the call. FIXME(tmiasko): Since this is a strictly code generation concern, + // the code generation should be responsible for handling it. + if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized) + && self.is_critical_call_edge(target, unwind) + { + self.fail( + location, + format!( + "encountered critical edge in `Call` terminator {:?}", + terminator.kind, + ), + ); + } - // The code generation assumes that there are no critical call edges. The assumption - // is used to simplify inserting code that should be executed along the return edge - // from the call. FIXME(tmiasko): Since this is a strictly code generation concern, - // the code generation should be responsible for handling it. - if self.mir_phase >= MirPhase::Runtime(RuntimePhase::Optimized) - && self.is_critical_call_edge(*target, *unwind) - { - self.fail( - location, - format!( - "encountered critical edge in `Call` terminator {:?}", - terminator.kind, - ), - ); + // The call destination place and Operand::Move place used as an argument might be + // passed by a reference to the callee. Consequently they cannot be packed. + if is_within_packed(self.tcx, &self.body.local_decls, destination).is_some() { + // This is bad! The callee will expect the memory to be aligned. + self.fail( + location, + format!( + "encountered packed place in `Call` terminator destination: {:?}", + terminator.kind, + ), + ); + } } - // The call destination place and Operand::Move place used as an argument might be - // passed by a reference to the callee. Consequently they cannot be packed. - if is_within_packed(self.tcx, &self.body.local_decls, *destination).is_some() { - // This is bad! The callee will expect the memory to be aligned. - self.fail( - location, - format!( - "encountered packed place in `Call` terminator destination: {:?}", - terminator.kind, - ), - ); - } for arg in args { if let Operand::Move(place) = &arg.node { if is_within_packed(self.tcx, &self.body.local_decls, *place).is_some() { @@ -1498,15 +1502,22 @@ impl<'a, 'tcx> Visitor<'tcx> for TypeChecker<'a, 'tcx> { } } } - TerminatorKind::Call { func, .. } => { + TerminatorKind::Call { func, .. } | TerminatorKind::TailCall { func, .. } => { let func_ty = func.ty(&self.body.local_decls, self.tcx); match func_ty.kind() { ty::FnPtr(..) | ty::FnDef(..) => {} _ => self.fail( location, - format!("encountered non-callable type {func_ty} in `Call` terminator"), + format!( + "encountered non-callable type {func_ty} in `{}` terminator", + terminator.kind.name() + ), ), } + + if let TerminatorKind::TailCall { .. } = terminator.kind { + // FIXME(explicit_tail_calls): implement tail-call specific checks here (such as signature matching, forbidding closures, etc) + } } TerminatorKind::Assert { cond, .. } => { let cond_ty = cond.ty(&self.body.local_decls, self.tcx); diff --git a/compiler/rustc_monomorphize/src/collector.rs b/compiler/rustc_monomorphize/src/collector.rs index 342c01ff69731..3ef8da4a9c51c 100644 --- a/compiler/rustc_monomorphize/src/collector.rs +++ b/compiler/rustc_monomorphize/src/collector.rs @@ -755,7 +755,8 @@ impl<'a, 'tcx> MirVisitor<'tcx> for MirUsedCollector<'a, 'tcx> { }; match terminator.kind { - mir::TerminatorKind::Call { ref func, ref args, ref fn_span, .. } => { + mir::TerminatorKind::Call { ref func, ref args, ref fn_span, .. } + | mir::TerminatorKind::TailCall { ref func, ref args, ref fn_span } => { let callee_ty = func.ty(self.body, tcx); // *Before* monomorphizing, record that we already handled this mention. self.used_mentioned_items.insert(MentionedItem::Fn(callee_ty)); diff --git a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs index f15b82d0c031f..3367cea9897ae 100644 --- a/compiler/rustc_smir/src/rustc_smir/convert/mir.rs +++ b/compiler/rustc_smir/src/rustc_smir/convert/mir.rs @@ -644,6 +644,7 @@ impl<'tcx> Stable<'tcx> for mir::TerminatorKind<'tcx> { target: target.map(|t| t.as_usize()), unwind: unwind.stable(tables), }, + mir::TerminatorKind::TailCall { func: _, args: _, fn_span: _ } => todo!(), mir::TerminatorKind::Assert { cond, expected, msg, target, unwind } => { TerminatorKind::Assert { cond: cond.stable(tables), diff --git a/tests/ui/explicit-tail-calls/constck.rs b/tests/ui/explicit-tail-calls/constck.rs new file mode 100644 index 0000000000000..938f15f12c091 --- /dev/null +++ b/tests/ui/explicit-tail-calls/constck.rs @@ -0,0 +1,22 @@ +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +const fn f() { + if false { + become not_const(); + //~^ error: cannot call non-const fn `not_const` in constant functions + } +} + +const fn g((): ()) { + if false { + become yes_const(not_const()); + //~^ error: cannot call non-const fn `not_const` in constant functions + } +} + +fn not_const() {} + +const fn yes_const((): ()) {} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/constck.stderr b/tests/ui/explicit-tail-calls/constck.stderr new file mode 100644 index 0000000000000..d9967c45fa03c --- /dev/null +++ b/tests/ui/explicit-tail-calls/constck.stderr @@ -0,0 +1,19 @@ +error[E0015]: cannot call non-const fn `not_const` in constant functions + --> $DIR/constck.rs:6:16 + | +LL | become not_const(); + | ^^^^^^^^^^^ + | + = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants + +error[E0015]: cannot call non-const fn `not_const` in constant functions + --> $DIR/constck.rs:13:26 + | +LL | become yes_const(not_const()); + | ^^^^^^^^^^^ + | + = note: calls in constant functions are limited to constant functions, tuple structs and tuple variants + +error: aborting due to 2 previous errors + +For more information about this error, try `rustc --explain E0015`. diff --git a/tests/ui/explicit-tail-calls/return-mismatches.rs b/tests/ui/explicit-tail-calls/return-mismatches.rs index 8094a192913bc..935a1a1d28b02 100644 --- a/tests/ui/explicit-tail-calls/return-mismatches.rs +++ b/tests/ui/explicit-tail-calls/return-mismatches.rs @@ -13,7 +13,7 @@ fn _f1() { become _g1(); //~ error: mismatched types } -fn _g1() -> ! { //~ WARN: cannot return without recursing +fn _g1() -> ! { become _g1(); } diff --git a/tests/ui/explicit-tail-calls/return-mismatches.stderr b/tests/ui/explicit-tail-calls/return-mismatches.stderr index 31c7a46ded911..1dcc35797c130 100644 --- a/tests/ui/explicit-tail-calls/return-mismatches.stderr +++ b/tests/ui/explicit-tail-calls/return-mismatches.stderr @@ -22,17 +22,6 @@ error[E0308]: mismatched types LL | become _g2(); | ^^^^^^^^^^^^ expected `u32`, found `u16` -warning: function cannot return without recursing - --> $DIR/return-mismatches.rs:16:1 - | -LL | fn _g1() -> ! { - | ^^^^^^^^^^^^^ cannot return without recursing -LL | become _g1(); - | ----- recursive call site - | - = help: a `loop` may express intention better if this is on purpose - = note: `#[warn(unconditional_recursion)]` on by default - -error: aborting due to 3 previous errors; 1 warning emitted +error: aborting due to 3 previous errors For more information about this error, try `rustc --explain E0308`. diff --git a/tests/ui/explicit-tail-calls/unsafeck.rs b/tests/ui/explicit-tail-calls/unsafeck.rs new file mode 100644 index 0000000000000..872a70ca3a0a7 --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsafeck.rs @@ -0,0 +1,11 @@ +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +const fn f() { + become dangerous(); + //~^ error: call to unsafe function `dangerous` is unsafe and requires unsafe function or block +} + +const unsafe fn dangerous() {} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/unsafeck.stderr b/tests/ui/explicit-tail-calls/unsafeck.stderr new file mode 100644 index 0000000000000..25b8967e17b6d --- /dev/null +++ b/tests/ui/explicit-tail-calls/unsafeck.stderr @@ -0,0 +1,11 @@ +error[E0133]: call to unsafe function `dangerous` is unsafe and requires unsafe function or block + --> $DIR/unsafeck.rs:5:12 + | +LL | become dangerous(); + | ^^^^^^^^^^^ call to unsafe function + | + = note: consult the function's documentation for information on how to avoid undefined behavior + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0133`. diff --git a/tests/ui/parser/bad-let-else-statement.rs b/tests/ui/parser/bad-let-else-statement.rs index ff6619cbc9864..3ede26dbcd06c 100644 --- a/tests/ui/parser/bad-let-else-statement.rs +++ b/tests/ui/parser/bad-let-else-statement.rs @@ -147,14 +147,14 @@ fn o() -> Result<(), ()> { }; } -fn p() { - let 0 = become { - () - } else { - //~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed - return; - }; -} +// fn p() { // FIXME(explicit_tail_calls): this currently trips an assertion... +// let 0 = become { +// () +// } else { +// // ~^ ERROR right curly brace `}` before `else` in a `let...else` statement not allowed +// return; +// }; +// } fn q() { let foo = |x: i32| { diff --git a/tests/ui/parser/bad-let-else-statement.stderr b/tests/ui/parser/bad-let-else-statement.stderr index 0bf6a346dfb17..79d722bb7ac44 100644 --- a/tests/ui/parser/bad-let-else-statement.stderr +++ b/tests/ui/parser/bad-let-else-statement.stderr @@ -203,19 +203,6 @@ LL | () LL ~ }) else { | -error: right curly brace `}` before `else` in a `let...else` statement not allowed - --> $DIR/bad-let-else-statement.rs:153:5 - | -LL | } else { - | ^ - | -help: wrap the expression in parentheses - | -LL ~ let 0 = become ({ -LL | () -LL ~ }) else { - | - error: right curly brace `}` before `else` in a `let...else` statement not allowed --> $DIR/bad-let-else-statement.rs:163:5 | @@ -325,5 +312,5 @@ LL | | } else { = note: this pattern will always match, so the `else` clause is useless = help: consider removing the `else` clause -error: aborting due to 20 previous errors; 5 warnings emitted +error: aborting due to 19 previous errors; 5 warnings emitted From 4187cdc0135ee05802733632d4cbf6467da22847 Mon Sep 17 00:00:00 2001 From: DrMeepster <19316085+DrMeepster@users.noreply.github.com> Date: Wed, 14 Jun 2023 01:12:25 -0700 Subject: [PATCH 16/32] Properly handle drops for tail calls --- .../rustc_mir_build/src/build/expr/stmt.rs | 40 ++-- compiler/rustc_mir_build/src/build/scope.rs | 85 ++++++++ ...ll_drops.f.ElaborateDrops.panic-abort.diff | 108 ++++++++++ ...l_drops.f.ElaborateDrops.panic-unwind.diff | 109 ++++++++++ ...l_call_drops.f.built.after.panic-abort.mir | 118 ++++++++++ ..._call_drops.f.built.after.panic-unwind.mir | 118 ++++++++++ ...f_with_arg.ElaborateDrops.panic-abort.diff | 184 ++++++++++++++++ ..._with_arg.ElaborateDrops.panic-unwind.diff | 184 ++++++++++++++++ ...ops.f_with_arg.built.after.panic-abort.mir | 202 ++++++++++++++++++ ...ps.f_with_arg.built.after.panic-unwind.mir | 202 ++++++++++++++++++ tests/mir-opt/tail_call_drops.rs | 41 ++++ tests/ui/explicit-tail-calls/drop-order.rs | 70 ++++++ 12 files changed, 1443 insertions(+), 18 deletions(-) create mode 100644 tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff create mode 100644 tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff create mode 100644 tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir create mode 100644 tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir create mode 100644 tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff create mode 100644 tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff create mode 100644 tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir create mode 100644 tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir create mode 100644 tests/mir-opt/tail_call_drops.rs create mode 100644 tests/ui/explicit-tail-calls/drop-order.rs diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index 60ab843257d5d..7d2c32e000b90 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -95,7 +95,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { } ExprKind::Become { value } => { let v = &this.thir[value]; - let ExprKind::Scope { value, .. } = v.kind else { + let ExprKind::Scope { value, lint_level, region_scope } = v.kind else { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") }; @@ -104,27 +104,31 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { span_bug!(v.span, "`thir_check_tail_calls` should have disallowed this {v:?}") }; - let fun = unpack!(block = this.as_local_operand(block, fun)); - let args: Vec<_> = args - .into_iter() - .copied() - .map(|arg| Spanned { - node: unpack!(block = this.as_local_call_operand(block, arg)), - span: this.thir.exprs[arg].span, - }) - .collect(); + this.in_scope((region_scope, source_info), lint_level, |this| { + let fun = unpack!(block = this.as_local_operand(block, fun)); + let args: Vec<_> = args + .into_iter() + .copied() + .map(|arg| Spanned { + node: unpack!(block = this.as_local_call_operand(block, arg)), + span: this.thir.exprs[arg].span, + }) + .collect(); - this.record_operands_moved(&args); + this.record_operands_moved(&args); - debug!("expr_into_dest: fn_span={:?}", fn_span); + debug!("expr_into_dest: fn_span={:?}", fn_span); - this.cfg.terminate( - block, - source_info, - TerminatorKind::TailCall { func: fun, args, fn_span }, - ); + unpack!(block = this.break_for_tail_call(block, &args, source_info)); - this.cfg.start_new_block().unit() + this.cfg.terminate( + block, + source_info, + TerminatorKind::TailCall { func: fun, args, fn_span }, + ); + + this.cfg.start_new_block().unit() + }) } _ => { assert!( diff --git a/compiler/rustc_mir_build/src/build/scope.rs b/compiler/rustc_mir_build/src/build/scope.rs index 9e7534a283d9d..948301e2ece4d 100644 --- a/compiler/rustc_mir_build/src/build/scope.rs +++ b/compiler/rustc_mir_build/src/build/scope.rs @@ -745,6 +745,91 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { self.cfg.terminate(block, source_info, TerminatorKind::UnwindResume); } + /// Sets up the drops for explict tail calls. + /// + /// Unlike other kinds of early exits, tail calls do not go through the drop tree. + /// Instead, all scheduled drops are immediately added to the CFG. + pub(crate) fn break_for_tail_call( + &mut self, + mut block: BasicBlock, + args: &[Spanned>], + source_info: SourceInfo, + ) -> BlockAnd<()> { + let arg_drops: Vec<_> = args + .iter() + .rev() + .filter_map(|arg| match &arg.node { + Operand::Copy(_) => bug!("copy op in tail call args"), + Operand::Move(place) => { + let local = + place.as_local().unwrap_or_else(|| bug!("projection in tail call args")); + + Some(DropData { source_info, local, kind: DropKind::Value }) + } + Operand::Constant(_) => None, + }) + .collect(); + + let mut unwind_to = self.diverge_cleanup_target( + self.scopes.scopes.iter().rev().nth(1).unwrap().region_scope, + DUMMY_SP, + ); + let unwind_drops = &mut self.scopes.unwind_drops; + + // the innermost scope contains only the destructors for the tail call arguments + // we only want to drop these in case of a panic, so we skip it + for scope in self.scopes.scopes[1..].iter().rev().skip(1) { + // FIXME(explicit_tail_calls) code duplication with `build_scope_drops` + for drop_data in scope.drops.iter().rev() { + let source_info = drop_data.source_info; + let local = drop_data.local; + + match drop_data.kind { + DropKind::Value => { + // `unwind_to` should drop the value that we're about to + // schedule. If dropping this value panics, then we continue + // with the *next* value on the unwind path. + debug_assert_eq!(unwind_drops.drops[unwind_to].data.local, drop_data.local); + debug_assert_eq!(unwind_drops.drops[unwind_to].data.kind, drop_data.kind); + unwind_to = unwind_drops.drops[unwind_to].next; + + let mut unwind_entry_point = unwind_to; + + // the tail call arguments must be dropped if any of these drops panic + for drop in arg_drops.iter().copied() { + unwind_entry_point = unwind_drops.add_drop(drop, unwind_entry_point); + } + + unwind_drops.add_entry_point(block, unwind_entry_point); + + let next = self.cfg.start_new_block(); + self.cfg.terminate( + block, + source_info, + TerminatorKind::Drop { + place: local.into(), + target: next, + unwind: UnwindAction::Continue, + replace: false, + }, + ); + block = next; + } + DropKind::Storage => { + // Only temps and vars need their storage dead. + assert!(local.index() > self.arg_count); + self.cfg.push( + block, + Statement { source_info, kind: StatementKind::StorageDead(local) }, + ); + } + } + } + } + + block.unit() + } + fn leave_top_scope(&mut self, block: BasicBlock) -> BasicBlock { // If we are emitting a `drop` statement, we need to have the cached // diverge cleanup pads ready in case that drop panics. diff --git a/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff new file mode 100644 index 0000000000000..44673ea00a966 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-abort.diff @@ -0,0 +1,108 @@ +- // MIR for `f` before ElaborateDrops ++ // MIR for `f` after ElaborateDrops + + fn f() -> () { + let mut _0: (); + let mut _1: !; + let _2: std::string::String; + let _6: (); + let mut _7: std::string::String; ++ let mut _8: bool; + scope 1 { + debug _a => _2; + let _3: i32; + scope 2 { + debug _b => _3; + let _4: std::string::String; + scope 3 { + debug _c => _4; + let _5: std::string::String; + scope 4 { + debug _d => _5; + } + } + } + } + + bb0: { ++ _8 = const false; + StorageLive(_2); + _2 = String::new() -> [return: bb1, unwind: bb12]; + } + + bb1: { + StorageLive(_3); + _3 = const 12_i32; + StorageLive(_4); + _4 = String::new() -> [return: bb2, unwind: bb11]; + } + + bb2: { ++ _8 = const true; + StorageLive(_5); + _5 = String::new() -> [return: bb3, unwind: bb10]; + } + + bb3: { + StorageLive(_6); + StorageLive(_7); ++ _8 = const false; + _7 = move _4; + _6 = std::mem::drop::(move _7) -> [return: bb4, unwind: bb8]; + } + + bb4: { + StorageDead(_7); + StorageDead(_6); + drop(_5) -> [return: bb5, unwind: bb10]; + } + + bb5: { + StorageDead(_5); +- drop(_4) -> [return: bb6, unwind: bb11]; ++ goto -> bb6; + } + + bb6: { ++ _8 = const false; + StorageDead(_4); + StorageDead(_3); + drop(_2) -> [return: bb7, unwind: bb12]; + } + + bb7: { + StorageDead(_2); + tailcall g(); + } + + bb8 (cleanup): { +- drop(_7) -> [return: bb9, unwind terminate(cleanup)]; ++ goto -> bb9; + } + + bb9 (cleanup): { + drop(_5) -> [return: bb10, unwind terminate(cleanup)]; + } + + bb10 (cleanup): { +- drop(_4) -> [return: bb11, unwind terminate(cleanup)]; ++ goto -> bb14; + } + + bb11 (cleanup): { + drop(_2) -> [return: bb12, unwind terminate(cleanup)]; + } + + bb12 (cleanup): { + resume; ++ } ++ ++ bb13 (cleanup): { ++ drop(_4) -> [return: bb11, unwind terminate(cleanup)]; ++ } ++ ++ bb14 (cleanup): { ++ switchInt(_8) -> [0: bb11, otherwise: bb13]; + } + } + diff --git a/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff new file mode 100644 index 0000000000000..a6d33a2459543 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f.ElaborateDrops.panic-unwind.diff @@ -0,0 +1,109 @@ +- // MIR for `f` before ElaborateDrops ++ // MIR for `f` after ElaborateDrops + + fn f() -> () { + let mut _0: (); + let mut _1: !; + let _2: std::string::String; + let _6: (); + let mut _7: std::string::String; ++ let mut _8: bool; + scope 1 { + debug _a => _2; + let _3: i32; + scope 2 { + debug _b => _3; + let _4: std::string::String; + scope 3 { + debug _c => _4; + let _5: std::string::String; + scope 4 { + debug _d => _5; + } + } + } + } + + bb0: { ++ _8 = const false; + StorageLive(_2); + _2 = String::new() -> [return: bb1, unwind continue]; + } + + bb1: { + StorageLive(_3); + _3 = const 12_i32; + StorageLive(_4); + _4 = String::new() -> [return: bb2, unwind: bb11]; + } + + bb2: { ++ _8 = const true; + StorageLive(_5); + _5 = String::new() -> [return: bb3, unwind: bb10]; + } + + bb3: { + StorageLive(_6); + StorageLive(_7); ++ _8 = const false; + _7 = move _4; + _6 = std::mem::drop::(move _7) -> [return: bb4, unwind: bb8]; + } + + bb4: { + StorageDead(_7); + StorageDead(_6); + drop(_5) -> [return: bb5, unwind: bb10]; + } + + bb5: { + StorageDead(_5); +- drop(_4) -> [return: bb6, unwind: bb11]; ++ goto -> bb6; + } + + bb6: { ++ _8 = const false; + StorageDead(_4); + StorageDead(_3); +- drop(_2) -> [return: bb7, unwind continue]; ++ drop(_2) -> [return: bb7, unwind: bb12]; + } + + bb7: { + StorageDead(_2); + tailcall g(); + } + + bb8 (cleanup): { +- drop(_7) -> [return: bb9, unwind terminate(cleanup)]; ++ goto -> bb9; + } + + bb9 (cleanup): { + drop(_5) -> [return: bb10, unwind terminate(cleanup)]; + } + + bb10 (cleanup): { +- drop(_4) -> [return: bb11, unwind terminate(cleanup)]; ++ goto -> bb14; + } + + bb11 (cleanup): { + drop(_2) -> [return: bb12, unwind terminate(cleanup)]; + } + + bb12 (cleanup): { + resume; ++ } ++ ++ bb13 (cleanup): { ++ drop(_4) -> [return: bb11, unwind terminate(cleanup)]; ++ } ++ ++ bb14 (cleanup): { ++ switchInt(_8) -> [0: bb11, otherwise: bb13]; + } + } + diff --git a/tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir b/tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir new file mode 100644 index 0000000000000..2c3d62491d715 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f.built.after.panic-abort.mir @@ -0,0 +1,118 @@ +// MIR for `f` after built + +fn f() -> () { + let mut _0: (); + let mut _1: !; + let _2: std::string::String; + let _6: (); + let mut _7: std::string::String; + scope 1 { + debug _a => _2; + let _3: i32; + scope 2 { + debug _b => _3; + let _4: std::string::String; + scope 3 { + debug _c => _4; + let _5: std::string::String; + scope 4 { + debug _d => _5; + } + } + } + } + + bb0: { + StorageLive(_2); + _2 = String::new() -> [return: bb1, unwind: bb17]; + } + + bb1: { + FakeRead(ForLet(None), _2); + StorageLive(_3); + _3 = const 12_i32; + FakeRead(ForLet(None), _3); + StorageLive(_4); + _4 = String::new() -> [return: bb2, unwind: bb16]; + } + + bb2: { + FakeRead(ForLet(None), _4); + StorageLive(_5); + _5 = String::new() -> [return: bb3, unwind: bb15]; + } + + bb3: { + FakeRead(ForLet(None), _5); + StorageLive(_6); + StorageLive(_7); + _7 = move _4; + _6 = std::mem::drop::(move _7) -> [return: bb4, unwind: bb13]; + } + + bb4: { + StorageDead(_7); + StorageDead(_6); + drop(_5) -> [return: bb5, unwind: bb15]; + } + + bb5: { + StorageDead(_5); + drop(_4) -> [return: bb6, unwind: bb16]; + } + + bb6: { + StorageDead(_4); + StorageDead(_3); + drop(_2) -> [return: bb7, unwind: bb17]; + } + + bb7: { + StorageDead(_2); + tailcall g(); + } + + bb8: { + drop(_5) -> [return: bb9, unwind: bb15]; + } + + bb9: { + StorageDead(_5); + drop(_4) -> [return: bb10, unwind: bb16]; + } + + bb10: { + StorageDead(_4); + StorageDead(_3); + drop(_2) -> [return: bb11, unwind: bb17]; + } + + bb11: { + StorageDead(_2); + unreachable; + } + + bb12: { + return; + } + + bb13 (cleanup): { + drop(_7) -> [return: bb14, unwind terminate(cleanup)]; + } + + bb14 (cleanup): { + drop(_5) -> [return: bb15, unwind terminate(cleanup)]; + } + + bb15 (cleanup): { + drop(_4) -> [return: bb16, unwind terminate(cleanup)]; + } + + bb16 (cleanup): { + drop(_2) -> [return: bb17, unwind terminate(cleanup)]; + } + + bb17 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir b/tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..2c3d62491d715 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f.built.after.panic-unwind.mir @@ -0,0 +1,118 @@ +// MIR for `f` after built + +fn f() -> () { + let mut _0: (); + let mut _1: !; + let _2: std::string::String; + let _6: (); + let mut _7: std::string::String; + scope 1 { + debug _a => _2; + let _3: i32; + scope 2 { + debug _b => _3; + let _4: std::string::String; + scope 3 { + debug _c => _4; + let _5: std::string::String; + scope 4 { + debug _d => _5; + } + } + } + } + + bb0: { + StorageLive(_2); + _2 = String::new() -> [return: bb1, unwind: bb17]; + } + + bb1: { + FakeRead(ForLet(None), _2); + StorageLive(_3); + _3 = const 12_i32; + FakeRead(ForLet(None), _3); + StorageLive(_4); + _4 = String::new() -> [return: bb2, unwind: bb16]; + } + + bb2: { + FakeRead(ForLet(None), _4); + StorageLive(_5); + _5 = String::new() -> [return: bb3, unwind: bb15]; + } + + bb3: { + FakeRead(ForLet(None), _5); + StorageLive(_6); + StorageLive(_7); + _7 = move _4; + _6 = std::mem::drop::(move _7) -> [return: bb4, unwind: bb13]; + } + + bb4: { + StorageDead(_7); + StorageDead(_6); + drop(_5) -> [return: bb5, unwind: bb15]; + } + + bb5: { + StorageDead(_5); + drop(_4) -> [return: bb6, unwind: bb16]; + } + + bb6: { + StorageDead(_4); + StorageDead(_3); + drop(_2) -> [return: bb7, unwind: bb17]; + } + + bb7: { + StorageDead(_2); + tailcall g(); + } + + bb8: { + drop(_5) -> [return: bb9, unwind: bb15]; + } + + bb9: { + StorageDead(_5); + drop(_4) -> [return: bb10, unwind: bb16]; + } + + bb10: { + StorageDead(_4); + StorageDead(_3); + drop(_2) -> [return: bb11, unwind: bb17]; + } + + bb11: { + StorageDead(_2); + unreachable; + } + + bb12: { + return; + } + + bb13 (cleanup): { + drop(_7) -> [return: bb14, unwind terminate(cleanup)]; + } + + bb14 (cleanup): { + drop(_5) -> [return: bb15, unwind terminate(cleanup)]; + } + + bb15 (cleanup): { + drop(_4) -> [return: bb16, unwind terminate(cleanup)]; + } + + bb16 (cleanup): { + drop(_2) -> [return: bb17, unwind terminate(cleanup)]; + } + + bb17 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff new file mode 100644 index 0000000000000..c7df2bb2207fb --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-abort.diff @@ -0,0 +1,184 @@ +- // MIR for `f_with_arg` before ElaborateDrops ++ // MIR for `f_with_arg` after ElaborateDrops + + fn f_with_arg(_1: String, _2: String) -> () { + debug _arg1 => _1; + debug _arg2 => _2; + let mut _0: (); + let mut _3: !; + let _4: std::string::String; + let _8: (); + let mut _9: std::string::String; + let mut _10: std::string::String; + let mut _11: std::string::String; ++ let mut _12: bool; + scope 1 { + debug _a => _4; + let _5: i32; + scope 2 { + debug _b => _5; + let _6: std::string::String; + scope 3 { + debug _c => _6; + let _7: std::string::String; + scope 4 { + debug _d => _7; + } + } + } + } + + bb0: { ++ _12 = const false; + StorageLive(_4); + _4 = String::new() -> [return: bb1, unwind: bb27]; + } + + bb1: { + StorageLive(_5); + _5 = const 12_i32; + StorageLive(_6); + _6 = String::new() -> [return: bb2, unwind: bb26]; + } + + bb2: { ++ _12 = const true; + StorageLive(_7); + _7 = String::new() -> [return: bb3, unwind: bb25]; + } + + bb3: { + StorageLive(_8); + StorageLive(_9); ++ _12 = const false; + _9 = move _6; + _8 = std::mem::drop::(move _9) -> [return: bb4, unwind: bb23]; + } + + bb4: { + StorageDead(_9); + StorageDead(_8); + StorageLive(_10); + _10 = String::new() -> [return: bb5, unwind: bb24]; + } + + bb5: { + StorageLive(_11); + _11 = String::new() -> [return: bb6, unwind: bb22]; + } + + bb6: { + drop(_7) -> [return: bb7, unwind: bb20]; + } + + bb7: { + StorageDead(_7); +- drop(_6) -> [return: bb8, unwind: bb18]; ++ goto -> bb8; + } + + bb8: { ++ _12 = const false; + StorageDead(_6); + StorageDead(_5); + drop(_4) -> [return: bb9, unwind: bb16]; + } + + bb9: { + StorageDead(_4); + drop(_2) -> [return: bb10, unwind: bb14]; + } + + bb10: { + drop(_1) -> [return: bb11, unwind: bb12]; + } + + bb11: { + tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) }); + } + + bb12 (cleanup): { + drop(_10) -> [return: bb13, unwind terminate(cleanup)]; + } + + bb13 (cleanup): { + drop(_11) -> [return: bb29, unwind terminate(cleanup)]; + } + + bb14 (cleanup): { + drop(_10) -> [return: bb15, unwind terminate(cleanup)]; + } + + bb15 (cleanup): { + drop(_11) -> [return: bb28, unwind terminate(cleanup)]; + } + + bb16 (cleanup): { + drop(_10) -> [return: bb17, unwind terminate(cleanup)]; + } + + bb17 (cleanup): { + drop(_11) -> [return: bb27, unwind terminate(cleanup)]; + } + + bb18 (cleanup): { +- drop(_10) -> [return: bb19, unwind terminate(cleanup)]; ++ goto -> bb19; + } + + bb19 (cleanup): { +- drop(_11) -> [return: bb26, unwind terminate(cleanup)]; ++ goto -> bb26; + } + + bb20 (cleanup): { + drop(_10) -> [return: bb21, unwind terminate(cleanup)]; + } + + bb21 (cleanup): { + drop(_11) -> [return: bb25, unwind terminate(cleanup)]; + } + + bb22 (cleanup): { + drop(_10) -> [return: bb24, unwind terminate(cleanup)]; + } + + bb23 (cleanup): { +- drop(_9) -> [return: bb24, unwind terminate(cleanup)]; ++ goto -> bb24; + } + + bb24 (cleanup): { + drop(_7) -> [return: bb25, unwind terminate(cleanup)]; + } + + bb25 (cleanup): { +- drop(_6) -> [return: bb26, unwind terminate(cleanup)]; ++ goto -> bb31; + } + + bb26 (cleanup): { + drop(_4) -> [return: bb27, unwind terminate(cleanup)]; + } + + bb27 (cleanup): { + drop(_2) -> [return: bb28, unwind terminate(cleanup)]; + } + + bb28 (cleanup): { + drop(_1) -> [return: bb29, unwind terminate(cleanup)]; + } + + bb29 (cleanup): { + resume; ++ } ++ ++ bb30 (cleanup): { ++ drop(_6) -> [return: bb26, unwind terminate(cleanup)]; ++ } ++ ++ bb31 (cleanup): { ++ switchInt(_12) -> [0: bb26, otherwise: bb30]; + } + } + diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff new file mode 100644 index 0000000000000..c7df2bb2207fb --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f_with_arg.ElaborateDrops.panic-unwind.diff @@ -0,0 +1,184 @@ +- // MIR for `f_with_arg` before ElaborateDrops ++ // MIR for `f_with_arg` after ElaborateDrops + + fn f_with_arg(_1: String, _2: String) -> () { + debug _arg1 => _1; + debug _arg2 => _2; + let mut _0: (); + let mut _3: !; + let _4: std::string::String; + let _8: (); + let mut _9: std::string::String; + let mut _10: std::string::String; + let mut _11: std::string::String; ++ let mut _12: bool; + scope 1 { + debug _a => _4; + let _5: i32; + scope 2 { + debug _b => _5; + let _6: std::string::String; + scope 3 { + debug _c => _6; + let _7: std::string::String; + scope 4 { + debug _d => _7; + } + } + } + } + + bb0: { ++ _12 = const false; + StorageLive(_4); + _4 = String::new() -> [return: bb1, unwind: bb27]; + } + + bb1: { + StorageLive(_5); + _5 = const 12_i32; + StorageLive(_6); + _6 = String::new() -> [return: bb2, unwind: bb26]; + } + + bb2: { ++ _12 = const true; + StorageLive(_7); + _7 = String::new() -> [return: bb3, unwind: bb25]; + } + + bb3: { + StorageLive(_8); + StorageLive(_9); ++ _12 = const false; + _9 = move _6; + _8 = std::mem::drop::(move _9) -> [return: bb4, unwind: bb23]; + } + + bb4: { + StorageDead(_9); + StorageDead(_8); + StorageLive(_10); + _10 = String::new() -> [return: bb5, unwind: bb24]; + } + + bb5: { + StorageLive(_11); + _11 = String::new() -> [return: bb6, unwind: bb22]; + } + + bb6: { + drop(_7) -> [return: bb7, unwind: bb20]; + } + + bb7: { + StorageDead(_7); +- drop(_6) -> [return: bb8, unwind: bb18]; ++ goto -> bb8; + } + + bb8: { ++ _12 = const false; + StorageDead(_6); + StorageDead(_5); + drop(_4) -> [return: bb9, unwind: bb16]; + } + + bb9: { + StorageDead(_4); + drop(_2) -> [return: bb10, unwind: bb14]; + } + + bb10: { + drop(_1) -> [return: bb11, unwind: bb12]; + } + + bb11: { + tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) }); + } + + bb12 (cleanup): { + drop(_10) -> [return: bb13, unwind terminate(cleanup)]; + } + + bb13 (cleanup): { + drop(_11) -> [return: bb29, unwind terminate(cleanup)]; + } + + bb14 (cleanup): { + drop(_10) -> [return: bb15, unwind terminate(cleanup)]; + } + + bb15 (cleanup): { + drop(_11) -> [return: bb28, unwind terminate(cleanup)]; + } + + bb16 (cleanup): { + drop(_10) -> [return: bb17, unwind terminate(cleanup)]; + } + + bb17 (cleanup): { + drop(_11) -> [return: bb27, unwind terminate(cleanup)]; + } + + bb18 (cleanup): { +- drop(_10) -> [return: bb19, unwind terminate(cleanup)]; ++ goto -> bb19; + } + + bb19 (cleanup): { +- drop(_11) -> [return: bb26, unwind terminate(cleanup)]; ++ goto -> bb26; + } + + bb20 (cleanup): { + drop(_10) -> [return: bb21, unwind terminate(cleanup)]; + } + + bb21 (cleanup): { + drop(_11) -> [return: bb25, unwind terminate(cleanup)]; + } + + bb22 (cleanup): { + drop(_10) -> [return: bb24, unwind terminate(cleanup)]; + } + + bb23 (cleanup): { +- drop(_9) -> [return: bb24, unwind terminate(cleanup)]; ++ goto -> bb24; + } + + bb24 (cleanup): { + drop(_7) -> [return: bb25, unwind terminate(cleanup)]; + } + + bb25 (cleanup): { +- drop(_6) -> [return: bb26, unwind terminate(cleanup)]; ++ goto -> bb31; + } + + bb26 (cleanup): { + drop(_4) -> [return: bb27, unwind terminate(cleanup)]; + } + + bb27 (cleanup): { + drop(_2) -> [return: bb28, unwind terminate(cleanup)]; + } + + bb28 (cleanup): { + drop(_1) -> [return: bb29, unwind terminate(cleanup)]; + } + + bb29 (cleanup): { + resume; ++ } ++ ++ bb30 (cleanup): { ++ drop(_6) -> [return: bb26, unwind terminate(cleanup)]; ++ } ++ ++ bb31 (cleanup): { ++ switchInt(_12) -> [0: bb26, otherwise: bb30]; + } + } + diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir new file mode 100644 index 0000000000000..744f1989acc92 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-abort.mir @@ -0,0 +1,202 @@ +// MIR for `f_with_arg` after built + +fn f_with_arg(_1: String, _2: String) -> () { + debug _arg1 => _1; + debug _arg2 => _2; + let mut _0: (); + let mut _3: !; + let _4: std::string::String; + let _8: (); + let mut _9: std::string::String; + let mut _10: std::string::String; + let mut _11: std::string::String; + scope 1 { + debug _a => _4; + let _5: i32; + scope 2 { + debug _b => _5; + let _6: std::string::String; + scope 3 { + debug _c => _6; + let _7: std::string::String; + scope 4 { + debug _d => _7; + } + } + } + } + + bb0: { + StorageLive(_4); + _4 = String::new() -> [return: bb1, unwind: bb34]; + } + + bb1: { + FakeRead(ForLet(None), _4); + StorageLive(_5); + _5 = const 12_i32; + FakeRead(ForLet(None), _5); + StorageLive(_6); + _6 = String::new() -> [return: bb2, unwind: bb33]; + } + + bb2: { + FakeRead(ForLet(None), _6); + StorageLive(_7); + _7 = String::new() -> [return: bb3, unwind: bb32]; + } + + bb3: { + FakeRead(ForLet(None), _7); + StorageLive(_8); + StorageLive(_9); + _9 = move _6; + _8 = std::mem::drop::(move _9) -> [return: bb4, unwind: bb30]; + } + + bb4: { + StorageDead(_9); + StorageDead(_8); + StorageLive(_10); + _10 = String::new() -> [return: bb5, unwind: bb31]; + } + + bb5: { + StorageLive(_11); + _11 = String::new() -> [return: bb6, unwind: bb29]; + } + + bb6: { + drop(_7) -> [return: bb7, unwind: bb27]; + } + + bb7: { + StorageDead(_7); + drop(_6) -> [return: bb8, unwind: bb25]; + } + + bb8: { + StorageDead(_6); + StorageDead(_5); + drop(_4) -> [return: bb9, unwind: bb23]; + } + + bb9: { + StorageDead(_4); + drop(_2) -> [return: bb10, unwind: bb21]; + } + + bb10: { + drop(_1) -> [return: bb11, unwind: bb19]; + } + + bb11: { + tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) }); + } + + bb12: { + StorageDead(_11); + StorageDead(_10); + drop(_7) -> [return: bb13, unwind: bb32]; + } + + bb13: { + StorageDead(_7); + drop(_6) -> [return: bb14, unwind: bb33]; + } + + bb14: { + StorageDead(_6); + StorageDead(_5); + drop(_4) -> [return: bb15, unwind: bb34]; + } + + bb15: { + StorageDead(_4); + unreachable; + } + + bb16: { + drop(_2) -> [return: bb17, unwind: bb35]; + } + + bb17: { + drop(_1) -> [return: bb18, unwind: bb36]; + } + + bb18: { + return; + } + + bb19 (cleanup): { + drop(_10) -> [return: bb20, unwind terminate(cleanup)]; + } + + bb20 (cleanup): { + drop(_11) -> [return: bb36, unwind terminate(cleanup)]; + } + + bb21 (cleanup): { + drop(_10) -> [return: bb22, unwind terminate(cleanup)]; + } + + bb22 (cleanup): { + drop(_11) -> [return: bb35, unwind terminate(cleanup)]; + } + + bb23 (cleanup): { + drop(_10) -> [return: bb24, unwind terminate(cleanup)]; + } + + bb24 (cleanup): { + drop(_11) -> [return: bb34, unwind terminate(cleanup)]; + } + + bb25 (cleanup): { + drop(_10) -> [return: bb26, unwind terminate(cleanup)]; + } + + bb26 (cleanup): { + drop(_11) -> [return: bb33, unwind terminate(cleanup)]; + } + + bb27 (cleanup): { + drop(_10) -> [return: bb28, unwind terminate(cleanup)]; + } + + bb28 (cleanup): { + drop(_11) -> [return: bb32, unwind terminate(cleanup)]; + } + + bb29 (cleanup): { + drop(_10) -> [return: bb31, unwind terminate(cleanup)]; + } + + bb30 (cleanup): { + drop(_9) -> [return: bb31, unwind terminate(cleanup)]; + } + + bb31 (cleanup): { + drop(_7) -> [return: bb32, unwind terminate(cleanup)]; + } + + bb32 (cleanup): { + drop(_6) -> [return: bb33, unwind terminate(cleanup)]; + } + + bb33 (cleanup): { + drop(_4) -> [return: bb34, unwind terminate(cleanup)]; + } + + bb34 (cleanup): { + drop(_2) -> [return: bb35, unwind terminate(cleanup)]; + } + + bb35 (cleanup): { + drop(_1) -> [return: bb36, unwind terminate(cleanup)]; + } + + bb36 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir new file mode 100644 index 0000000000000..744f1989acc92 --- /dev/null +++ b/tests/mir-opt/tail_call_drops.f_with_arg.built.after.panic-unwind.mir @@ -0,0 +1,202 @@ +// MIR for `f_with_arg` after built + +fn f_with_arg(_1: String, _2: String) -> () { + debug _arg1 => _1; + debug _arg2 => _2; + let mut _0: (); + let mut _3: !; + let _4: std::string::String; + let _8: (); + let mut _9: std::string::String; + let mut _10: std::string::String; + let mut _11: std::string::String; + scope 1 { + debug _a => _4; + let _5: i32; + scope 2 { + debug _b => _5; + let _6: std::string::String; + scope 3 { + debug _c => _6; + let _7: std::string::String; + scope 4 { + debug _d => _7; + } + } + } + } + + bb0: { + StorageLive(_4); + _4 = String::new() -> [return: bb1, unwind: bb34]; + } + + bb1: { + FakeRead(ForLet(None), _4); + StorageLive(_5); + _5 = const 12_i32; + FakeRead(ForLet(None), _5); + StorageLive(_6); + _6 = String::new() -> [return: bb2, unwind: bb33]; + } + + bb2: { + FakeRead(ForLet(None), _6); + StorageLive(_7); + _7 = String::new() -> [return: bb3, unwind: bb32]; + } + + bb3: { + FakeRead(ForLet(None), _7); + StorageLive(_8); + StorageLive(_9); + _9 = move _6; + _8 = std::mem::drop::(move _9) -> [return: bb4, unwind: bb30]; + } + + bb4: { + StorageDead(_9); + StorageDead(_8); + StorageLive(_10); + _10 = String::new() -> [return: bb5, unwind: bb31]; + } + + bb5: { + StorageLive(_11); + _11 = String::new() -> [return: bb6, unwind: bb29]; + } + + bb6: { + drop(_7) -> [return: bb7, unwind: bb27]; + } + + bb7: { + StorageDead(_7); + drop(_6) -> [return: bb8, unwind: bb25]; + } + + bb8: { + StorageDead(_6); + StorageDead(_5); + drop(_4) -> [return: bb9, unwind: bb23]; + } + + bb9: { + StorageDead(_4); + drop(_2) -> [return: bb10, unwind: bb21]; + } + + bb10: { + drop(_1) -> [return: bb11, unwind: bb19]; + } + + bb11: { + tailcall g_with_arg(Spanned { node: move _10, span: $DIR/tail_call_drops.rs:36:23: 36:36 (#0) }, Spanned { node: move _11, span: $DIR/tail_call_drops.rs:36:38: 36:51 (#0) }); + } + + bb12: { + StorageDead(_11); + StorageDead(_10); + drop(_7) -> [return: bb13, unwind: bb32]; + } + + bb13: { + StorageDead(_7); + drop(_6) -> [return: bb14, unwind: bb33]; + } + + bb14: { + StorageDead(_6); + StorageDead(_5); + drop(_4) -> [return: bb15, unwind: bb34]; + } + + bb15: { + StorageDead(_4); + unreachable; + } + + bb16: { + drop(_2) -> [return: bb17, unwind: bb35]; + } + + bb17: { + drop(_1) -> [return: bb18, unwind: bb36]; + } + + bb18: { + return; + } + + bb19 (cleanup): { + drop(_10) -> [return: bb20, unwind terminate(cleanup)]; + } + + bb20 (cleanup): { + drop(_11) -> [return: bb36, unwind terminate(cleanup)]; + } + + bb21 (cleanup): { + drop(_10) -> [return: bb22, unwind terminate(cleanup)]; + } + + bb22 (cleanup): { + drop(_11) -> [return: bb35, unwind terminate(cleanup)]; + } + + bb23 (cleanup): { + drop(_10) -> [return: bb24, unwind terminate(cleanup)]; + } + + bb24 (cleanup): { + drop(_11) -> [return: bb34, unwind terminate(cleanup)]; + } + + bb25 (cleanup): { + drop(_10) -> [return: bb26, unwind terminate(cleanup)]; + } + + bb26 (cleanup): { + drop(_11) -> [return: bb33, unwind terminate(cleanup)]; + } + + bb27 (cleanup): { + drop(_10) -> [return: bb28, unwind terminate(cleanup)]; + } + + bb28 (cleanup): { + drop(_11) -> [return: bb32, unwind terminate(cleanup)]; + } + + bb29 (cleanup): { + drop(_10) -> [return: bb31, unwind terminate(cleanup)]; + } + + bb30 (cleanup): { + drop(_9) -> [return: bb31, unwind terminate(cleanup)]; + } + + bb31 (cleanup): { + drop(_7) -> [return: bb32, unwind terminate(cleanup)]; + } + + bb32 (cleanup): { + drop(_6) -> [return: bb33, unwind terminate(cleanup)]; + } + + bb33 (cleanup): { + drop(_4) -> [return: bb34, unwind terminate(cleanup)]; + } + + bb34 (cleanup): { + drop(_2) -> [return: bb35, unwind terminate(cleanup)]; + } + + bb35 (cleanup): { + drop(_1) -> [return: bb36, unwind terminate(cleanup)]; + } + + bb36 (cleanup): { + resume; + } +} diff --git a/tests/mir-opt/tail_call_drops.rs b/tests/mir-opt/tail_call_drops.rs new file mode 100644 index 0000000000000..56f4852a95f4b --- /dev/null +++ b/tests/mir-opt/tail_call_drops.rs @@ -0,0 +1,41 @@ +// skip-filecheck +// EMIT_MIR_FOR_EACH_PANIC_STRATEGY +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +// EMIT_MIR tail_call_drops.f.built.after.mir +// Expected result: +// drop(_d) -> drop(_c) -> drop(_a) -> tailcall g() +// +// EMIT_MIR tail_call_drops.f.ElaborateDrops.diff +// Expected result: +// drop(_d) -> drop(_a) -> tailcall g() +fn f() { + let _a = String::new(); + let _b = 12; + let _c = String::new(); + let _d = String::new(); + + drop(_c); + + become g(); +} + +fn g() {} + +// EMIT_MIR tail_call_drops.f_with_arg.built.after.mir +// EMIT_MIR tail_call_drops.f_with_arg.ElaborateDrops.diff +fn f_with_arg(_arg1: String, _arg2: String) { + let _a = String::new(); + let _b = 12; + let _c = String::new(); + let _d = String::new(); + + drop(_c); + + become g_with_arg(String::new(), String::new()); +} + +fn g_with_arg(_arg1: String, _arg2: String) {} + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/drop-order.rs b/tests/ui/explicit-tail-calls/drop-order.rs new file mode 100644 index 0000000000000..e20730446ec55 --- /dev/null +++ b/tests/ui/explicit-tail-calls/drop-order.rs @@ -0,0 +1,70 @@ +// FIXME(explicit_tail_calls): enable this test once rustc_codegen_ssa supports tail calls +//@ ignore-test: tail calls are not implemented in rustc_codegen_ssa yet, so this causes 🧊 +//@ run-pass +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] +use std::cell::RefCell; + +fn main() { + let tail_counter = Default::default(); + tail_recursive(0, &tail_counter); + assert_eq!(tail_counter.into_inner(), (0..128).collect::>()); + + let simply_counter = Default::default(); + simply_recursive(0, &simply_counter); + assert_eq!(simply_counter.into_inner(), (0..128).rev().collect::>()); + + let scope_counter = Default::default(); + out_of_inner_scope(&scope_counter); + assert_eq!(scope_counter.into_inner(), (0..8).collect::>()); +} + +fn tail_recursive(n: u8, order: &RefCell>) { + if n >= 128 { + return; + } + + let _local = DropCounter(n, order); + + become tail_recursive(n + 1, order) +} + +fn simply_recursive(n: u8, order: &RefCell>) { + if n >= 128 { + return; + } + + let _local = DropCounter(n, order); + + return simply_recursive(n + 1, order); +} + +fn out_of_inner_scope(order: &RefCell>) { + fn inner(order: &RefCell>) { + let _7 = DropCounter(7, order); + let _6 = DropCounter(6, order); + } + + let _5 = DropCounter(5, order); + let _4 = DropCounter(4, order); + + if true { + let _3 = DropCounter(3, order); + let _2 = DropCounter(2, order); + loop { + let _1 = DropCounter(1, order); + let _0 = DropCounter(0, order); + + become inner(order); + } + } +} + +struct DropCounter<'a>(u8, &'a RefCell>); + +impl Drop for DropCounter<'_> { + #[track_caller] + fn drop(&mut self) { + self.1.borrow_mut().push(self.0); + } +} From 3b5a5ee6c8b2b055e772de2163379d99e0d463df Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Wed, 28 Jun 2023 15:34:10 +0000 Subject: [PATCH 17/32] Support tail calls in the interpreter --- .../src/interpret/terminator.rs | 72 ++++++++++++++++++- .../ctfe-arg-bad-borrow.rs | 14 ++++ .../ctfe-arg-bad-borrow.stderr | 14 ++++ .../ctfe-arg-good-borrow.rs | 13 ++++ tests/ui/explicit-tail-calls/ctfe-arg-move.rs | 15 ++++ .../ctfe-collatz-multi-rec.rs | 43 +++++++++++ .../ctfe-id-unlimited.return.stderr | 36 ++++++++++ .../explicit-tail-calls/ctfe-id-unlimited.rs | 35 +++++++++ .../ctfe-tail-call-panic.rs | 19 +++++ .../ctfe-tail-call-panic.stderr | 21 ++++++ 10 files changed, 281 insertions(+), 1 deletion(-) create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-arg-move.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr create mode 100644 tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs create mode 100644 tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 7d1a48d6ded6f..d77bcdd709731 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -172,7 +172,77 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } - TailCall { func: _, args: _, fn_span: _ } => todo!(), + TailCall { ref func, ref args, fn_span: _ } => { + // FIXME(explicit_tail_calls): a lot of code here is duplicated with normal calls, can we refactor this? + let old_frame_idx = self.frame_idx(); + let func = self.eval_operand(func, None)?; + let args = self.eval_fn_call_arguments(args)?; + + let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx); + let fn_sig = + self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig_binder); + let extra_args = &args[fn_sig.inputs().len()..]; + let extra_args = + self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout().ty)); + + let (fn_val, fn_abi, with_caller_location) = match *func.layout.ty.kind() { + ty::FnPtr(_sig) => { + let fn_ptr = self.read_pointer(&func)?; + let fn_val = self.get_ptr_fn(fn_ptr)?; + (fn_val, self.fn_abi_of_fn_ptr(fn_sig_binder, extra_args)?, false) + } + ty::FnDef(def_id, substs) => { + let instance = self.resolve(def_id, substs)?; + ( + FnVal::Instance(instance), + self.fn_abi_of_instance(instance, extra_args)?, + instance.def.requires_caller_location(*self.tcx), + ) + } + _ => span_bug!( + terminator.source_info.span, + "invalid callee of type {:?}", + func.layout.ty + ), + }; + + // This is the "canonical" implementation of tails calls, + // a pop of the current stack frame, followed by a normal call + // which pushes a new stack frame, with the return address from + // the popped stack frame. + // + // Note that we can't use `pop_stack_frame` as it "executes" + // the goto to the return block, but we don't want to, + // only the tail called function should return to the current + // return block. + let Some(prev_frame) = self.stack_mut().pop() else { + span_bug!( + terminator.source_info.span, + "empty stack while evaluating this tail call" + ) + }; + + let StackPopCleanup::Goto { ret, unwind } = prev_frame.return_to_block else { + span_bug!(terminator.source_info.span, "tail call with the root stack frame") + }; + + self.eval_fn_call( + fn_val, + (fn_sig.abi, fn_abi), + &args, + with_caller_location, + &prev_frame.return_place, + ret, + unwind, + )?; + + if self.frame_idx() != old_frame_idx { + span_bug!( + terminator.source_info.span, + "evaluating this tail call pushed a new stack frame" + ); + } + } Drop { place, target, unwind, replace: _ } => { let place = self.eval_place(place)?; diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs new file mode 100644 index 0000000000000..5a105ee4eb59d --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.rs @@ -0,0 +1,14 @@ +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +pub const fn test(_: &Type) { + const fn takes_borrow(_: &Type) {} + + let local = Type; + become takes_borrow(&local); + //~^ error: `local` does not live long enough +} + +struct Type; + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr new file mode 100644 index 0000000000000..75fb13c378c24 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-bad-borrow.stderr @@ -0,0 +1,14 @@ +error[E0597]: `local` does not live long enough + --> $DIR/ctfe-arg-bad-borrow.rs:8:25 + | +LL | let local = Type; + | ----- binding `local` declared here +LL | become takes_borrow(&local); + | ^^^^^^ borrowed value does not live long enough +LL | +LL | } + | - `local` dropped here while still borrowed + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0597`. diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs b/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs new file mode 100644 index 0000000000000..50bf6c946ca8f --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-good-borrow.rs @@ -0,0 +1,13 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +pub const fn test(x: &Type) { + const fn takes_borrow(_: &Type) {} + + become takes_borrow(x); +} + +pub struct Type; + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-arg-move.rs b/tests/ui/explicit-tail-calls/ctfe-arg-move.rs new file mode 100644 index 0000000000000..88ff3a4a5ad13 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-arg-move.rs @@ -0,0 +1,15 @@ +//@ check-pass +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +pub const fn test(s: String) -> String { + const fn takes_string(s: String) -> String { + s + } + + become takes_string(s); +} + +struct Type; + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs new file mode 100644 index 0000000000000..2e6bb0e1ac5b7 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs @@ -0,0 +1,43 @@ +//@ run-pass +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +/// A very unnecessarily complicated "implementation" of the callatz conjecture. +/// Returns the number of steps to reach `1`. +/// +/// This is just a test for tail calls, which involves multiple functions calling each other. +/// +/// Panics if `x == 0`. +const fn collatz(x: u32) -> u32 { + assert!(x > 0); + + const fn switch(x: u32, steps: u32) -> u32 { + match x { + 1 => steps, + _ if x & 1 == 0 => become div2(x, steps + 1), + _ => become mul3plus1(x, steps + 1), + } + } + + const fn div2(x: u32, steps: u32) -> u32 { + become switch(x >> 1, steps) + } + + const fn mul3plus1(x: u32, steps: u32) -> u32 { + become switch(3 * x + 1, steps) + } + + switch(x, 0) +} + +const ASSERTS: () = { + assert!(collatz(1) == 0); + assert!(collatz(2) == 1); + assert!(collatz(3) == 7); + assert!(collatz(4) == 2); + assert!(collatz(6171) == 261); +}; + +fn main() { + let _ = ASSERTS; +} diff --git a/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr new file mode 100644 index 0000000000000..4a1e50b4111e3 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.return.stderr @@ -0,0 +1,36 @@ +error[E0080]: evaluation of constant value failed + --> $DIR/ctfe-id-unlimited.rs:17:42 + | +LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1), + | ^^^^^^^^^^^^^^^^^^^^^ reached the configured maximum number of stack frames + | +note: inside `inner` + --> $DIR/ctfe-id-unlimited.rs:17:42 + | +LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1), + | ^^^^^^^^^^^^^^^^^^^^^ +note: [... 125 additional calls inside `inner` ...] + --> $DIR/ctfe-id-unlimited.rs:17:42 + | +LL | #[cfg(r#return)] _ => return inner(acc + 1, n - 1), + | ^^^^^^^^^^^^^^^^^^^^^ +note: inside `rec_id` + --> $DIR/ctfe-id-unlimited.rs:22:5 + | +LL | inner(0, n) + | ^^^^^^^^^^^ +note: inside `ID_ED` + --> $DIR/ctfe-id-unlimited.rs:29:20 + | +LL | const ID_ED: u32 = rec_id(ORIGINAL); + | ^^^^^^^^^^^^^^^^ + +note: erroneous constant encountered + --> $DIR/ctfe-id-unlimited.rs:31:40 + | +LL | const ASSERT: () = assert!(ORIGINAL == ID_ED); + | ^^^^^ + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. diff --git a/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs new file mode 100644 index 0000000000000..54e68b2b7f793 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-id-unlimited.rs @@ -0,0 +1,35 @@ +//@ revisions: become return +//@ [become] run-pass +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +// This is an identity function (`|x| x`), but implemented using recursion. +// Each step we increment accumulator and decrement the number. +// +// With normal calls this fails compilation because of the recursion limit, +// but with tail calls/`become` we don't grow the stack/spend recursion limit +// so this should compile. +const fn rec_id(n: u32) -> u32 { + const fn inner(acc: u32, n: u32) -> u32 { + match n { + 0 => acc, + #[cfg(r#become)] _ => become inner(acc + 1, n - 1), + #[cfg(r#return)] _ => return inner(acc + 1, n - 1), + //[return]~^ error: evaluation of constant value failed + } + } + + inner(0, n) +} + +// Some relatively big number that is higher than recursion limit +const ORIGINAL: u32 = 12345; +// Original number, but with identity function applied +// (this is the same, but requires execution of the recursion) +const ID_ED: u32 = rec_id(ORIGINAL); +// Assert to make absolutely sure the computation actually happens +const ASSERT: () = assert!(ORIGINAL == ID_ED); + +fn main() { + let _ = ASSERT; +} diff --git a/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs new file mode 100644 index 0000000000000..3d69cde29895a --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.rs @@ -0,0 +1,19 @@ +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +pub const fn f() { + become g(); +} + +const fn g() { + panic!() + //~^ error: evaluation of constant value failed + //~| note: in this expansion of panic! + //~| note: inside `g` + //~| note: in this expansion of panic! +} + +const _: () = f(); +//~^ note: inside `_` + +fn main() {} diff --git a/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr new file mode 100644 index 0000000000000..8c07051210578 --- /dev/null +++ b/tests/ui/explicit-tail-calls/ctfe-tail-call-panic.stderr @@ -0,0 +1,21 @@ +error[E0080]: evaluation of constant value failed + --> $DIR/ctfe-tail-call-panic.rs:9:5 + | +LL | panic!() + | ^^^^^^^^ the evaluated program panicked at 'explicit panic', $DIR/ctfe-tail-call-panic.rs:9:5 + | +note: inside `g` + --> $DIR/ctfe-tail-call-panic.rs:9:5 + | +LL | panic!() + | ^^^^^^^^ +note: inside `_` + --> $DIR/ctfe-tail-call-panic.rs:16:15 + | +LL | const _: () = f(); + | ^^^ + = note: this error originates in the macro `$crate::panic::panic_2015` which comes from the expansion of the macro `panic` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: aborting due to 1 previous error + +For more information about this error, try `rustc --explain E0080`. From 45c70318f796cb20f47120ebe9de66919f948f91 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 30 Jun 2023 18:27:43 +0000 Subject: [PATCH 18/32] Refactor common part of evaluating `Call`&`TailCall` in the interpreter --- .../src/interpret/terminator.rs | 119 +++++++++--------- 1 file changed, 56 insertions(+), 63 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index d77bcdd709731..6644e70e039a8 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -47,6 +47,15 @@ impl<'tcx, Prov: Provenance> FnArg<'tcx, Prov> { } } +struct EvaluatedCalleeAndArgs<'tcx, M: Machine<'tcx>> { + callee: FnVal<'tcx, M::ExtraFnVal>, + args: Vec>, + fn_sig: ty::FnSig<'tcx>, + fn_abi: &'tcx FnAbi<'tcx, Ty<'tcx>>, + /// True if the function is marked as `#[track_caller]` ([`ty::InstanceDef::requires_caller_location`]) + with_caller_location: bool, +} + impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// Make a copy of the given fn_arg. Any `InPlace` are degenerated to copies, no protection of the /// original memory occurs. @@ -124,40 +133,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } => { let old_stack = self.frame_idx(); let old_loc = self.frame().loc; - let func = self.eval_operand(func, None)?; - let args = self.eval_fn_call_arguments(args)?; - - let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx); - let fn_sig = - self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig_binder); - let extra_args = &args[fn_sig.inputs().len()..]; - let extra_args = - self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout().ty)); - - let (fn_val, fn_abi, with_caller_location) = match *func.layout.ty.kind() { - ty::FnPtr(_sig) => { - let fn_ptr = self.read_pointer(&func)?; - let fn_val = self.get_ptr_fn(fn_ptr)?; - (fn_val, self.fn_abi_of_fn_ptr(fn_sig_binder, extra_args)?, false) - } - ty::FnDef(def_id, args) => { - let instance = self.resolve(def_id, args)?; - ( - FnVal::Instance(instance), - self.fn_abi_of_instance(instance, extra_args)?, - instance.def.requires_caller_location(*self.tcx), - ) - } - _ => span_bug!( - terminator.source_info.span, - "invalid callee of type {}", - func.layout.ty - ), - }; + + let EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location } = + self.eval_callee_and_args(terminator, func, args)?; let destination = self.force_allocation(&self.eval_place(destination)?)?; self.eval_fn_call( - fn_val, + callee, (fn_sig.abi, fn_abi), &args, with_caller_location, @@ -173,38 +155,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } TailCall { ref func, ref args, fn_span: _ } => { - // FIXME(explicit_tail_calls): a lot of code here is duplicated with normal calls, can we refactor this? let old_frame_idx = self.frame_idx(); - let func = self.eval_operand(func, None)?; - let args = self.eval_fn_call_arguments(args)?; - - let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx); - let fn_sig = - self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig_binder); - let extra_args = &args[fn_sig.inputs().len()..]; - let extra_args = - self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout().ty)); - - let (fn_val, fn_abi, with_caller_location) = match *func.layout.ty.kind() { - ty::FnPtr(_sig) => { - let fn_ptr = self.read_pointer(&func)?; - let fn_val = self.get_ptr_fn(fn_ptr)?; - (fn_val, self.fn_abi_of_fn_ptr(fn_sig_binder, extra_args)?, false) - } - ty::FnDef(def_id, substs) => { - let instance = self.resolve(def_id, substs)?; - ( - FnVal::Instance(instance), - self.fn_abi_of_instance(instance, extra_args)?, - instance.def.requires_caller_location(*self.tcx), - ) - } - _ => span_bug!( - terminator.source_info.span, - "invalid callee of type {:?}", - func.layout.ty - ), - }; + + let EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location } = + self.eval_callee_and_args(terminator, func, args)?; // This is the "canonical" implementation of tails calls, // a pop of the current stack frame, followed by a normal call @@ -227,7 +181,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }; self.eval_fn_call( - fn_val, + callee, (fn_sig.abi, fn_abi), &args, with_caller_location, @@ -586,6 +540,45 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { Ok(()) } + /// Shared part of `Call` and `TailCall` implementation — finding and evaluating all the + /// necessary information about callee and arguments to make a call. + fn eval_callee_and_args( + &self, + terminator: &mir::Terminator<'tcx>, + func: &mir::Operand<'tcx>, + args: &[Spanned>], + ) -> InterpResult<'tcx, EvaluatedCalleeAndArgs<'tcx, M>> { + let func = self.eval_operand(func, None)?; + let args = self.eval_fn_call_arguments(args)?; + + let fn_sig_binder = func.layout.ty.fn_sig(*self.tcx); + let fn_sig = self.tcx.normalize_erasing_late_bound_regions(self.param_env, fn_sig_binder); + let extra_args = &args[fn_sig.inputs().len()..]; + let extra_args = + self.tcx.mk_type_list_from_iter(extra_args.iter().map(|arg| arg.layout().ty)); + + let (callee, fn_abi, with_caller_location) = match *func.layout.ty.kind() { + ty::FnPtr(_sig) => { + let fn_ptr = self.read_pointer(&func)?; + let fn_val = self.get_ptr_fn(fn_ptr)?; + (fn_val, self.fn_abi_of_fn_ptr(fn_sig_binder, extra_args)?, false) + } + ty::FnDef(def_id, args) => { + let instance = self.resolve(def_id, args)?; + ( + FnVal::Instance(instance), + self.fn_abi_of_instance(instance, extra_args)?, + instance.def.requires_caller_location(*self.tcx), + ) + } + _ => { + span_bug!(terminator.source_info.span, "invalid callee of type {}", func.layout.ty) + } + }; + + Ok(EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location }) + } + /// Call this function -- pushing the stack frame and initializing the arguments. /// /// `caller_fn_abi` is used to determine if all the arguments are passed the proper way. From 5f4caae11c7a640350fbe90ddc465d8e2fa0156e Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 18 Jul 2023 16:25:59 +0000 Subject: [PATCH 19/32] Fix unconditional recursion lint wrt tail calls --- compiler/rustc_mir_build/src/lints.rs | 18 ++++++++++++-- ...lint-unconditional-recursion-tail-calls.rs | 24 +++++++++++++++++++ ...-unconditional-recursion-tail-calls.stderr | 18 ++++++++++++++ 3 files changed, 58 insertions(+), 2 deletions(-) create mode 100644 tests/ui/lint/lint-unconditional-recursion-tail-calls.rs create mode 100644 tests/ui/lint/lint-unconditional-recursion-tail-calls.stderr diff --git a/compiler/rustc_mir_build/src/lints.rs b/compiler/rustc_mir_build/src/lints.rs index 3cb83a48ffe1b..263e777d03ae1 100644 --- a/compiler/rustc_mir_build/src/lints.rs +++ b/compiler/rustc_mir_build/src/lints.rs @@ -196,8 +196,6 @@ impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor ControlFlow::Break(NonRecursive), @@ -219,12 +217,28 @@ impl<'mir, 'tcx, C: TerminatorClassifier<'tcx>> TriColorVisitor ControlFlow::Continue(()), + + // Note that tail call terminator technically returns to the caller, + // but for purposes of this lint it makes sense to count it as possibly recursive, + // since it's still a call. + // + // If this'll be repurposed for something else, this might need to be changed. + TerminatorKind::TailCall { .. } => ControlFlow::Continue(()), } } fn node_settled(&mut self, bb: BasicBlock) -> ControlFlow { // When we examine a node for the last time, remember it if it is a recursive call. let terminator = self.body[bb].terminator(); + + // FIXME(explicit_tail_calls): highlight tail calls as "recursive call site" + // + // We don't want to lint functions that recurse only through tail calls + // (such as `fn g() { become () }`), so just adding `| TailCall { ... }` + // here won't work. + // + // But at the same time we would like to highlight both calls in a function like + // `fn f() { if false { become f() } else { f() } }`, so we need to figure something out. if self.classifier.is_recursive_terminator(self.tcx, self.body, terminator) { self.reachable_recursive_calls.push(terminator.source_info.span); } diff --git a/tests/ui/lint/lint-unconditional-recursion-tail-calls.rs b/tests/ui/lint/lint-unconditional-recursion-tail-calls.rs new file mode 100644 index 0000000000000..c94bf03257972 --- /dev/null +++ b/tests/ui/lint/lint-unconditional-recursion-tail-calls.rs @@ -0,0 +1,24 @@ +#![allow(incomplete_features, dead_code)] +#![deny(unconditional_recursion)] //~ note: the lint level is defined here +#![feature(explicit_tail_calls)] + +fn f(x: bool) { + //~^ error: function cannot return without recursing + //~| note: cannot return without recursing + if x { + become f(!x) + } else { + f(!x) //~ note: recursive call site + } +} + +// This should *not* lint, tail-recursive functions which never return is a reasonable thing +fn g(x: bool) { + if x { + become g(!x) + } else { + become g(!x) + } +} + +fn main() {} diff --git a/tests/ui/lint/lint-unconditional-recursion-tail-calls.stderr b/tests/ui/lint/lint-unconditional-recursion-tail-calls.stderr new file mode 100644 index 0000000000000..52f9740d027e4 --- /dev/null +++ b/tests/ui/lint/lint-unconditional-recursion-tail-calls.stderr @@ -0,0 +1,18 @@ +error: function cannot return without recursing + --> $DIR/lint-unconditional-recursion-tail-calls.rs:5:1 + | +LL | fn f(x: bool) { + | ^^^^^^^^^^^^^ cannot return without recursing +... +LL | f(!x) + | ----- recursive call site + | + = help: a `loop` may express intention better if this is on purpose +note: the lint level is defined here + --> $DIR/lint-unconditional-recursion-tail-calls.rs:2:9 + | +LL | #![deny(unconditional_recursion)] + | ^^^^^^^^^^^^^^^^^^^^^^^ + +error: aborting due to 1 previous error + From 30b18d7c3621e4b6c2cb84b7f84e72c8efd3ff3f Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Tue, 9 May 2023 21:30:28 +0000 Subject: [PATCH 20/32] Add support for `mir::TerminatorKind::TailCall` in clippy --- src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs index 42b10f69c0cdf..f206b2ceebcb1 100644 --- a/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs +++ b/src/tools/clippy/clippy_utils/src/qualify_min_const_fn.rs @@ -330,7 +330,8 @@ fn check_terminator<'tcx>( target: _, unwind: _, fn_span: _, - } => { + } + | TerminatorKind::TailCall { func, args, fn_span: _ } => { let fn_ty = func.ty(body, tcx); if let ty::FnDef(fn_def_id, _) = *fn_ty.kind() { if !is_const_fn(tcx, fn_def_id, msrv) { From 90423a7abbddd98b6fbb22e9780991c736b51ca4 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 6 Jul 2024 12:21:00 -0400 Subject: [PATCH 21/32] Uplift elaboration --- compiler/rustc_infer/src/traits/util.rs | 349 +----------------- compiler/rustc_middle/src/ty/context.rs | 21 +- .../rustc_middle/src/ty/elaborate_impl.rs | 84 +++++ compiler/rustc_middle/src/ty/mod.rs | 1 + compiler/rustc_middle/src/ty/predicate.rs | 10 +- compiler/rustc_middle/src/ty/sty.rs | 8 + .../src/solve/assembly/structural_traits.rs | 4 +- compiler/rustc_type_ir/src/elaborate.rs | 277 ++++++++++++++ compiler/rustc_type_ir/src/inherent.rs | 37 +- compiler/rustc_type_ir/src/interner.rs | 14 +- compiler/rustc_type_ir/src/lib.rs | 1 + 11 files changed, 437 insertions(+), 369 deletions(-) create mode 100644 compiler/rustc_middle/src/ty/elaborate_impl.rs create mode 100644 compiler/rustc_type_ir/src/elaborate.rs diff --git a/compiler/rustc_infer/src/traits/util.rs b/compiler/rustc_infer/src/traits/util.rs index b269bfcbfebc2..f54d041859581 100644 --- a/compiler/rustc_infer/src/traits/util.rs +++ b/compiler/rustc_infer/src/traits/util.rs @@ -1,12 +1,10 @@ -use smallvec::smallvec; - use crate::traits::{self, Obligation, ObligationCauseCode, PredicateObligation}; use rustc_data_structures::fx::FxHashSet; use rustc_middle::ty::ToPolyTraitRef; -use rustc_middle::ty::{self, Ty, TyCtxt, Upcast}; +use rustc_middle::ty::{self, TyCtxt}; use rustc_span::symbol::Ident; use rustc_span::Span; -use rustc_type_ir::outlives::{push_outlives_components, Component}; +pub use rustc_type_ir::elaborate::*; pub fn anonymize_predicate<'tcx>( tcx: TyCtxt<'tcx>, @@ -64,50 +62,9 @@ impl<'tcx> Extend> for PredicateSet<'tcx> { } } -/////////////////////////////////////////////////////////////////////////// -// `Elaboration` iterator -/////////////////////////////////////////////////////////////////////////// - -/// "Elaboration" is the process of identifying all the predicates that -/// are implied by a source predicate. Currently, this basically means -/// walking the "supertraits" and other similar assumptions. For example, -/// if we know that `T: Ord`, the elaborator would deduce that `T: PartialOrd` -/// holds as well. Similarly, if we have `trait Foo: 'static`, and we know that -/// `T: Foo`, then we know that `T: 'static`. -pub struct Elaborator<'tcx, O> { - stack: Vec, - visited: PredicateSet<'tcx>, - mode: Filter, -} - -enum Filter { - All, - OnlySelf, -} - -/// Describes how to elaborate an obligation into a sub-obligation. -/// /// For [`Obligation`], a sub-obligation is combined with the current obligation's -/// param-env and cause code. For [`ty::Predicate`], none of this is needed, since -/// there is no param-env or cause code to copy over. -pub trait Elaboratable<'tcx> { - fn predicate(&self) -> ty::Predicate<'tcx>; - - // Makes a new `Self` but with a different clause that comes from elaboration. - fn child(&self, clause: ty::Clause<'tcx>) -> Self; - - // Makes a new `Self` but with a different clause and a different cause - // code (if `Self` has one, such as [`PredicateObligation`]). - fn child_with_derived_cause( - &self, - clause: ty::Clause<'tcx>, - span: Span, - parent_trait_pred: ty::PolyTraitPredicate<'tcx>, - index: usize, - ) -> Self; -} - -impl<'tcx> Elaboratable<'tcx> for PredicateObligation<'tcx> { +/// param-env and cause code. +impl<'tcx> Elaboratable> for PredicateObligation<'tcx> { fn predicate(&self) -> ty::Predicate<'tcx> { self.predicate } @@ -145,270 +102,6 @@ impl<'tcx> Elaboratable<'tcx> for PredicateObligation<'tcx> { } } -impl<'tcx> Elaboratable<'tcx> for ty::Predicate<'tcx> { - fn predicate(&self) -> ty::Predicate<'tcx> { - *self - } - - fn child(&self, clause: ty::Clause<'tcx>) -> Self { - clause.as_predicate() - } - - fn child_with_derived_cause( - &self, - clause: ty::Clause<'tcx>, - _span: Span, - _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, - _index: usize, - ) -> Self { - clause.as_predicate() - } -} - -impl<'tcx> Elaboratable<'tcx> for (ty::Predicate<'tcx>, Span) { - fn predicate(&self) -> ty::Predicate<'tcx> { - self.0 - } - - fn child(&self, clause: ty::Clause<'tcx>) -> Self { - (clause.as_predicate(), self.1) - } - - fn child_with_derived_cause( - &self, - clause: ty::Clause<'tcx>, - _span: Span, - _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, - _index: usize, - ) -> Self { - (clause.as_predicate(), self.1) - } -} - -impl<'tcx> Elaboratable<'tcx> for (ty::Clause<'tcx>, Span) { - fn predicate(&self) -> ty::Predicate<'tcx> { - self.0.as_predicate() - } - - fn child(&self, clause: ty::Clause<'tcx>) -> Self { - (clause, self.1) - } - - fn child_with_derived_cause( - &self, - clause: ty::Clause<'tcx>, - _span: Span, - _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, - _index: usize, - ) -> Self { - (clause, self.1) - } -} - -impl<'tcx> Elaboratable<'tcx> for ty::Clause<'tcx> { - fn predicate(&self) -> ty::Predicate<'tcx> { - self.as_predicate() - } - - fn child(&self, clause: ty::Clause<'tcx>) -> Self { - clause - } - - fn child_with_derived_cause( - &self, - clause: ty::Clause<'tcx>, - _span: Span, - _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, - _index: usize, - ) -> Self { - clause - } -} - -pub fn elaborate<'tcx, O: Elaboratable<'tcx>>( - tcx: TyCtxt<'tcx>, - obligations: impl IntoIterator, -) -> Elaborator<'tcx, O> { - let mut elaborator = - Elaborator { stack: Vec::new(), visited: PredicateSet::new(tcx), mode: Filter::All }; - elaborator.extend_deduped(obligations); - elaborator -} - -impl<'tcx, O: Elaboratable<'tcx>> Elaborator<'tcx, O> { - fn extend_deduped(&mut self, obligations: impl IntoIterator) { - // Only keep those bounds that we haven't already seen. - // This is necessary to prevent infinite recursion in some - // cases. One common case is when people define - // `trait Sized: Sized { }` rather than `trait Sized { }`. - // let visited = &mut self.visited; - self.stack.extend(obligations.into_iter().filter(|o| self.visited.insert(o.predicate()))); - } - - /// Filter to only the supertraits of trait predicates, i.e. only the predicates - /// that have `Self` as their self type, instead of all implied predicates. - pub fn filter_only_self(mut self) -> Self { - self.mode = Filter::OnlySelf; - self - } - - fn elaborate(&mut self, elaboratable: &O) { - let tcx = self.visited.tcx; - - // We only elaborate clauses. - let Some(clause) = elaboratable.predicate().as_clause() else { - return; - }; - - let bound_clause = clause.kind(); - match bound_clause.skip_binder() { - ty::ClauseKind::Trait(data) => { - // Negative trait bounds do not imply any supertrait bounds - if data.polarity != ty::PredicatePolarity::Positive { - return; - } - // Get predicates implied by the trait, or only super predicates if we only care about self predicates. - let predicates = match self.mode { - Filter::All => tcx.explicit_implied_predicates_of(data.def_id()), - Filter::OnlySelf => tcx.explicit_super_predicates_of(data.def_id()), - }; - - let obligations = - predicates.predicates.iter().enumerate().map(|(index, &(clause, span))| { - elaboratable.child_with_derived_cause( - clause.instantiate_supertrait(tcx, bound_clause.rebind(data.trait_ref)), - span, - bound_clause.rebind(data), - index, - ) - }); - debug!(?data, ?obligations, "super_predicates"); - self.extend_deduped(obligations); - } - ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty_max, r_min)) => { - // We know that `T: 'a` for some type `T`. We can - // often elaborate this. For example, if we know that - // `[U]: 'a`, that implies that `U: 'a`. Similarly, if - // we know `&'a U: 'b`, then we know that `'a: 'b` and - // `U: 'b`. - // - // We can basically ignore bound regions here. So for - // example `for<'c> Foo<'a,'c>: 'b` can be elaborated to - // `'a: 'b`. - - // Ignore `for<'a> T: 'a` -- we might in the future - // consider this as evidence that `T: 'static`, but - // I'm a bit wary of such constructions and so for now - // I want to be conservative. --nmatsakis - if r_min.is_bound() { - return; - } - - let mut components = smallvec![]; - push_outlives_components(tcx, ty_max, &mut components); - self.extend_deduped( - components - .into_iter() - .filter_map(|component| match component { - Component::Region(r) => { - if r.is_bound() { - None - } else { - Some(ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate( - r, r_min, - ))) - } - } - - Component::Param(p) => { - let ty = Ty::new_param(tcx, p.index, p.name); - Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, r_min))) - } - - Component::Placeholder(p) => { - let ty = Ty::new_placeholder(tcx, p); - Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, r_min))) - } - - Component::UnresolvedInferenceVariable(_) => None, - - Component::Alias(alias_ty) => { - // We might end up here if we have `Foo<::Assoc>: 'a`. - // With this, we can deduce that `::Assoc: 'a`. - Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate( - alias_ty.to_ty(tcx), - r_min, - ))) - } - - Component::EscapingAlias(_) => { - // We might be able to do more here, but we don't - // want to deal with escaping vars right now. - None - } - }) - .map(|clause| elaboratable.child(bound_clause.rebind(clause).upcast(tcx))), - ); - } - ty::ClauseKind::RegionOutlives(..) => { - // Nothing to elaborate from `'a: 'b`. - } - ty::ClauseKind::WellFormed(..) => { - // Currently, we do not elaborate WF predicates, - // although we easily could. - } - ty::ClauseKind::Projection(..) => { - // Nothing to elaborate in a projection predicate. - } - ty::ClauseKind::ConstEvaluatable(..) => { - // Currently, we do not elaborate const-evaluatable - // predicates. - } - ty::ClauseKind::ConstArgHasType(..) => { - // Nothing to elaborate - } - } - } -} - -impl<'tcx, O: Elaboratable<'tcx>> Iterator for Elaborator<'tcx, O> { - type Item = O; - - fn size_hint(&self) -> (usize, Option) { - (self.stack.len(), None) - } - - fn next(&mut self) -> Option { - // Extract next item from top-most stack frame, if any. - if let Some(obligation) = self.stack.pop() { - self.elaborate(&obligation); - Some(obligation) - } else { - None - } - } -} - -/////////////////////////////////////////////////////////////////////////// -// Supertrait iterator -/////////////////////////////////////////////////////////////////////////// - -pub fn supertraits<'tcx>( - tcx: TyCtxt<'tcx>, - trait_ref: ty::PolyTraitRef<'tcx>, -) -> FilterToTraits>> { - elaborate(tcx, [trait_ref.upcast(tcx)]).filter_only_self().filter_to_traits() -} - -pub fn transitive_bounds<'tcx>( - tcx: TyCtxt<'tcx>, - trait_refs: impl Iterator>, -) -> FilterToTraits>> { - elaborate(tcx, trait_refs.map(|trait_ref| trait_ref.upcast(tcx))) - .filter_only_self() - .filter_to_traits() -} - /// A specialized variant of `elaborate` that only elaborates trait references that may /// define the given associated item with the name `assoc_name`. It uses the /// `explicit_supertraits_containing_assoc_item` query to avoid enumerating super-predicates that @@ -443,37 +136,3 @@ pub fn transitive_bounds_that_define_assoc_item<'tcx>( None }) } - -/////////////////////////////////////////////////////////////////////////// -// Other -/////////////////////////////////////////////////////////////////////////// - -impl<'tcx> Elaborator<'tcx, ty::Clause<'tcx>> { - fn filter_to_traits(self) -> FilterToTraits { - FilterToTraits { base_iterator: self } - } -} - -/// A filter around an iterator of predicates that makes it yield up -/// just trait references. -pub struct FilterToTraits { - base_iterator: I, -} - -impl<'tcx, I: Iterator>> Iterator for FilterToTraits { - type Item = ty::PolyTraitRef<'tcx>; - - fn next(&mut self) -> Option> { - while let Some(pred) = self.base_iterator.next() { - if let Some(data) = pred.as_trait_clause() { - return Some(data.map_bound(|t| t.trait_ref)); - } - } - None - } - - fn size_hint(&self) -> (usize, Option) { - let (_, upper) = self.base_iterator.size_hint(); - (0, upper) - } -} diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index dca48069974f0..59ab5d791399b 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -347,12 +347,16 @@ impl<'tcx> Interner for TyCtxt<'tcx> { fn explicit_super_predicates_of( self, def_id: DefId, - ) -> ty::EarlyBinder<'tcx, impl IntoIterator>> { + ) -> ty::EarlyBinder<'tcx, impl IntoIterator, Span)>> { + ty::EarlyBinder::bind(self.explicit_super_predicates_of(def_id).instantiate_identity(self)) + } + + fn explicit_implied_predicates_of( + self, + def_id: DefId, + ) -> ty::EarlyBinder<'tcx, impl IntoIterator, Span)>> { ty::EarlyBinder::bind( - self.explicit_super_predicates_of(def_id) - .instantiate_identity(self) - .predicates - .into_iter(), + self.explicit_implied_predicates_of(def_id).instantiate_identity(self), ) } @@ -569,6 +573,13 @@ impl<'tcx> Interner for TyCtxt<'tcx> { ) -> Ty<'tcx> { placeholder.find_const_ty_from_env(param_env) } + + fn anonymize_bound_vars>>( + self, + binder: ty::Binder<'tcx, T>, + ) -> ty::Binder<'tcx, T> { + self.anonymize_bound_vars(binder) + } } macro_rules! bidirectional_lang_item_map { diff --git a/compiler/rustc_middle/src/ty/elaborate_impl.rs b/compiler/rustc_middle/src/ty/elaborate_impl.rs new file mode 100644 index 0000000000000..8c89a2d884b30 --- /dev/null +++ b/compiler/rustc_middle/src/ty/elaborate_impl.rs @@ -0,0 +1,84 @@ +use rustc_span::Span; +use rustc_type_ir::elaborate::Elaboratable; + +use crate::ty::{self, TyCtxt}; + +impl<'tcx> Elaboratable> for ty::Clause<'tcx> { + fn predicate(&self) -> ty::Predicate<'tcx> { + self.as_predicate() + } + + fn child(&self, clause: ty::Clause<'tcx>) -> Self { + clause + } + + fn child_with_derived_cause( + &self, + clause: ty::Clause<'tcx>, + _span: Span, + _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, + _index: usize, + ) -> Self { + clause + } +} + +impl<'tcx> Elaboratable> for ty::Predicate<'tcx> { + fn predicate(&self) -> ty::Predicate<'tcx> { + *self + } + + fn child(&self, clause: ty::Clause<'tcx>) -> Self { + clause.as_predicate() + } + + fn child_with_derived_cause( + &self, + clause: ty::Clause<'tcx>, + _span: Span, + _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, + _index: usize, + ) -> Self { + clause.as_predicate() + } +} + +impl<'tcx> Elaboratable> for (ty::Predicate<'tcx>, Span) { + fn predicate(&self) -> ty::Predicate<'tcx> { + self.0 + } + + fn child(&self, clause: ty::Clause<'tcx>) -> Self { + (clause.as_predicate(), self.1) + } + + fn child_with_derived_cause( + &self, + clause: ty::Clause<'tcx>, + _span: Span, + _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, + _index: usize, + ) -> Self { + (clause.as_predicate(), self.1) + } +} + +impl<'tcx> Elaboratable> for (ty::Clause<'tcx>, Span) { + fn predicate(&self) -> ty::Predicate<'tcx> { + self.0.as_predicate() + } + + fn child(&self, clause: ty::Clause<'tcx>) -> Self { + (clause, self.1) + } + + fn child_with_derived_cause( + &self, + clause: ty::Clause<'tcx>, + _span: Span, + _parent_trait_pred: ty::PolyTraitPredicate<'tcx>, + _index: usize, + ) -> Self { + (clause, self.1) + } +} diff --git a/compiler/rustc_middle/src/ty/mod.rs b/compiler/rustc_middle/src/ty/mod.rs index 7d57d88f40f4a..4470db47474cb 100644 --- a/compiler/rustc_middle/src/ty/mod.rs +++ b/compiler/rustc_middle/src/ty/mod.rs @@ -148,6 +148,7 @@ mod closure; mod consts; mod context; mod diagnostics; +mod elaborate_impl; mod erase_regions; mod generic_args; mod generics; diff --git a/compiler/rustc_middle/src/ty/predicate.rs b/compiler/rustc_middle/src/ty/predicate.rs index e9b37503bb3e5..c2cc3be3aaa56 100644 --- a/compiler/rustc_middle/src/ty/predicate.rs +++ b/compiler/rustc_middle/src/ty/predicate.rs @@ -46,6 +46,10 @@ pub struct Predicate<'tcx>( ); impl<'tcx> rustc_type_ir::inherent::Predicate> for Predicate<'tcx> { + fn as_clause(self) -> Option> { + self.as_clause() + } + fn is_coinductive(self, interner: TyCtxt<'tcx>) -> bool { self.is_coinductive(interner) } @@ -173,7 +177,11 @@ pub struct Clause<'tcx>( pub(super) Interned<'tcx, WithCachedTypeInfo>>>, ); -impl<'tcx> rustc_type_ir::inherent::Clause> for Clause<'tcx> {} +impl<'tcx> rustc_type_ir::inherent::Clause> for Clause<'tcx> { + fn instantiate_supertrait(self, tcx: TyCtxt<'tcx>, trait_ref: ty::PolyTraitRef<'tcx>) -> Self { + self.instantiate_supertrait(tcx, trait_ref) + } +} impl<'tcx> rustc_type_ir::inherent::IntoKind for Clause<'tcx> { type Kind = ty::Binder<'tcx, ClauseKind<'tcx>>; diff --git a/compiler/rustc_middle/src/ty/sty.rs b/compiler/rustc_middle/src/ty/sty.rs index 52690ae678d6c..d2b444a066bcc 100644 --- a/compiler/rustc_middle/src/ty/sty.rs +++ b/compiler/rustc_middle/src/ty/sty.rs @@ -811,6 +811,14 @@ impl<'tcx> rustc_type_ir::inherent::Ty> for Ty<'tcx> { Ty::new_var(tcx, vid) } + fn new_param(tcx: TyCtxt<'tcx>, param: ty::ParamTy) -> Self { + Ty::new_param(tcx, param.index, param.name) + } + + fn new_placeholder(tcx: TyCtxt<'tcx>, placeholder: ty::PlaceholderType) -> Self { + Ty::new_placeholder(tcx, placeholder) + } + fn new_bound(interner: TyCtxt<'tcx>, debruijn: ty::DebruijnIndex, var: ty::BoundTy) -> Self { Ty::new_bound(interner, debruijn, var) } diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs index 3447b39fa5b40..7df14e81ab5d4 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/structural_traits.rs @@ -669,7 +669,9 @@ where let cx = ecx.cx(); let mut requirements = vec![]; requirements.extend( - cx.explicit_super_predicates_of(trait_ref.def_id).iter_instantiated(cx, trait_ref.args), + cx.explicit_super_predicates_of(trait_ref.def_id) + .iter_instantiated(cx, trait_ref.args) + .map(|(pred, _)| pred), ); // FIXME(associated_const_equality): Also add associated consts to diff --git a/compiler/rustc_type_ir/src/elaborate.rs b/compiler/rustc_type_ir/src/elaborate.rs new file mode 100644 index 0000000000000..04cdf29a77799 --- /dev/null +++ b/compiler/rustc_type_ir/src/elaborate.rs @@ -0,0 +1,277 @@ +use std::marker::PhantomData; + +use smallvec::smallvec; + +use crate::data_structures::HashSet; +use crate::outlives::{push_outlives_components, Component}; +use crate::{self as ty, Interner}; +use crate::{inherent::*, Upcast as _}; + +/// "Elaboration" is the process of identifying all the predicates that +/// are implied by a source predicate. Currently, this basically means +/// walking the "supertraits" and other similar assumptions. For example, +/// if we know that `T: Ord`, the elaborator would deduce that `T: PartialOrd` +/// holds as well. Similarly, if we have `trait Foo: 'static`, and we know that +/// `T: Foo`, then we know that `T: 'static`. +pub struct Elaborator { + cx: I, + stack: Vec, + visited: HashSet>>, + mode: Filter, +} + +enum Filter { + All, + OnlySelf, +} + +/// Describes how to elaborate an obligation into a sub-obligation. +pub trait Elaboratable { + fn predicate(&self) -> I::Predicate; + + // Makes a new `Self` but with a different clause that comes from elaboration. + fn child(&self, clause: I::Clause) -> Self; + + // Makes a new `Self` but with a different clause and a different cause + // code (if `Self` has one, such as [`PredicateObligation`]). + fn child_with_derived_cause( + &self, + clause: I::Clause, + span: I::Span, + parent_trait_pred: ty::Binder>, + index: usize, + ) -> Self; +} + +pub fn elaborate>( + cx: I, + obligations: impl IntoIterator, +) -> Elaborator { + let mut elaborator = + Elaborator { cx, stack: Vec::new(), visited: HashSet::default(), mode: Filter::All }; + elaborator.extend_deduped(obligations); + elaborator +} + +impl> Elaborator { + fn extend_deduped(&mut self, obligations: impl IntoIterator) { + // Only keep those bounds that we haven't already seen. + // This is necessary to prevent infinite recursion in some + // cases. One common case is when people define + // `trait Sized: Sized { }` rather than `trait Sized { }`. + self.stack.extend( + obligations.into_iter().filter(|o| { + self.visited.insert(self.cx.anonymize_bound_vars(o.predicate().kind())) + }), + ); + } + + /// Filter to only the supertraits of trait predicates, i.e. only the predicates + /// that have `Self` as their self type, instead of all implied predicates. + pub fn filter_only_self(mut self) -> Self { + self.mode = Filter::OnlySelf; + self + } + + fn elaborate(&mut self, elaboratable: &O) { + let cx = self.cx; + + // We only elaborate clauses. + let Some(clause) = elaboratable.predicate().as_clause() else { + return; + }; + + let bound_clause = clause.kind(); + match bound_clause.skip_binder() { + ty::ClauseKind::Trait(data) => { + // Negative trait bounds do not imply any supertrait bounds + if data.polarity != ty::PredicatePolarity::Positive { + return; + } + + let map_to_child_clause = + |(index, (clause, span)): (usize, (I::Clause, I::Span))| { + elaboratable.child_with_derived_cause( + clause.instantiate_supertrait(cx, bound_clause.rebind(data.trait_ref)), + span, + bound_clause.rebind(data), + index, + ) + }; + + // Get predicates implied by the trait, or only super predicates if we only care about self predicates. + match self.mode { + Filter::All => self.extend_deduped( + cx.explicit_implied_predicates_of(data.def_id()) + .iter_identity() + .enumerate() + .map(map_to_child_clause), + ), + Filter::OnlySelf => self.extend_deduped( + cx.explicit_super_predicates_of(data.def_id()) + .iter_identity() + .enumerate() + .map(map_to_child_clause), + ), + }; + } + ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty_max, r_min)) => { + // We know that `T: 'a` for some type `T`. We can + // often elaborate this. For example, if we know that + // `[U]: 'a`, that implies that `U: 'a`. Similarly, if + // we know `&'a U: 'b`, then we know that `'a: 'b` and + // `U: 'b`. + // + // We can basically ignore bound regions here. So for + // example `for<'c> Foo<'a,'c>: 'b` can be elaborated to + // `'a: 'b`. + + // Ignore `for<'a> T: 'a` -- we might in the future + // consider this as evidence that `T: 'static`, but + // I'm a bit wary of such constructions and so for now + // I want to be conservative. --nmatsakis + if r_min.is_bound() { + return; + } + + let mut components = smallvec![]; + push_outlives_components(cx, ty_max, &mut components); + self.extend_deduped( + components + .into_iter() + .filter_map(|component| elaborate_component_to_clause(cx, component, r_min)) + .map(|clause| elaboratable.child(bound_clause.rebind(clause).upcast(cx))), + ); + } + ty::ClauseKind::RegionOutlives(..) => { + // Nothing to elaborate from `'a: 'b`. + } + ty::ClauseKind::WellFormed(..) => { + // Currently, we do not elaborate WF predicates, + // although we easily could. + } + ty::ClauseKind::Projection(..) => { + // Nothing to elaborate in a projection predicate. + } + ty::ClauseKind::ConstEvaluatable(..) => { + // Currently, we do not elaborate const-evaluatable + // predicates. + } + ty::ClauseKind::ConstArgHasType(..) => { + // Nothing to elaborate + } + } + } +} + +fn elaborate_component_to_clause( + cx: I, + component: Component, + outlives_region: I::Region, +) -> Option> { + match component { + Component::Region(r) => { + if r.is_bound() { + None + } else { + Some(ty::ClauseKind::RegionOutlives(ty::OutlivesPredicate(r, outlives_region))) + } + } + + Component::Param(p) => { + let ty = Ty::new_param(cx, p); + Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, outlives_region))) + } + + Component::Placeholder(p) => { + let ty = Ty::new_placeholder(cx, p); + Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate(ty, outlives_region))) + } + + Component::UnresolvedInferenceVariable(_) => None, + + Component::Alias(alias_ty) => { + // We might end up here if we have `Foo<::Assoc>: 'a`. + // With this, we can deduce that `::Assoc: 'a`. + Some(ty::ClauseKind::TypeOutlives(ty::OutlivesPredicate( + alias_ty.to_ty(cx), + outlives_region, + ))) + } + + Component::EscapingAlias(_) => { + // We might be able to do more here, but we don't + // want to deal with escaping vars right now. + None + } + } +} + +impl> Iterator for Elaborator { + type Item = O; + + fn size_hint(&self) -> (usize, Option) { + (self.stack.len(), None) + } + + fn next(&mut self) -> Option { + // Extract next item from top-most stack frame, if any. + if let Some(obligation) = self.stack.pop() { + self.elaborate(&obligation); + Some(obligation) + } else { + None + } + } +} + +/////////////////////////////////////////////////////////////////////////// +// Supertrait iterator +/////////////////////////////////////////////////////////////////////////// + +pub fn supertraits( + tcx: I, + trait_ref: ty::Binder>, +) -> FilterToTraits> { + elaborate(tcx, [trait_ref.upcast(tcx)]).filter_only_self().filter_to_traits() +} + +pub fn transitive_bounds( + tcx: I, + trait_refs: impl Iterator>>, +) -> FilterToTraits> { + elaborate(tcx, trait_refs.map(|trait_ref| trait_ref.upcast(tcx))) + .filter_only_self() + .filter_to_traits() +} + +impl Elaborator { + fn filter_to_traits(self) -> FilterToTraits { + FilterToTraits { _cx: PhantomData, base_iterator: self } + } +} + +/// A filter around an iterator of predicates that makes it yield up +/// just trait references. +pub struct FilterToTraits> { + _cx: PhantomData, + base_iterator: It, +} + +impl> Iterator for FilterToTraits { + type Item = ty::Binder>; + + fn next(&mut self) -> Option>> { + while let Some(pred) = self.base_iterator.next() { + if let Some(data) = pred.as_trait_clause() { + return Some(data.map_bound(|t| t.trait_ref)); + } + } + None + } + + fn size_hint(&self) -> (usize, Option) { + let (_, upper) = self.base_iterator.size_hint(); + (0, upper) + } +} diff --git a/compiler/rustc_type_ir/src/inherent.rs b/compiler/rustc_type_ir/src/inherent.rs index 68c2575258d2f..1db55b26719b0 100644 --- a/compiler/rustc_type_ir/src/inherent.rs +++ b/compiler/rustc_type_ir/src/inherent.rs @@ -9,6 +9,7 @@ use std::hash::Hash; use rustc_ast_ir::Mutability; use crate::data_structures::HashSet; +use crate::elaborate::Elaboratable; use crate::fold::{TypeFoldable, TypeSuperFoldable}; use crate::relate::Relate; use crate::solve::{CacheData, CanonicalInput, QueryResult, Reveal}; @@ -40,6 +41,10 @@ pub trait Ty>: fn new_var(interner: I, var: ty::TyVid) -> Self; + fn new_param(interner: I, param: I::ParamTy) -> Self; + + fn new_placeholder(interner: I, param: I::PlaceholderTy) -> Self; + fn new_bound(interner: I, debruijn: ty::DebruijnIndex, var: I::BoundTy) -> Self; fn new_anon_bound(interner: I, debruijn: ty::DebruijnIndex, var: ty::BoundVar) -> Self; @@ -429,6 +434,8 @@ pub trait Predicate>: + UpcastFrom> + IntoKind>> { + fn as_clause(self) -> Option; + fn is_coinductive(self, interner: I) -> bool; // FIXME: Eventually uplift the impl out of rustc and make this defaulted. @@ -441,35 +448,35 @@ pub trait Clause>: + Hash + Eq + TypeFoldable - // FIXME: Remove these, uplift the `Upcast` impls. + + UpcastFrom>> + UpcastFrom> + UpcastFrom>> + UpcastFrom> + UpcastFrom>> + IntoKind>> + + Elaboratable { fn as_trait_clause(self) -> Option>> { self.kind() - .map_bound(|clause| { - if let ty::ClauseKind::Trait(t) = clause { - Some(t) - } else { - None - } - }) + .map_bound(|clause| if let ty::ClauseKind::Trait(t) = clause { Some(t) } else { None }) .transpose() } + fn as_projection_clause(self) -> Option>> { self.kind() - .map_bound(|clause| { - if let ty::ClauseKind::Projection(p) = clause { - Some(p) - } else { - None - } - }) + .map_bound( + |clause| { + if let ty::ClauseKind::Projection(p) = clause { Some(p) } else { None } + }, + ) .transpose() } + + /// Performs a instantiation suitable for going from a + /// poly-trait-ref to supertraits that must hold if that + /// poly-trait-ref holds. This is slightly different from a normal + /// instantiation in terms of what happens with bound regions. + fn instantiate_supertrait(self, tcx: I, trait_ref: ty::Binder>) -> Self; } /// Common capabilities of placeholder kinds diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 6e013768c3ea8..446a4060e62f8 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -32,7 +32,7 @@ pub trait Interner: { type DefId: DefId; type LocalDefId: Copy + Debug + Hash + Eq + Into + TypeFoldable; - type Span: Copy + Debug + Hash + Eq; + type Span: Copy + Debug + Hash + Eq + TypeFoldable; type GenericArgs: GenericArgs; type GenericArgsSlice: Copy + Debug + Hash + Eq + SliceLike; @@ -213,7 +213,12 @@ pub trait Interner: fn explicit_super_predicates_of( self, def_id: Self::DefId, - ) -> ty::EarlyBinder>; + ) -> ty::EarlyBinder>; + + fn explicit_implied_predicates_of( + self, + def_id: Self::DefId, + ) -> ty::EarlyBinder>; fn has_target_features(self, def_id: Self::DefId) -> bool; @@ -268,6 +273,11 @@ pub trait Interner: param_env: Self::ParamEnv, placeholder: Self::PlaceholderConst, ) -> Self::Ty; + + fn anonymize_bound_vars>( + self, + binder: ty::Binder, + ) -> ty::Binder; } /// Imagine you have a function `F: FnOnce(&[T]) -> R`, plus an iterator `iter` diff --git a/compiler/rustc_type_ir/src/lib.rs b/compiler/rustc_type_ir/src/lib.rs index 2a909b06baf3a..b14a65fc77958 100644 --- a/compiler/rustc_type_ir/src/lib.rs +++ b/compiler/rustc_type_ir/src/lib.rs @@ -20,6 +20,7 @@ pub mod visit; #[cfg(feature = "nightly")] pub mod codec; pub mod data_structures; +pub mod elaborate; pub mod error; pub mod fast_reject; pub mod fold; From 66eb346770a97fe96c02a79a740cb151e5232010 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 6 Jul 2024 12:33:03 -0400 Subject: [PATCH 22/32] Get rid of the redundant elaboration in middle --- compiler/rustc_middle/src/traits/mod.rs | 1 - compiler/rustc_middle/src/traits/util.rs | 62 ------------------- compiler/rustc_middle/src/ty/context.rs | 26 +------- compiler/rustc_middle/src/ty/print/pretty.rs | 15 ++--- .../src/solve/trait_goals.rs | 5 +- compiler/rustc_type_ir/src/elaborate.rs | 28 +++++++++ compiler/rustc_type_ir/src/interner.rs | 3 - 7 files changed, 40 insertions(+), 100 deletions(-) delete mode 100644 compiler/rustc_middle/src/traits/util.rs diff --git a/compiler/rustc_middle/src/traits/mod.rs b/compiler/rustc_middle/src/traits/mod.rs index b4e3fae1b43b0..b74775142e487 100644 --- a/compiler/rustc_middle/src/traits/mod.rs +++ b/compiler/rustc_middle/src/traits/mod.rs @@ -7,7 +7,6 @@ pub mod select; pub mod solve; pub mod specialization_graph; mod structural_impls; -pub mod util; use crate::mir::ConstraintCategory; use crate::ty::abstract_const::NotConstEvaluatable; diff --git a/compiler/rustc_middle/src/traits/util.rs b/compiler/rustc_middle/src/traits/util.rs deleted file mode 100644 index 7437be7a74c14..0000000000000 --- a/compiler/rustc_middle/src/traits/util.rs +++ /dev/null @@ -1,62 +0,0 @@ -use rustc_data_structures::fx::FxHashSet; - -use crate::ty::{Clause, PolyTraitRef, ToPolyTraitRef, TyCtxt, Upcast}; - -/// Given a [`PolyTraitRef`], get the [`Clause`]s implied by the trait's definition. -/// -/// This only exists in `rustc_middle` because the more powerful elaborator depends on -/// `rustc_infer` for elaborating outlives bounds -- this should only be used for pretty -/// printing. -pub fn super_predicates_for_pretty_printing<'tcx>( - tcx: TyCtxt<'tcx>, - trait_ref: PolyTraitRef<'tcx>, -) -> impl Iterator> { - let clause = trait_ref.upcast(tcx); - Elaborator { tcx, visited: FxHashSet::from_iter([clause]), stack: vec![clause] } -} - -/// Like [`super_predicates_for_pretty_printing`], except it only returns traits and filters out -/// all other [`Clause`]s. -pub fn supertraits_for_pretty_printing<'tcx>( - tcx: TyCtxt<'tcx>, - trait_ref: PolyTraitRef<'tcx>, -) -> impl Iterator> { - super_predicates_for_pretty_printing(tcx, trait_ref).filter_map(|clause| { - clause.as_trait_clause().map(|trait_clause| trait_clause.to_poly_trait_ref()) - }) -} - -struct Elaborator<'tcx> { - tcx: TyCtxt<'tcx>, - visited: FxHashSet>, - stack: Vec>, -} - -impl<'tcx> Elaborator<'tcx> { - fn elaborate(&mut self, trait_ref: PolyTraitRef<'tcx>) { - let super_predicates = - self.tcx.explicit_super_predicates_of(trait_ref.def_id()).predicates.iter().filter_map( - |&(pred, _)| { - let clause = pred.instantiate_supertrait(self.tcx, trait_ref); - self.visited.insert(clause).then_some(clause) - }, - ); - - self.stack.extend(super_predicates); - } -} - -impl<'tcx> Iterator for Elaborator<'tcx> { - type Item = Clause<'tcx>; - - fn next(&mut self) -> Option> { - if let Some(clause) = self.stack.pop() { - if let Some(trait_clause) = clause.as_trait_clause() { - self.elaborate(trait_clause.to_poly_trait_ref()); - } - Some(clause) - } else { - None - } - } -} diff --git a/compiler/rustc_middle/src/ty/context.rs b/compiler/rustc_middle/src/ty/context.rs index 59ab5d791399b..c249579c5d723 100644 --- a/compiler/rustc_middle/src/ty/context.rs +++ b/compiler/rustc_middle/src/ty/context.rs @@ -37,7 +37,7 @@ use crate::ty::{GenericArg, GenericArgs, GenericArgsRef}; use rustc_ast::{self as ast, attr}; use rustc_data_structures::defer; use rustc_data_structures::fingerprint::Fingerprint; -use rustc_data_structures::fx::{FxHashMap, FxHashSet}; +use rustc_data_structures::fx::FxHashMap; use rustc_data_structures::intern::Interned; use rustc_data_structures::profiling::SelfProfilerRef; use rustc_data_structures::sharded::{IntoPointer, ShardedHashMap}; @@ -532,10 +532,6 @@ impl<'tcx> Interner for TyCtxt<'tcx> { self.trait_def(trait_def_id).implement_via_object } - fn supertrait_def_ids(self, trait_def_id: DefId) -> impl IntoIterator { - self.supertrait_def_ids(trait_def_id) - } - fn delay_bug(self, msg: impl ToString) -> ErrorGuaranteed { self.dcx().span_delayed_bug(DUMMY_SP, msg.to_string()) } @@ -2495,25 +2491,7 @@ impl<'tcx> TyCtxt<'tcx> { /// to identify which traits may define a given associated type to help avoid cycle errors, /// and to make size estimates for vtable layout computation. pub fn supertrait_def_ids(self, trait_def_id: DefId) -> impl Iterator + 'tcx { - let mut set = FxHashSet::default(); - let mut stack = vec![trait_def_id]; - - set.insert(trait_def_id); - - iter::from_fn(move || -> Option { - let trait_did = stack.pop()?; - let generic_predicates = self.explicit_super_predicates_of(trait_did); - - for (predicate, _) in generic_predicates.predicates { - if let ty::ClauseKind::Trait(data) = predicate.kind().skip_binder() { - if set.insert(data.def_id()) { - stack.push(data.def_id()); - } - } - } - - Some(trait_did) - }) + rustc_type_ir::elaborate::supertrait_def_ids(self, trait_def_id) } /// Given a closure signature, returns an equivalent fn signature. Detuples diff --git a/compiler/rustc_middle/src/ty/print/pretty.rs b/compiler/rustc_middle/src/ty/print/pretty.rs index 7e64c50740659..df080b2887b89 100644 --- a/compiler/rustc_middle/src/ty/print/pretty.rs +++ b/compiler/rustc_middle/src/ty/print/pretty.rs @@ -1,7 +1,6 @@ use crate::mir::interpret::{AllocRange, GlobalAlloc, Pointer, Provenance, Scalar}; use crate::query::IntoQueryParam; use crate::query::Providers; -use crate::traits::util::{super_predicates_for_pretty_printing, supertraits_for_pretty_printing}; use crate::ty::GenericArgKind; use crate::ty::{ ConstInt, Expr, ParamConst, ScalarInt, Term, TermKind, TypeFoldable, TypeSuperFoldable, @@ -23,6 +22,7 @@ use rustc_span::symbol::{kw, Ident, Symbol}; use rustc_span::FileNameDisplayPreference; use rustc_target::abi::Size; use rustc_target::spec::abi::Abi; +use rustc_type_ir::{elaborate, Upcast as _}; use smallvec::SmallVec; use std::cell::Cell; @@ -1255,14 +1255,14 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { entry.has_fn_once = true; return; } else if self.tcx().is_lang_item(trait_def_id, LangItem::FnMut) { - let super_trait_ref = supertraits_for_pretty_printing(self.tcx(), trait_ref) + let super_trait_ref = elaborate::supertraits(self.tcx(), trait_ref) .find(|super_trait_ref| super_trait_ref.def_id() == fn_once_trait) .unwrap(); fn_traits.entry(super_trait_ref).or_default().fn_mut_trait_ref = Some(trait_ref); return; } else if self.tcx().is_lang_item(trait_def_id, LangItem::Fn) { - let super_trait_ref = supertraits_for_pretty_printing(self.tcx(), trait_ref) + let super_trait_ref = elaborate::supertraits(self.tcx(), trait_ref) .find(|super_trait_ref| super_trait_ref.def_id() == fn_once_trait) .unwrap(); @@ -1343,10 +1343,11 @@ pub trait PrettyPrinter<'tcx>: Printer<'tcx> + fmt::Write { let bound_principal_with_self = bound_principal .with_self_ty(cx.tcx(), cx.tcx().types.trait_object_dummy_self); - let super_projections: Vec<_> = - super_predicates_for_pretty_printing(cx.tcx(), bound_principal_with_self) - .filter_map(|clause| clause.as_projection_clause()) - .collect(); + let clause: ty::Clause<'tcx> = bound_principal_with_self.upcast(cx.tcx()); + let super_projections: Vec<_> = elaborate::elaborate(cx.tcx(), [clause]) + .filter_only_self() + .filter_map(|clause| clause.as_projection_clause()) + .collect(); let mut projections: Vec<_> = predicates .projection_bounds() diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index b2a59eece0d16..f1128f70aaaa6 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -6,7 +6,7 @@ use rustc_type_ir::fast_reject::{DeepRejectCtxt, TreatParams}; use rustc_type_ir::inherent::*; use rustc_type_ir::lang_items::TraitSolverLangItem; use rustc_type_ir::visit::TypeVisitableExt as _; -use rustc_type_ir::{self as ty, Interner, TraitPredicate, Upcast as _}; +use rustc_type_ir::{self as ty, elaborate, Interner, TraitPredicate, Upcast as _}; use tracing::{instrument, trace}; use crate::delegate::SolverDelegate; @@ -862,8 +862,7 @@ where .auto_traits() .into_iter() .chain(a_data.principal_def_id().into_iter().flat_map(|principal_def_id| { - self.cx() - .supertrait_def_ids(principal_def_id) + elaborate::supertrait_def_ids(self.cx(), principal_def_id) .into_iter() .filter(|def_id| self.cx().trait_is_auto(*def_id)) })) diff --git a/compiler/rustc_type_ir/src/elaborate.rs b/compiler/rustc_type_ir/src/elaborate.rs index 04cdf29a77799..0def7d12f742a 100644 --- a/compiler/rustc_type_ir/src/elaborate.rs +++ b/compiler/rustc_type_ir/src/elaborate.rs @@ -229,6 +229,34 @@ impl> Iterator for Elaborator { // Supertrait iterator /////////////////////////////////////////////////////////////////////////// +/// Computes the def-ids of the transitive supertraits of `trait_def_id`. This (intentionally) +/// does not compute the full elaborated super-predicates but just the set of def-ids. It is used +/// to identify which traits may define a given associated type to help avoid cycle errors, +/// and to make size estimates for vtable layout computation. +pub fn supertrait_def_ids( + cx: I, + trait_def_id: I::DefId, +) -> impl Iterator { + let mut set = HashSet::default(); + let mut stack = vec![trait_def_id]; + + set.insert(trait_def_id); + + std::iter::from_fn(move || { + let trait_def_id = stack.pop()?; + + for (predicate, _) in cx.explicit_super_predicates_of(trait_def_id).iter_identity() { + if let ty::ClauseKind::Trait(data) = predicate.kind().skip_binder() { + if set.insert(data.def_id()) { + stack.push(data.def_id()); + } + } + } + + Some(trait_def_id) + }) +} + pub fn supertraits( tcx: I, trait_ref: ty::Binder>, diff --git a/compiler/rustc_type_ir/src/interner.rs b/compiler/rustc_type_ir/src/interner.rs index 446a4060e62f8..6a291a02d6126 100644 --- a/compiler/rustc_type_ir/src/interner.rs +++ b/compiler/rustc_type_ir/src/interner.rs @@ -253,9 +253,6 @@ pub trait Interner: fn trait_may_be_implemented_via_object(self, trait_def_id: Self::DefId) -> bool; - fn supertrait_def_ids(self, trait_def_id: Self::DefId) - -> impl IntoIterator; - fn delay_bug(self, msg: impl ToString) -> Self::ErrorGuaranteed; fn is_general_coroutine(self, coroutine_def_id: Self::DefId) -> bool; From 15d16f1cd6f4daea12c7be4e34ff3b5a776671d2 Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Sat, 6 Jul 2024 12:37:04 -0400 Subject: [PATCH 23/32] Finish uplifting supertraits --- compiler/rustc_next_trait_solver/src/delegate.rs | 6 ------ .../rustc_next_trait_solver/src/solve/assembly/mod.rs | 3 ++- compiler/rustc_next_trait_solver/src/solve/trait_goals.rs | 2 +- compiler/rustc_trait_selection/src/solve/delegate.rs | 8 -------- 4 files changed, 3 insertions(+), 16 deletions(-) diff --git a/compiler/rustc_next_trait_solver/src/delegate.rs b/compiler/rustc_next_trait_solver/src/delegate.rs index 31032dc679ac3..cdea7363d0e62 100644 --- a/compiler/rustc_next_trait_solver/src/delegate.rs +++ b/compiler/rustc_next_trait_solver/src/delegate.rs @@ -32,12 +32,6 @@ pub trait SolverDelegate: // FIXME: Uplift the leak check into this crate. fn leak_check(&self, max_input_universe: ty::UniverseIndex) -> Result<(), NoSolution>; - // FIXME: This is only here because elaboration lives in `rustc_infer`! - fn elaborate_supertraits( - cx: Self::Interner, - trait_ref: ty::Binder>, - ) -> impl Iterator>>; - fn try_const_eval_resolve( &self, param_env: ::ParamEnv, diff --git a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs index 38a4f7dfe25cb..01dde9ca587ca 100644 --- a/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs +++ b/compiler/rustc_next_trait_solver/src/solve/assembly/mod.rs @@ -2,6 +2,7 @@ pub(super) mod structural_traits; +use rustc_type_ir::elaborate; use rustc_type_ir::fold::TypeFoldable; use rustc_type_ir::inherent::*; use rustc_type_ir::lang_items::TraitSolverLangItem; @@ -667,7 +668,7 @@ where // a projection goal. if let Some(principal) = bounds.principal() { let principal_trait_ref = principal.with_self_ty(cx, self_ty); - for (idx, assumption) in D::elaborate_supertraits(cx, principal_trait_ref).enumerate() { + for (idx, assumption) in elaborate::supertraits(cx, principal_trait_ref).enumerate() { candidates.extend(G::probe_and_consider_object_bound_candidate( self, CandidateSource::BuiltinImpl(BuiltinImplSource::Object(idx)), diff --git a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs index f1128f70aaaa6..4474bbc235198 100644 --- a/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs +++ b/compiler/rustc_next_trait_solver/src/solve/trait_goals.rs @@ -787,7 +787,7 @@ where )); } else if let Some(a_principal) = a_data.principal() { for new_a_principal in - D::elaborate_supertraits(self.cx(), a_principal.with_self_ty(cx, a_ty)).skip(1) + elaborate::supertraits(self.cx(), a_principal.with_self_ty(cx, a_ty)).skip(1) { responses.extend(self.consider_builtin_upcast_to_principal( goal, diff --git a/compiler/rustc_trait_selection/src/solve/delegate.rs b/compiler/rustc_trait_selection/src/solve/delegate.rs index f98744e906f05..03743e57333a7 100644 --- a/compiler/rustc_trait_selection/src/solve/delegate.rs +++ b/compiler/rustc_trait_selection/src/solve/delegate.rs @@ -8,7 +8,6 @@ use rustc_infer::infer::canonical::{ }; use rustc_infer::infer::{InferCtxt, RegionVariableOrigin, TyCtxtInferExt}; use rustc_infer::traits::solve::Goal; -use rustc_infer::traits::util::supertraits; use rustc_infer::traits::{ObligationCause, Reveal}; use rustc_middle::ty::fold::TypeFoldable; use rustc_middle::ty::{self, Ty, TyCtxt, TypeVisitableExt as _}; @@ -82,13 +81,6 @@ impl<'tcx> rustc_next_trait_solver::delegate::SolverDelegate for SolverDelegate< self.0.leak_check(max_input_universe, None).map_err(|_| NoSolution) } - fn elaborate_supertraits( - interner: TyCtxt<'tcx>, - trait_ref: ty::Binder<'tcx, ty::TraitRef<'tcx>>, - ) -> impl Iterator>> { - supertraits(interner, trait_ref) - } - fn try_const_eval_resolve( &self, param_env: ty::ParamEnv<'tcx>, From cda25e56c8d4f810d19034ebf5d0e4fdb155a264 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Fri, 15 Mar 2024 15:08:20 +0000 Subject: [PATCH 24/32] Refactor & fixup interpreter implementation of tail calls --- .../src/interpret/eval_context.rs | 145 +++++++++++++----- .../rustc_const_eval/src/interpret/machine.rs | 3 + .../rustc_const_eval/src/interpret/step.rs | 2 +- .../src/interpret/terminator.rs | 82 ++++++---- 4 files changed, 162 insertions(+), 70 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 830c4bd3e26e0..fd063575e5d98 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -159,6 +159,13 @@ pub enum StackPopCleanup { Root { cleanup: bool }, } +/// Return type of [`InterpCx::pop_stack_frame`]. +pub struct StackPop<'tcx, Prov: Provenance> { + pub jump: StackPopJump, + pub target: StackPopCleanup, + pub destination: MPlaceTy<'tcx, Prov>, +} + /// State of a local variable including a memoized layout #[derive(Clone)] pub struct LocalState<'tcx, Prov: Provenance = CtfeProvenance> { @@ -803,14 +810,31 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { return_to_block: StackPopCleanup, ) -> InterpResult<'tcx> { trace!("body: {:#?}", body); + + // First push a stack frame so we have access to the local args + self.push_new_stack_frame(instance, body, return_to_block, return_place.clone())?; + + self.after_stack_frame_push(instance, body)?; + + Ok(()) + } + + /// Creates a new stack frame, initializes it and pushes it unto the stack. + /// A private helper for [`push_stack_frame`](InterpCx::push_stack_frame). + fn push_new_stack_frame( + &mut self, + instance: ty::Instance<'tcx>, + body: &'tcx mir::Body<'tcx>, + return_to_block: StackPopCleanup, + return_place: MPlaceTy<'tcx, M::Provenance>, + ) -> InterpResult<'tcx> { let dead_local = LocalState { value: LocalValue::Dead, layout: Cell::new(None) }; let locals = IndexVec::from_elem(dead_local, &body.local_decls); - // First push a stack frame so we have access to the local args let pre_frame = Frame { body, loc: Right(body.span), // Span used for errors caused during preamble. return_to_block, - return_place: return_place.clone(), + return_place, locals, instance, tracing_span: SpanGuard::new(), @@ -819,6 +843,15 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let frame = M::init_frame(self, pre_frame)?; self.stack_mut().push(frame); + Ok(()) + } + + /// A private helper for [`push_stack_frame`](InterpCx::push_stack_frame). + fn after_stack_frame_push( + &mut self, + instance: ty::Instance<'tcx>, + body: &'tcx mir::Body<'tcx>, + ) -> InterpResult<'tcx> { // Make sure all the constants required by this frame evaluate successfully (post-monomorphization check). for &const_ in &body.required_consts { let c = @@ -839,6 +872,59 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { Ok(()) } + /// Pops a stack frame from the stack and returns some information about it. + /// + /// This also deallocates locals, if necessary. + /// + /// [`M::before_stack_pop`] should be called before calling this function. + /// [`M::after_stack_pop`] is called by this function automatically. + /// + /// [`M::before_stack_pop`]: Machine::before_stack_pop + /// [`M::after_stack_pop`]: Machine::after_stack_pop + pub fn pop_stack_frame( + &mut self, + unwinding: bool, + ) -> InterpResult<'tcx, StackPop<'tcx, M::Provenance>> { + let cleanup = self.cleanup_current_frame_locals()?; + + let frame = + self.stack_mut().pop().expect("tried to pop a stack frame, but there were none"); + + let target = frame.return_to_block; + let destination = frame.return_place.clone(); + + let jump = if cleanup { + M::after_stack_pop(self, frame, unwinding)? + } else { + StackPopJump::NoCleanup + }; + + Ok(StackPop { jump, target, destination }) + } + + /// A private helper for [`push_stack_frame`](InterpCx::push_stack_frame). + /// Returns whatever the cleanup was done. + fn cleanup_current_frame_locals(&mut self) -> InterpResult<'tcx, bool> { + // Cleanup: deallocate locals. + // Usually we want to clean up (deallocate locals), but in a few rare cases we don't. + // We do this while the frame is still on the stack, so errors point to the callee. + let return_to_block = self.frame().return_to_block; + let cleanup = match return_to_block { + StackPopCleanup::Goto { .. } => true, + StackPopCleanup::Root { cleanup, .. } => cleanup, + }; + + if cleanup { + // We need to take the locals out, since we need to mutate while iterating. + let locals = mem::take(&mut self.frame_mut().locals); + for local in &locals { + self.deallocate_local(local.value)?; + } + } + + Ok(cleanup) + } + /// Jump to the given block. #[inline] pub fn go_to_block(&mut self, target: mir::BasicBlock) { @@ -886,7 +972,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } /// Pops the current frame from the stack, deallocating the - /// memory for allocated locals. + /// memory for allocated locals, and jumps to an appropriate place. /// /// If `unwinding` is `false`, then we are performing a normal return /// from a function. In this case, we jump back into the frame of the caller, @@ -899,7 +985,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { /// The cleanup block ends with a special `Resume` terminator, which will /// cause us to continue unwinding. #[instrument(skip(self), level = "debug")] - pub(super) fn pop_stack_frame(&mut self, unwinding: bool) -> InterpResult<'tcx> { + pub(super) fn return_from_current_stack_frame( + &mut self, + unwinding: bool, + ) -> InterpResult<'tcx> { info!( "popping stack frame ({})", if unwinding { "during unwinding" } else { "returning from function" } @@ -947,45 +1036,31 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { Ok(()) }; - // Cleanup: deallocate locals. - // Usually we want to clean up (deallocate locals), but in a few rare cases we don't. - // We do this while the frame is still on the stack, so errors point to the callee. - let return_to_block = self.frame().return_to_block; - let cleanup = match return_to_block { - StackPopCleanup::Goto { .. } => true, - StackPopCleanup::Root { cleanup, .. } => cleanup, - }; - if cleanup { - // We need to take the locals out, since we need to mutate while iterating. - let locals = mem::take(&mut self.frame_mut().locals); - for local in &locals { - self.deallocate_local(local.value)?; - } - } - // All right, now it is time to actually pop the frame. - // Note that its locals are gone already, but that's fine. - let frame = - self.stack_mut().pop().expect("tried to pop a stack frame, but there were none"); + let frame = self.pop_stack_frame(unwinding)?; + // Report error from return value copy, if any. copy_ret_result?; - // If we are not doing cleanup, also skip everything else. - if !cleanup { - assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked"); - assert!(!unwinding, "tried to skip cleanup during unwinding"); - // Skip machine hook. - return Ok(()); - } - if M::after_stack_pop(self, frame, unwinding)? == StackPopJump::NoJump { - // The hook already did everything. - return Ok(()); + match frame.jump { + StackPopJump::Normal => {} + StackPopJump::NoJump => { + // The hook already did everything. + return Ok(()); + } + StackPopJump::NoCleanup => { + // If we are not doing cleanup, also skip everything else. + assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked"); + assert!(!unwinding, "tried to skip cleanup during unwinding"); + // Skip machine hook. + return Ok(()); + } } // Normal return, figure out where to jump. if unwinding { // Follow the unwind edge. - let unwind = match return_to_block { + let unwind = match frame.target { StackPopCleanup::Goto { unwind, .. } => unwind, StackPopCleanup::Root { .. } => { panic!("encountered StackPopCleanup::Root when unwinding!") @@ -995,7 +1070,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.unwind_to_block(unwind) } else { // Follow the normal return edge. - match return_to_block { + match frame.target { StackPopCleanup::Goto { ret, .. } => self.return_to_block(ret), StackPopCleanup::Root { .. } => { assert!( diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index e91ab7c179163..b8a651a744aa6 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -36,6 +36,9 @@ pub enum StackPopJump { /// Indicates that we should *not* jump to the return/unwind address, as the callback already /// took care of everything. NoJump, + + /// Returned by [`InterpCx::pop_stack_frame`] when no cleanup should be done. + NoCleanup, } /// Whether this kind of memory is allowed to leak diff --git a/compiler/rustc_const_eval/src/interpret/step.rs b/compiler/rustc_const_eval/src/interpret/step.rs index 1baf62baa816f..b3124dfdfbc01 100644 --- a/compiler/rustc_const_eval/src/interpret/step.rs +++ b/compiler/rustc_const_eval/src/interpret/step.rs @@ -32,7 +32,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // We are unwinding and this fn has no cleanup code. // Just go on unwinding. trace!("unwinding: skipping frame"); - self.pop_stack_frame(/* unwinding */ true)?; + self.return_from_current_stack_frame(/* unwinding */ true)?; return Ok(true); }; let basic_block = &self.body().basic_blocks[loc.block]; diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 6644e70e039a8..a8d18405b6e56 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -4,9 +4,8 @@ use either::Either; use rustc_middle::ty::TyCtxt; use tracing::trace; -use rustc_middle::span_bug; use rustc_middle::{ - mir, + bug, mir, span_bug, ty::{ self, layout::{FnAbiOf, IntegerExt, LayoutOf, TyAndLayout}, @@ -26,7 +25,10 @@ use super::{ InterpResult, MPlaceTy, Machine, OpTy, PlaceTy, Projectable, Provenance, Scalar, StackPopCleanup, }; -use crate::fluent_generated as fluent; +use crate::{ + fluent_generated as fluent, + interpret::{eval_context::StackPop, StackPopJump}, +}; /// An argment passed to a function. #[derive(Clone, Debug)] @@ -93,7 +95,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { use rustc_middle::mir::TerminatorKind::*; match terminator.kind { Return => { - self.pop_stack_frame(/* unwinding */ false)? + self.return_from_current_stack_frame(/* unwinding */ false)? } Goto { target } => self.go_to_block(target), @@ -160,35 +162,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let EvaluatedCalleeAndArgs { callee, args, fn_sig, fn_abi, with_caller_location } = self.eval_callee_and_args(terminator, func, args)?; - // This is the "canonical" implementation of tails calls, - // a pop of the current stack frame, followed by a normal call - // which pushes a new stack frame, with the return address from - // the popped stack frame. - // - // Note that we can't use `pop_stack_frame` as it "executes" - // the goto to the return block, but we don't want to, - // only the tail called function should return to the current - // return block. - let Some(prev_frame) = self.stack_mut().pop() else { - span_bug!( - terminator.source_info.span, - "empty stack while evaluating this tail call" - ) - }; - - let StackPopCleanup::Goto { ret, unwind } = prev_frame.return_to_block else { - span_bug!(terminator.source_info.span, "tail call with the root stack frame") - }; - - self.eval_fn_call( - callee, - (fn_sig.abi, fn_abi), - &args, - with_caller_location, - &prev_frame.return_place, - ret, - unwind, - )?; + self.eval_fn_tail_call(callee, (fn_sig.abi, fn_abi), &args, with_caller_location)?; if self.frame_idx() != old_frame_idx { span_bug!( @@ -235,7 +209,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { trace!("unwinding: resuming from cleanup"); // By definition, a Resume terminator means // that we're unwinding - self.pop_stack_frame(/* unwinding */ true)?; + self.return_from_current_stack_frame(/* unwinding */ true)?; return Ok(()); } @@ -989,6 +963,46 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { } } + pub(crate) fn eval_fn_tail_call( + &mut self, + fn_val: FnVal<'tcx, M::ExtraFnVal>, + (caller_abi, caller_fn_abi): (Abi, &FnAbi<'tcx, Ty<'tcx>>), + args: &[FnArg<'tcx, M::Provenance>], + with_caller_location: bool, + ) -> InterpResult<'tcx> { + trace!("eval_fn_call: {:#?}", fn_val); + + // This is the "canonical" implementation of tails calls, + // a pop of the current stack frame, followed by a normal call + // which pushes a new stack frame, with the return address from + // the popped stack frame. + // + // Note that we can't use `pop_stack_frame` as it "executes" + // the goto to the return block, but we don't want to, + // only the tail called function should return to the current + // return block. + + M::before_stack_pop(self, self.frame())?; + + let StackPop { jump, target, destination } = self.pop_stack_frame(false)?; + + assert_eq!(jump, StackPopJump::Normal); + + let StackPopCleanup::Goto { ret, unwind } = target else { + bug!("can't tailcall as root"); + }; + + self.eval_fn_call( + fn_val, + (caller_abi, caller_fn_abi), + args, + with_caller_location, + &destination, + ret, + unwind, + ) + } + fn check_fn_target_features(&self, instance: ty::Instance<'tcx>) -> InterpResult<'tcx, ()> { // Calling functions with `#[target_feature]` is not unsafe on WASM, see #84988 let attrs = self.tcx.codegen_fn_attrs(instance.def_id()); From 6d4995f4e6d3f4fe6ac54943a5b01c3cf752dd5d Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 17 Jun 2024 15:37:33 +0000 Subject: [PATCH 25/32] add miri tests and a fixme --- .../src/interpret/terminator.rs | 4 ++ .../miri/tests/fail/tail_calls/cc-mismatch.rs | 10 +++++ .../tests/fail/tail_calls/cc-mismatch.stderr | 25 ++++++++++++ .../fail/tail_calls/signature-mismatch-arg.rs | 17 ++++++++ .../tail_calls/signature-mismatch-arg.stderr | 17 ++++++++ src/tools/miri/tests/pass/tail_call.rs | 39 +++++++++++++++++++ 6 files changed, 112 insertions(+) create mode 100644 src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs create mode 100644 src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr create mode 100644 src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs create mode 100644 src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr create mode 100644 src/tools/miri/tests/pass/tail_call.rs diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index a8d18405b6e56..9f4ca93a3e8d6 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -992,6 +992,10 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { bug!("can't tailcall as root"); }; + // FIXME(explicit_tail_calls): + // we should check if both caller&callee can/n't unwind, + // see + self.eval_fn_call( fn_val, (caller_abi, caller_fn_abi), diff --git a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs new file mode 100644 index 0000000000000..5f00dbf257366 --- /dev/null +++ b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.rs @@ -0,0 +1,10 @@ +//@error-in-other-file: Undefined Behavior: calling a function with calling convention C using calling convention Rust +#![feature(explicit_tail_calls)] +#![allow(incomplete_features)] + +fn main() { + let f = unsafe { std::mem::transmute::(f) }; + become f(); +} + +extern "C" fn f() {} diff --git a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr new file mode 100644 index 0000000000000..708972e6eff13 --- /dev/null +++ b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr @@ -0,0 +1,25 @@ +error: Undefined Behavior: calling a function with calling convention C using calling convention Rust + --> RUSTLIB/core/src/ops/function.rs:LL:CC + | +LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; + | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ calling a function with calling convention C using calling convention Rust + | + = 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 `>::call_once - shim(fn())` at RUSTLIB/core/src/ops/function.rs:LL:CC + = note: inside `std::sys_common::backtrace::__rust_begin_short_backtrace::` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::ops::function::impls:: for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once` at RUSTLIB/core/src/ops/function.rs:LL:CC + = note: inside `std::panicking::r#try::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::r#try:: i32 + std::marker::Sync + std::panic::RefUnwindSafe>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::panicking::r#try::do_call::<{closure@std::rt::lang_start_internal::{closure#2}}, isize>` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panicking::r#try::` at RUSTLIB/std/src/panicking.rs:LL:CC + = note: inside `std::panic::catch_unwind::<{closure@std::rt::lang_start_internal::{closure#2}}, isize>` at RUSTLIB/std/src/panic.rs:LL:CC + = note: inside `std::rt::lang_start_internal` at RUSTLIB/std/src/rt.rs:LL:CC + = note: inside `std::rt::lang_start::<()>` at RUSTLIB/std/src/rt.rs:LL:CC + +error: aborting due to 1 previous error + diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs new file mode 100644 index 0000000000000..3264a74d159e3 --- /dev/null +++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs @@ -0,0 +1,17 @@ +#![feature(explicit_tail_calls)] +#![allow(incomplete_features)] + +fn main() { + // FIXME(explicit_tail_calls): + // the error should point to `become f(x)`, + // but tail calls mess up the backtrace it seems like... + f(0); + //~^ error: Undefined Behavior: calling a function with argument of type i32 passing data of type u32 +} + +fn f(x: u32) { + let g = unsafe { std::mem::transmute::(g) }; + become g(x); +} + +fn g(_: i32) {} diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr new file mode 100644 index 0000000000000..2ecc5674c6537 --- /dev/null +++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr @@ -0,0 +1,17 @@ +error: Undefined Behavior: calling a function with argument of type i32 passing data of type u32 + --> $DIR/signature-mismatch-arg.rs:LL:CC + | +LL | f(0); + | ^^^^ calling a function with argument of type i32 passing data of type u32 + | + = 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 + = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets + = help: if you think this code should be accepted anyway, please report an issue + = note: BACKTRACE: + = note: inside `main` at $DIR/signature-mismatch-arg.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/pass/tail_call.rs b/src/tools/miri/tests/pass/tail_call.rs new file mode 100644 index 0000000000000..f620070639801 --- /dev/null +++ b/src/tools/miri/tests/pass/tail_call.rs @@ -0,0 +1,39 @@ +#![allow(incomplete_features)] +#![feature(explicit_tail_calls)] + +fn main() { + assert_eq!(factorial(10), 3_628_800); + assert_eq!(mutually_recursive_identity(1000), 1000); +} + +fn factorial(n: u32) -> u32 { + fn factorial_acc(n: u32, acc: u32) -> u32 { + match n { + 0 => acc, + _ => become factorial_acc(n - 1, acc * n), + } + } + + factorial_acc(n, 1) +} + +// this is of course very silly, but we need to demonstrate mutual recursion somehow so... +fn mutually_recursive_identity(x: u32) -> u32 { + fn switch(src: u32, tgt: u32) -> u32 { + match src { + 0 => tgt, + _ if src % 7 == 0 => become advance_with_extra_steps(src, tgt), + _ => become advance(src, tgt), + } + } + + fn advance(src: u32, tgt: u32) -> u32 { + become switch(src - 1, tgt + 1) + } + + fn advance_with_extra_steps(src: u32, tgt: u32) -> u32 { + become advance(src, tgt) + } + + switch(x, 0) +} From cda6f0c25d6421fd6814bd55a25701205da23585 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 17 Jun 2024 17:04:28 +0000 Subject: [PATCH 26/32] doc fixups from review --- compiler/rustc_const_eval/src/interpret/eval_context.rs | 6 +++--- compiler/rustc_const_eval/src/interpret/terminator.rs | 8 +++----- tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs | 2 +- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index fd063575e5d98..9517e3d359744 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -819,7 +819,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { Ok(()) } - /// Creates a new stack frame, initializes it and pushes it unto the stack. + /// Creates a new stack frame, initializes it and pushes it onto the stack. /// A private helper for [`push_stack_frame`](InterpCx::push_stack_frame). fn push_new_stack_frame( &mut self, @@ -902,8 +902,8 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { Ok(StackPop { jump, target, destination }) } - /// A private helper for [`push_stack_frame`](InterpCx::push_stack_frame). - /// Returns whatever the cleanup was done. + /// A private helper for [`pop_stack_frame`](InterpCx::pop_stack_frame). + /// Returns `true` if cleanup has been done, `false` otherwise. fn cleanup_current_frame_locals(&mut self) -> InterpResult<'tcx, bool> { // Cleanup: deallocate locals. // Usually we want to clean up (deallocate locals), but in a few rare cases we don't. diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 9f4ca93a3e8d6..589ab6029f9bc 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -977,11 +977,9 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // which pushes a new stack frame, with the return address from // the popped stack frame. // - // Note that we can't use `pop_stack_frame` as it "executes" - // the goto to the return block, but we don't want to, - // only the tail called function should return to the current - // return block. - + // Note that we are using `pop_stack_frame` and not `return_from_current_stack_frame`, + // as the latter "executes" the goto to the return block, but we don't want to, + // only the tail called function should return to the current return block. M::before_stack_pop(self, self.frame())?; let StackPop { jump, target, destination } = self.pop_stack_frame(false)?; diff --git a/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs index 2e6bb0e1ac5b7..86041b669601d 100644 --- a/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs +++ b/tests/ui/explicit-tail-calls/ctfe-collatz-multi-rec.rs @@ -2,7 +2,7 @@ #![allow(incomplete_features)] #![feature(explicit_tail_calls)] -/// A very unnecessarily complicated "implementation" of the callatz conjecture. +/// A very unnecessarily complicated "implementation" of the Collatz conjecture. /// Returns the number of steps to reach `1`. /// /// This is just a test for tail calls, which involves multiple functions calling each other. From 236352024b25c2e834c7a73fc9219ddc84c0bcf9 Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 17 Jun 2024 17:25:14 +0000 Subject: [PATCH 27/32] make `StackPop` field names less confusing --- .../src/interpret/eval_context.rs | 38 +++++++++++-------- .../rustc_const_eval/src/interpret/machine.rs | 11 +++--- .../rustc_const_eval/src/interpret/mod.rs | 2 +- .../src/interpret/terminator.rs | 11 +++--- src/tools/miri/src/machine.rs | 2 +- src/tools/miri/src/shims/panic.rs | 6 +-- 6 files changed, 39 insertions(+), 31 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 9517e3d359744..bc15e41343fcd 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -26,8 +26,8 @@ use rustc_target::abi::{call::FnAbi, Align, HasDataLayout, Size, TargetDataLayou use super::{ err_inval, throw_inval, throw_ub, throw_ub_custom, throw_unsup, GlobalId, Immediate, InterpErrorInfo, InterpResult, MPlaceTy, Machine, MemPlace, MemPlaceMeta, Memory, MemoryKind, - OpTy, Operand, Place, PlaceTy, Pointer, PointerArithmetic, Projectable, Provenance, Scalar, - StackPopJump, + OpTy, Operand, Place, PlaceTy, Pointer, PointerArithmetic, Projectable, Provenance, + ReturnAction, Scalar, }; use crate::errors; use crate::util; @@ -161,9 +161,15 @@ pub enum StackPopCleanup { /// Return type of [`InterpCx::pop_stack_frame`]. pub struct StackPop<'tcx, Prov: Provenance> { - pub jump: StackPopJump, - pub target: StackPopCleanup, - pub destination: MPlaceTy<'tcx, Prov>, + /// Additional information about the action to be performed when returning from the popped + /// stack frame. + pub return_action: ReturnAction, + + /// [`return_to_block`](Frame::return_to_block) of the popped stack frame. + pub return_to_block: StackPopCleanup, + + /// [`return_place`](Frame::return_place) of the popped stack frame. + pub return_place: MPlaceTy<'tcx, Prov>, } /// State of a local variable including a memoized layout @@ -890,16 +896,16 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let frame = self.stack_mut().pop().expect("tried to pop a stack frame, but there were none"); - let target = frame.return_to_block; - let destination = frame.return_place.clone(); + let return_to_block = frame.return_to_block; + let return_place = frame.return_place.clone(); - let jump = if cleanup { + let return_action = if cleanup { M::after_stack_pop(self, frame, unwinding)? } else { - StackPopJump::NoCleanup + ReturnAction::NoCleanup }; - Ok(StackPop { jump, target, destination }) + Ok(StackPop { return_action, return_to_block, return_place }) } /// A private helper for [`pop_stack_frame`](InterpCx::pop_stack_frame). @@ -1042,13 +1048,13 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Report error from return value copy, if any. copy_ret_result?; - match frame.jump { - StackPopJump::Normal => {} - StackPopJump::NoJump => { + match frame.return_action { + ReturnAction::Normal => {} + ReturnAction::NoJump => { // The hook already did everything. return Ok(()); } - StackPopJump::NoCleanup => { + ReturnAction::NoCleanup => { // If we are not doing cleanup, also skip everything else. assert!(self.stack().is_empty(), "only the topmost frame should ever be leaked"); assert!(!unwinding, "tried to skip cleanup during unwinding"); @@ -1060,7 +1066,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Normal return, figure out where to jump. if unwinding { // Follow the unwind edge. - let unwind = match frame.target { + let unwind = match frame.return_to_block { StackPopCleanup::Goto { unwind, .. } => unwind, StackPopCleanup::Root { .. } => { panic!("encountered StackPopCleanup::Root when unwinding!") @@ -1070,7 +1076,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.unwind_to_block(unwind) } else { // Follow the normal return edge. - match frame.target { + match frame.return_to_block { StackPopCleanup::Goto { ret, .. } => self.return_to_block(ret), StackPopCleanup::Root { .. } => { assert!( diff --git a/compiler/rustc_const_eval/src/interpret/machine.rs b/compiler/rustc_const_eval/src/interpret/machine.rs index b8a651a744aa6..7f2e9ce06a5a0 100644 --- a/compiler/rustc_const_eval/src/interpret/machine.rs +++ b/compiler/rustc_const_eval/src/interpret/machine.rs @@ -23,10 +23,11 @@ use super::{ MemoryKind, Misalignment, OpTy, PlaceTy, Pointer, Provenance, }; -/// Data returned by Machine::stack_pop, -/// to provide further control over the popping of the stack frame +/// Data returned by [`Machine::after_stack_pop`], and consumed by +/// [`InterpCx::return_from_current_stack_frame`] to determine what actions should be done when +/// returning from a stack frame. #[derive(Eq, PartialEq, Debug, Copy, Clone)] -pub enum StackPopJump { +pub enum ReturnAction { /// Indicates that no special handling should be /// done - we'll either return normally or unwind /// based on the terminator for the function @@ -525,10 +526,10 @@ pub trait Machine<'tcx>: Sized { _ecx: &mut InterpCx<'tcx, Self>, _frame: Frame<'tcx, Self::Provenance, Self::FrameExtra>, unwinding: bool, - ) -> InterpResult<'tcx, StackPopJump> { + ) -> InterpResult<'tcx, ReturnAction> { // By default, we do not support unwinding from panics assert!(!unwinding); - Ok(StackPopJump::Normal) + Ok(ReturnAction::Normal) } /// Called immediately after actual memory was allocated for a local diff --git a/compiler/rustc_const_eval/src/interpret/mod.rs b/compiler/rustc_const_eval/src/interpret/mod.rs index 7d7b421f86935..f703c6fbe3ea3 100644 --- a/compiler/rustc_const_eval/src/interpret/mod.rs +++ b/compiler/rustc_const_eval/src/interpret/mod.rs @@ -26,7 +26,7 @@ pub use self::intern::{ intern_const_alloc_for_constprop, intern_const_alloc_recursive, HasStaticRootDefId, InternKind, InternResult, }; -pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, StackPopJump}; +pub use self::machine::{compile_time_machine, AllocMap, Machine, MayLeak, ReturnAction}; pub use self::memory::{AllocKind, AllocRef, AllocRefMut, FnVal, Memory, MemoryKind}; pub use self::operand::{ImmTy, Immediate, OpTy, Readable}; pub use self::place::{MPlaceTy, MemPlaceMeta, PlaceTy, Writeable}; diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 589ab6029f9bc..8886d7d8af571 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -27,7 +27,7 @@ use super::{ }; use crate::{ fluent_generated as fluent, - interpret::{eval_context::StackPop, StackPopJump}, + interpret::{eval_context::StackPop, ReturnAction}, }; /// An argment passed to a function. @@ -982,11 +982,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // only the tail called function should return to the current return block. M::before_stack_pop(self, self.frame())?; - let StackPop { jump, target, destination } = self.pop_stack_frame(false)?; + let StackPop { return_action, return_to_block, return_place } = + self.pop_stack_frame(false)?; - assert_eq!(jump, StackPopJump::Normal); + assert_eq!(return_action, ReturnAction::Normal); - let StackPopCleanup::Goto { ret, unwind } = target else { + let StackPopCleanup::Goto { ret, unwind } = return_to_block else { bug!("can't tailcall as root"); }; @@ -999,7 +1000,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { (caller_abi, caller_fn_abi), args, with_caller_location, - &destination, + &return_place, ret, unwind, ) diff --git a/src/tools/miri/src/machine.rs b/src/tools/miri/src/machine.rs index 0d91279f9f4c4..d4d50ebdd14a9 100644 --- a/src/tools/miri/src/machine.rs +++ b/src/tools/miri/src/machine.rs @@ -1434,7 +1434,7 @@ impl<'tcx> Machine<'tcx> for MiriMachine<'tcx> { ecx: &mut InterpCx<'tcx, Self>, frame: Frame<'tcx, Provenance, FrameExtra<'tcx>>, unwinding: bool, - ) -> InterpResult<'tcx, StackPopJump> { + ) -> InterpResult<'tcx, ReturnAction> { if frame.extra.is_user_relevant { // All that we store is whether or not the frame we just removed is local, so now we // have no idea where the next topmost local frame is. So we recompute it. diff --git a/src/tools/miri/src/shims/panic.rs b/src/tools/miri/src/shims/panic.rs index ef832f5bbbd15..306dce5edcd3d 100644 --- a/src/tools/miri/src/shims/panic.rs +++ b/src/tools/miri/src/shims/panic.rs @@ -113,7 +113,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { &mut self, mut extra: FrameExtra<'tcx>, unwinding: bool, - ) -> InterpResult<'tcx, StackPopJump> { + ) -> InterpResult<'tcx, ReturnAction> { let this = self.eval_context_mut(); trace!("handle_stack_pop_unwind(extra = {:?}, unwinding = {})", extra, unwinding); @@ -150,9 +150,9 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> { )?; // We pushed a new stack frame, the engine should not do any jumping now! - Ok(StackPopJump::NoJump) + Ok(ReturnAction::NoJump) } else { - Ok(StackPopJump::Normal) + Ok(ReturnAction::Normal) } } From dd5a447b5abaabcefd1b8e6e03470aa345226e1e Mon Sep 17 00:00:00 2001 From: Maybe Waffle Date: Mon, 17 Jun 2024 17:36:26 +0000 Subject: [PATCH 28/32] Do renames proposed by review --- .../rustc_const_eval/src/interpret/eval_context.rs | 14 +++++++------- .../rustc_const_eval/src/interpret/terminator.rs | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index bc15e41343fcd..23ade4b0106dd 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -160,7 +160,7 @@ pub enum StackPopCleanup { } /// Return type of [`InterpCx::pop_stack_frame`]. -pub struct StackPop<'tcx, Prov: Provenance> { +pub struct StackPopInfo<'tcx, Prov: Provenance> { /// Additional information about the action to be performed when returning from the popped /// stack frame. pub return_action: ReturnAction, @@ -890,7 +890,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { pub fn pop_stack_frame( &mut self, unwinding: bool, - ) -> InterpResult<'tcx, StackPop<'tcx, M::Provenance>> { + ) -> InterpResult<'tcx, StackPopInfo<'tcx, M::Provenance>> { let cleanup = self.cleanup_current_frame_locals()?; let frame = @@ -905,7 +905,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { ReturnAction::NoCleanup }; - Ok(StackPop { return_action, return_to_block, return_place }) + Ok(StackPopInfo { return_action, return_to_block, return_place }) } /// A private helper for [`pop_stack_frame`](InterpCx::pop_stack_frame). @@ -1043,12 +1043,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { }; // All right, now it is time to actually pop the frame. - let frame = self.pop_stack_frame(unwinding)?; + let stack_pop_info = self.pop_stack_frame(unwinding)?; // Report error from return value copy, if any. copy_ret_result?; - match frame.return_action { + match stack_pop_info.return_action { ReturnAction::Normal => {} ReturnAction::NoJump => { // The hook already did everything. @@ -1066,7 +1066,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // Normal return, figure out where to jump. if unwinding { // Follow the unwind edge. - let unwind = match frame.return_to_block { + let unwind = match stack_pop_info.return_to_block { StackPopCleanup::Goto { unwind, .. } => unwind, StackPopCleanup::Root { .. } => { panic!("encountered StackPopCleanup::Root when unwinding!") @@ -1076,7 +1076,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { self.unwind_to_block(unwind) } else { // Follow the normal return edge. - match frame.return_to_block { + match stack_pop_info.return_to_block { StackPopCleanup::Goto { ret, .. } => self.return_to_block(ret), StackPopCleanup::Root { .. } => { assert!( diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 8886d7d8af571..76051f4651f56 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -27,7 +27,7 @@ use super::{ }; use crate::{ fluent_generated as fluent, - interpret::{eval_context::StackPop, ReturnAction}, + interpret::{eval_context::StackPopInfo, ReturnAction}, }; /// An argment passed to a function. @@ -982,7 +982,7 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { // only the tail called function should return to the current return block. M::before_stack_pop(self, self.frame())?; - let StackPop { return_action, return_to_block, return_place } = + let StackPopInfo { return_action, return_to_block, return_place } = self.pop_stack_frame(false)?; assert_eq!(return_action, ReturnAction::Normal); From 54e11cf378d5f3a423d98b9a8bcbc6c25078995a Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Sun, 7 Jul 2024 17:01:28 +0200 Subject: [PATCH 29/32] add an assertion that machine hook doesn't return NoCleanup --- compiler/rustc_const_eval/src/interpret/eval_context.rs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/compiler/rustc_const_eval/src/interpret/eval_context.rs b/compiler/rustc_const_eval/src/interpret/eval_context.rs index 23ade4b0106dd..6d3e5ea103148 100644 --- a/compiler/rustc_const_eval/src/interpret/eval_context.rs +++ b/compiler/rustc_const_eval/src/interpret/eval_context.rs @@ -899,10 +899,12 @@ impl<'tcx, M: Machine<'tcx>> InterpCx<'tcx, M> { let return_to_block = frame.return_to_block; let return_place = frame.return_place.clone(); - let return_action = if cleanup { - M::after_stack_pop(self, frame, unwinding)? + let return_action; + if cleanup { + return_action = M::after_stack_pop(self, frame, unwinding)?; + assert_ne!(return_action, ReturnAction::NoCleanup); } else { - ReturnAction::NoCleanup + return_action = ReturnAction::NoCleanup; }; Ok(StackPopInfo { return_action, return_to_block, return_place }) From 7fd0c55a1a85b42b02c378ba5472243cc17211dd Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Sun, 7 Jul 2024 18:04:29 +0200 Subject: [PATCH 30/32] Fix conflicts after rebase - r-l/r 126784 - r-l/r 127113 - r-l/miri 3562 --- compiler/rustc_middle/src/mir/syntax.rs | 2 +- compiler/rustc_mir_build/src/build/expr/stmt.rs | 2 +- compiler/rustc_mir_transform/src/cost_checker.rs | 7 ++++++- src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr | 2 +- .../tests/fail/tail_calls/signature-mismatch-arg.stderr | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/compiler/rustc_middle/src/mir/syntax.rs b/compiler/rustc_middle/src/mir/syntax.rs index deaa1259f6b73..620fa962d791f 100644 --- a/compiler/rustc_middle/src/mir/syntax.rs +++ b/compiler/rustc_middle/src/mir/syntax.rs @@ -767,7 +767,7 @@ pub enum TerminatorKind<'tcx> { /// These are owned by the callee, which is free to modify them. /// This allows the memory occupied by "by-value" arguments to be /// reused across function calls without duplicating the contents. - args: Vec>>, + args: Box<[Spanned>]>, // FIXME(explicit_tail_calls): should we have the span for `become`? is this span accurate? do we need it? /// This `Span` is the span of the function, without the dot and receiver /// (e.g. `foo(a, b)` in `x.foo(a, b)` diff --git a/compiler/rustc_mir_build/src/build/expr/stmt.rs b/compiler/rustc_mir_build/src/build/expr/stmt.rs index 7d2c32e000b90..88b76c46c90bf 100644 --- a/compiler/rustc_mir_build/src/build/expr/stmt.rs +++ b/compiler/rustc_mir_build/src/build/expr/stmt.rs @@ -106,7 +106,7 @@ impl<'a, 'tcx> Builder<'a, 'tcx> { this.in_scope((region_scope, source_info), lint_level, |this| { let fun = unpack!(block = this.as_local_operand(block, fun)); - let args: Vec<_> = args + let args: Box<[_]> = args .into_iter() .copied() .map(|arg| Spanned { diff --git a/compiler/rustc_mir_transform/src/cost_checker.rs b/compiler/rustc_mir_transform/src/cost_checker.rs index 3333bebff3a6e..a1c1422912ee3 100644 --- a/compiler/rustc_mir_transform/src/cost_checker.rs +++ b/compiler/rustc_mir_transform/src/cost_checker.rs @@ -40,7 +40,9 @@ impl<'b, 'tcx> CostChecker<'b, 'tcx> { fn is_call_like(bbd: &BasicBlockData<'_>) -> bool { use TerminatorKind::*; match bbd.terminator().kind { - Call { .. } | Drop { .. } | Assert { .. } | InlineAsm { .. } => true, + Call { .. } | TailCall { .. } | Drop { .. } | Assert { .. } | InlineAsm { .. } => { + true + } Goto { .. } | SwitchInt { .. } @@ -137,6 +139,9 @@ impl<'tcx> Visitor<'tcx> for CostChecker<'_, 'tcx> { self.penalty += LANDINGPAD_PENALTY; } } + TerminatorKind::TailCall { .. } => { + self.penalty += CALL_PENALTY; + } TerminatorKind::SwitchInt { discr, targets } => { if discr.constant().is_some() { // Not only will this become a `Goto`, but likely other diff --git a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr index 708972e6eff13..b157e9f0b211a 100644 --- a/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr +++ b/src/tools/miri/tests/fail/tail_calls/cc-mismatch.stderr @@ -8,7 +8,7 @@ LL | extern "rust-call" fn call_once(self, args: Args) -> Self::Output; = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: BACKTRACE: = note: inside `>::call_once - shim(fn())` at RUSTLIB/core/src/ops/function.rs:LL:CC - = note: inside `std::sys_common::backtrace::__rust_begin_short_backtrace::` at RUSTLIB/std/src/sys_common/backtrace.rs:LL:CC + = note: inside `std::sys::backtrace::__rust_begin_short_backtrace::` at RUSTLIB/std/src/sys/backtrace.rs:LL:CC = note: inside closure at RUSTLIB/std/src/rt.rs:LL:CC = note: inside `std::ops::function::impls:: for &dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe>::call_once` at RUSTLIB/core/src/ops/function.rs:LL:CC = note: inside `std::panicking::r#try::do_call::<&dyn std::ops::Fn() -> i32 + std::marker::Sync + std::panic::RefUnwindSafe, i32>` at RUSTLIB/std/src/panicking.rs:LL:CC diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr index 2ecc5674c6537..8823ab9b97059 100644 --- a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr +++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.stderr @@ -7,7 +7,7 @@ LL | f(0); = 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 = help: this means these two types are not *guaranteed* to be ABI-compatible across all targets - = help: if you think this code should be accepted anyway, please report an issue + = help: if you think this code should be accepted anyway, please report an issue with Miri = note: BACKTRACE: = note: inside `main` at $DIR/signature-mismatch-arg.rs:LL:CC From 39eaefc15db286dc295134f0c65704e90eae1d0e Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Sun, 7 Jul 2024 20:16:48 +0200 Subject: [PATCH 31/32] Fixup conflict with r-l/r/126567 --- compiler/rustc_const_eval/src/interpret/terminator.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/compiler/rustc_const_eval/src/interpret/terminator.rs b/compiler/rustc_const_eval/src/interpret/terminator.rs index 76051f4651f56..25f6bd640554e 100644 --- a/compiler/rustc_const_eval/src/interpret/terminator.rs +++ b/compiler/rustc_const_eval/src/interpret/terminator.rs @@ -54,7 +54,7 @@ struct EvaluatedCalleeAndArgs<'tcx, M: Machine<'tcx>> { args: Vec>, fn_sig: ty::FnSig<'tcx>, fn_abi: &'tcx FnAbi<'tcx, Ty<'tcx>>, - /// True if the function is marked as `#[track_caller]` ([`ty::InstanceDef::requires_caller_location`]) + /// True if the function is marked as `#[track_caller]` ([`ty::InstanceKind::requires_caller_location`]) with_caller_location: bool, } From 14e5d5fbee637af09759f924e14f97685e5c24ea Mon Sep 17 00:00:00 2001 From: Maybe Lapkin Date: Sun, 7 Jul 2024 20:18:42 +0200 Subject: [PATCH 32/32] Fixup a typo in a comment in a test --- src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs index 3264a74d159e3..6df132d325591 100644 --- a/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs +++ b/src/tools/miri/tests/fail/tail_calls/signature-mismatch-arg.rs @@ -3,7 +3,7 @@ fn main() { // FIXME(explicit_tail_calls): - // the error should point to `become f(x)`, + // the error should point to `become g(x)`, // but tail calls mess up the backtrace it seems like... f(0); //~^ error: Undefined Behavior: calling a function with argument of type i32 passing data of type u32