From c956fe5cea5c2221e54a98b6f83214b08eb5ce24 Mon Sep 17 00:00:00 2001 From: Jeff Date: Thu, 3 Mar 2022 19:45:53 -0500 Subject: [PATCH 01/13] Document new recommended use of method --- library/core/src/iter/traits/collect.rs | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/library/core/src/iter/traits/collect.rs b/library/core/src/iter/traits/collect.rs index 637d7bc44885e..9af3c0d48092b 100644 --- a/library/core/src/iter/traits/collect.rs +++ b/library/core/src/iter/traits/collect.rs @@ -4,9 +4,11 @@ /// created from an iterator. This is common for types which describe a /// collection of some kind. /// -/// [`FromIterator::from_iter()`] is rarely called explicitly, and is instead -/// used through [`Iterator::collect()`] method. See [`Iterator::collect()`]'s -/// documentation for more examples. +/// If you want to create a collection from the contents of an iterator, the +/// [`Iterator::collect()`] method is preferred. However, the compiler is +/// sometimes unable to infer the full type of the collection. In these cases, +/// [`FromIterator::from_iter()`] can be more concise and readable. See the +/// [`Iterator::collect()`] documentation for more examples of its use. /// /// See also: [`IntoIterator`]. /// @@ -32,6 +34,17 @@ /// assert_eq!(v, vec![5, 5, 5, 5, 5]); /// ``` /// +/// Using [`FromIterator::from_iter()`] as a more readable alternative to +/// [`Iterator::collect()`]: +/// +/// ``` +/// # use std::collections::VecDeque; +/// let first = (0..10).collect::>(); +/// let second = VecDeque::from_iter(0..10); +/// +/// assert_eq!(first, second); +/// ``` +/// /// Implementing `FromIterator` for your type: /// /// ``` From 5f34c04de66c9ec98dd4f6f6d88bb6c10d21b9fb Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 4 Mar 2022 09:45:18 -0500 Subject: [PATCH 02/13] Make use statement visible --- library/core/src/iter/traits/collect.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/library/core/src/iter/traits/collect.rs b/library/core/src/iter/traits/collect.rs index 9af3c0d48092b..28ae227725281 100644 --- a/library/core/src/iter/traits/collect.rs +++ b/library/core/src/iter/traits/collect.rs @@ -38,7 +38,7 @@ /// [`Iterator::collect()`]: /// /// ``` -/// # use std::collections::VecDeque; +/// use std::collections::VecDeque; /// let first = (0..10).collect::>(); /// let second = VecDeque::from_iter(0..10); /// From b363f130698dbc55fe594155bcb4df826ffad71e Mon Sep 17 00:00:00 2001 From: Jeff Date: Fri, 4 Mar 2022 09:48:51 -0500 Subject: [PATCH 03/13] Add suggested changes to the docs --- library/core/src/iter/traits/collect.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/library/core/src/iter/traits/collect.rs b/library/core/src/iter/traits/collect.rs index 28ae227725281..dee66e6e07283 100644 --- a/library/core/src/iter/traits/collect.rs +++ b/library/core/src/iter/traits/collect.rs @@ -5,9 +5,9 @@ /// collection of some kind. /// /// If you want to create a collection from the contents of an iterator, the -/// [`Iterator::collect()`] method is preferred. However, the compiler is -/// sometimes unable to infer the full type of the collection. In these cases, -/// [`FromIterator::from_iter()`] can be more concise and readable. See the +/// [`Iterator::collect()`] method is preferred. However, when you need to +/// specify the container type, [`FromIterator::from_iter()`] can be more +/// readable than using a turbofish (e.g. `::>()`). See the /// [`Iterator::collect()`] documentation for more examples of its use. /// /// See also: [`IntoIterator`]. From b328688d23d56a7b95ddcd994c3967a193dd25ea Mon Sep 17 00:00:00 2001 From: Jon Gjengset Date: Mon, 7 Mar 2022 16:59:10 -0800 Subject: [PATCH 04/13] Statically compile libstdc++ everywhere if asked PR #93918 made it so that `-static-libstdc++` was only set in one place, and was only set during linking, but accidentally also made it so that it is no longer passed when building LLD or sanitizers, only when building LLVM itself. This moves the logic for setting `-static-libstdc++` in the linker flags back to `configure_cmake` so that it takes effect for all CMake invocations in `native.rs`. As a side-effect, this also causes libstdc++ to be statically compiled into sanitizers and LLD if `llvm-tools-enabled` is set but `llvm-static-stdcpp` is not, even though previously it was only linked statically if `llvm-static-stdcpp` was set explicitly. But that seems more like the expected behavior anyway. --- src/bootstrap/native.rs | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/bootstrap/native.rs b/src/bootstrap/native.rs index f00c5ce5aa6f0..0fe39defae85d 100644 --- a/src/bootstrap/native.rs +++ b/src/bootstrap/native.rs @@ -259,18 +259,6 @@ impl Step for Llvm { cfg.define("LLVM_LINK_LLVM_DYLIB", "ON"); } - // For distribution we want the LLVM tools to be *statically* linked to libstdc++. - // We also do this if the user explicitly requested static libstdc++. - if builder.config.llvm_tools_enabled || builder.config.llvm_static_stdcpp { - if !target.contains("msvc") && !target.contains("netbsd") { - if target.contains("apple") { - ldflags.push_all("-static-libstdc++"); - } else { - ldflags.push_all("-Wl,-Bsymbolic -static-libstdc++"); - } - } - } - if target.starts_with("riscv") && !target.contains("freebsd") { // RISC-V GCC erroneously requires linking against // `libatomic` when using 1-byte and 2-byte C++ @@ -576,6 +564,18 @@ fn configure_cmake( ldflags.push_all(&flags); } + // For distribution we want the LLVM tools to be *statically* linked to libstdc++. + // We also do this if the user explicitly requested static libstdc++. + if builder.config.llvm_tools_enabled || builder.config.llvm_static_stdcpp { + if !target.contains("msvc") && !target.contains("netbsd") { + if target.contains("apple") { + ldflags.push_all("-static-libstdc++"); + } else { + ldflags.push_all("-Wl,-Bsymbolic -static-libstdc++"); + } + } + } + cfg.define("CMAKE_SHARED_LINKER_FLAGS", &ldflags.shared); cfg.define("CMAKE_MODULE_LINKER_FLAGS", &ldflags.module); cfg.define("CMAKE_EXE_LINKER_FLAGS", &ldflags.exe); From 307ee94a8a535019feadf69ce4258cdfb67a3a1c Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Mon, 7 Mar 2022 23:06:59 -0800 Subject: [PATCH 05/13] only emit pointer-like metadata for BZST-allocator Box --- .../src/debuginfo/metadata.rs | 4 +++- .../debuginfo-box-with-large-allocator.rs | 23 +++++++++++++++++++ ...fo_with_uninhabitable_field_and_unsized.rs | 2 +- 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 src/test/ui/debuginfo/debuginfo-box-with-large-allocator.rs diff --git a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs index 89fc898003737..a6e4878e5b361 100644 --- a/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs +++ b/compiler/rustc_codegen_llvm/src/debuginfo/metadata.rs @@ -617,7 +617,9 @@ pub fn type_metadata<'ll, 'tcx>(cx: &CodegenCx<'ll, 'tcx>, t: Ty<'tcx>) -> &'ll ty::RawPtr(ty::TypeAndMut { ty: pointee_type, .. }) | ty::Ref(_, pointee_type, _) => { pointer_or_reference_metadata(cx, t, pointee_type, unique_type_id) } - ty::Adt(def, _) if def.is_box() => { + // Box may have a non-ZST allocator A. In that case, we + // cannot treat Box as just an owned alias of `*mut T`. + ty::Adt(def, substs) if def.is_box() && cx.layout_of(substs.type_at(1)).is_zst() => { pointer_or_reference_metadata(cx, t, t.boxed_ty(), unique_type_id) } ty::FnDef(..) | ty::FnPtr(_) => subroutine_type_metadata(cx, unique_type_id), diff --git a/src/test/ui/debuginfo/debuginfo-box-with-large-allocator.rs b/src/test/ui/debuginfo/debuginfo-box-with-large-allocator.rs new file mode 100644 index 0000000000000..761539227a79c --- /dev/null +++ b/src/test/ui/debuginfo/debuginfo-box-with-large-allocator.rs @@ -0,0 +1,23 @@ +// build-pass +// compile-flags: -Cdebuginfo=2 +// fixes issue #94725 + +#![feature(allocator_api)] + +use std::alloc::{AllocError, Allocator, Layout}; +use std::ptr::NonNull; + +struct ZST; + +unsafe impl Allocator for &ZST { + fn allocate(&self, layout: Layout) -> Result, AllocError> { + todo!() + } + unsafe fn deallocate(&self, ptr: NonNull, layout: Layout) { + todo!() + } +} + +fn main() { + let _ = Box::::new_in(43, &ZST); +} diff --git a/src/test/ui/debuginfo/debuginfo_with_uninhabitable_field_and_unsized.rs b/src/test/ui/debuginfo/debuginfo_with_uninhabitable_field_and_unsized.rs index 833a4726acb0f..b3f22ecf5115e 100644 --- a/src/test/ui/debuginfo/debuginfo_with_uninhabitable_field_and_unsized.rs +++ b/src/test/ui/debuginfo/debuginfo_with_uninhabitable_field_and_unsized.rs @@ -1,4 +1,4 @@ -// check-pass +// build-pass // compile-flags: -Cdebuginfo=2 // fixes issue #94149 From 5226395d6faef77a5f1dadb6235bcd99352e1843 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Sat, 5 Mar 2022 17:19:04 +0100 Subject: [PATCH 06/13] Fix soundness issue in scoped threads. --- library/std/src/thread/mod.rs | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index 5ffc86b4560fc..56baa7e445588 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -1293,6 +1293,23 @@ impl<'scope, T> Drop for Packet<'scope, T> { // panicked, and nobody consumed the panic payload, we make sure // the scope function will panic. let unhandled_panic = matches!(self.result.get_mut(), Some(Err(_))); + // Drop the result before decrementing the number of running + // threads, because the Drop implementation might still use things + // it borrowed from 'scope. + // This is only relevant for threads that aren't join()ed, as + // join() will take the `result` and set it to None, such that + // there is nothing left to drop here. + // If this drop panics, that just results in an abort, because + // we're outside of the outermost `catch_unwind` of our thread. + // The same happens for detached non-scoped threads when dropping + // their ignored return value (or panic payload) panics, so + // there's no need to try to do anything better. + // (And even if we tried to handle it, we'd also need to handle + // the case where the panic payload we get out of it also panics + // on drop, and so on. See issue #86027.) + *self.result.get_mut() = None; + // Now that there will be no more user code running on this thread + // that can use 'scope, mark the thread as 'finished'. scope.decrement_num_running_threads(unhandled_panic); } } From 7a481ff8a412121b0a52c51c086ae22ed7e96ab5 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Sat, 5 Mar 2022 18:07:20 +0100 Subject: [PATCH 07/13] Properly abort when thread result panics on drop. --- library/std/src/lib.rs | 10 ++++----- library/std/src/thread/mod.rs | 40 ++++++++++++++++++----------------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/library/std/src/lib.rs b/library/std/src/lib.rs index c35389d44f9fc..232b66230782f 100644 --- a/library/std/src/lib.rs +++ b/library/std/src/lib.rs @@ -363,6 +363,11 @@ extern crate std as realstd; #[macro_use] mod macros; +// The runtime entry point and a few unstable public functions used by the +// compiler +#[macro_use] +pub mod rt; + // The Rust prelude pub mod prelude; @@ -547,11 +552,6 @@ pub mod arch { #[stable(feature = "simd_x86", since = "1.27.0")] pub use std_detect::is_x86_feature_detected; -// The runtime entry point and a few unstable public functions used by the -// compiler -#[macro_use] -pub mod rt; - // Platform-abstraction modules mod sys; mod sys_common; diff --git a/library/std/src/thread/mod.rs b/library/std/src/thread/mod.rs index 56baa7e445588..74b29454b94a4 100644 --- a/library/std/src/thread/mod.rs +++ b/library/std/src/thread/mod.rs @@ -1287,29 +1287,31 @@ unsafe impl<'scope, T: Sync> Sync for Packet<'scope, T> {} impl<'scope, T> Drop for Packet<'scope, T> { fn drop(&mut self) { + // If this packet was for a thread that ran in a scope, the thread + // panicked, and nobody consumed the panic payload, we make sure + // the scope function will panic. + let unhandled_panic = matches!(self.result.get_mut(), Some(Err(_))); + // Drop the result without causing unwinding. + // This is only relevant for threads that aren't join()ed, as + // join() will take the `result` and set it to None, such that + // there is nothing left to drop here. + // If this panics, we should handle that, because we're outside the + // outermost `catch_unwind` of our thread. + // We just abort in that case, since there's nothing else we can do. + // (And even if we tried to handle it somehow, we'd also need to handle + // the case where the panic payload we get out of it also panics on + // drop, and so on. See issue #86027.) + if let Err(_) = panic::catch_unwind(panic::AssertUnwindSafe(|| { + *self.result.get_mut() = None; + })) { + rtabort!("thread result panicked on drop"); + } // Book-keeping so the scope knows when it's done. if let Some(scope) = self.scope { - // If this packet was for a thread that ran in a scope, the thread - // panicked, and nobody consumed the panic payload, we make sure - // the scope function will panic. - let unhandled_panic = matches!(self.result.get_mut(), Some(Err(_))); - // Drop the result before decrementing the number of running - // threads, because the Drop implementation might still use things - // it borrowed from 'scope. - // This is only relevant for threads that aren't join()ed, as - // join() will take the `result` and set it to None, such that - // there is nothing left to drop here. - // If this drop panics, that just results in an abort, because - // we're outside of the outermost `catch_unwind` of our thread. - // The same happens for detached non-scoped threads when dropping - // their ignored return value (or panic payload) panics, so - // there's no need to try to do anything better. - // (And even if we tried to handle it, we'd also need to handle - // the case where the panic payload we get out of it also panics - // on drop, and so on. See issue #86027.) - *self.result.get_mut() = None; // Now that there will be no more user code running on this thread // that can use 'scope, mark the thread as 'finished'. + // It's important we only do this after the `result` has been dropped, + // since dropping it might still use things it borrowed from 'scope. scope.decrement_num_running_threads(unhandled_panic); } } From 1c06eb7c1f416055d7ede098f35bcf22cf85b7f8 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 9 Mar 2022 11:47:46 +0100 Subject: [PATCH 08/13] Remove outdated comment. --- library/std/src/thread/tests.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/library/std/src/thread/tests.rs b/library/std/src/thread/tests.rs index 3323ba36bf310..27eebd0ddf4ec 100644 --- a/library/std/src/thread/tests.rs +++ b/library/std/src/thread/tests.rs @@ -293,5 +293,3 @@ fn test_thread_id_not_equal() { assert!(thread::current().id() != spawned_id); } -// NOTE: the corresponding test for stderr is in ui/thread-stderr, due -// to the test harness apparently interfering with stderr configuration. From b97d87518d19e418220f726e774ffceadb4d33b9 Mon Sep 17 00:00:00 2001 From: Mara Bos Date: Wed, 9 Mar 2022 11:47:53 +0100 Subject: [PATCH 09/13] Add soundness test for dropping scoped thread results before joining. --- library/std/src/thread/tests.rs | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/library/std/src/thread/tests.rs b/library/std/src/thread/tests.rs index 27eebd0ddf4ec..7386fe1c442ab 100644 --- a/library/std/src/thread/tests.rs +++ b/library/std/src/thread/tests.rs @@ -4,10 +4,11 @@ use crate::mem; use crate::panic::panic_any; use crate::result; use crate::sync::{ + atomic::{AtomicBool, Ordering}, mpsc::{channel, Sender}, Arc, Barrier, }; -use crate::thread::{self, ThreadId}; +use crate::thread::{self, Scope, ThreadId}; use crate::time::Duration; use crate::time::Instant; @@ -293,3 +294,25 @@ fn test_thread_id_not_equal() { assert!(thread::current().id() != spawned_id); } +#[test] +fn test_scoped_threads_drop_result_before_join() { + let actually_finished = &AtomicBool::new(false); + struct X<'scope, 'env>(&'scope Scope<'scope, 'env>, &'env AtomicBool); + impl Drop for X<'_, '_> { + fn drop(&mut self) { + thread::sleep(Duration::from_millis(20)); + let actually_finished = self.1; + self.0.spawn(move || { + thread::sleep(Duration::from_millis(20)); + actually_finished.store(true, Ordering::Relaxed); + }); + } + } + thread::scope(|s| { + s.spawn(move || { + thread::sleep(Duration::from_millis(20)); + X(s, actually_finished) + }); + }); + assert!(actually_finished.load(Ordering::Relaxed)); +} From 8073a88f35728289ef535cca5cf13302faba5972 Mon Sep 17 00:00:00 2001 From: Caio Date: Wed, 9 Mar 2022 16:46:23 -0300 Subject: [PATCH 10/13] Implement macro meta-variable expressions --- compiler/rustc_ast/src/util/literal.rs | 2 +- compiler/rustc_expand/src/lib.rs | 1 + compiler/rustc_expand/src/mbe.rs | 16 +- compiler/rustc_expand/src/mbe/macro_check.rs | 4 + compiler/rustc_expand/src/mbe/macro_parser.rs | 13 +- compiler/rustc_expand/src/mbe/macro_rules.rs | 20 +- compiler/rustc_expand/src/mbe/metavar_expr.rs | 157 ++++++++ compiler/rustc_expand/src/mbe/quoted.rs | 123 ++++-- compiler/rustc_expand/src/mbe/transcribe.rs | 27 ++ compiler/rustc_feature/src/active.rs | 2 + compiler/rustc_span/src/symbol.rs | 1 + .../dollar-dollar-has-correct-behavior.rs | 28 ++ .../feature-gate-macro_metavar_expr.rs | 14 + .../rfc-3086-metavar-expr/required-feature.rs | 9 + .../required-feature.stderr | 12 + .../rfc-3086-metavar-expr/syntax-errors.rs | 148 +++++++ .../syntax-errors.stderr | 367 ++++++++++++++++++ 17 files changed, 900 insertions(+), 44 deletions(-) create mode 100644 compiler/rustc_expand/src/mbe/metavar_expr.rs create mode 100644 src/test/ui/macros/rfc-3086-metavar-expr/dollar-dollar-has-correct-behavior.rs create mode 100644 src/test/ui/macros/rfc-3086-metavar-expr/feature-gate-macro_metavar_expr.rs create mode 100644 src/test/ui/macros/rfc-3086-metavar-expr/required-feature.rs create mode 100644 src/test/ui/macros/rfc-3086-metavar-expr/required-feature.stderr create mode 100644 src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs create mode 100644 src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr diff --git a/compiler/rustc_ast/src/util/literal.rs b/compiler/rustc_ast/src/util/literal.rs index 231dd72af2c7d..9c18f55c03b4d 100644 --- a/compiler/rustc_ast/src/util/literal.rs +++ b/compiler/rustc_ast/src/util/literal.rs @@ -23,7 +23,7 @@ pub enum LitError { impl LitKind { /// Converts literal token into a semantic literal. - fn from_lit_token(lit: token::Lit) -> Result { + pub fn from_lit_token(lit: token::Lit) -> Result { let token::Lit { kind, symbol, suffix } = lit; if suffix.is_some() && !kind.may_have_suffix() { return Err(LitError::InvalidSuffix); diff --git a/compiler/rustc_expand/src/lib.rs b/compiler/rustc_expand/src/lib.rs index b024524aa2968..8a9efe01368e3 100644 --- a/compiler/rustc_expand/src/lib.rs +++ b/compiler/rustc_expand/src/lib.rs @@ -3,6 +3,7 @@ #![feature(crate_visibility_modifier)] #![feature(decl_macro)] #![feature(if_let_guard)] +#![feature(let_chains)] #![feature(let_else)] #![feature(proc_macro_diagnostic)] #![feature(proc_macro_internals)] diff --git a/compiler/rustc_expand/src/mbe.rs b/compiler/rustc_expand/src/mbe.rs index 5244ac36bba5d..3d4c77aba7339 100644 --- a/compiler/rustc_expand/src/mbe.rs +++ b/compiler/rustc_expand/src/mbe.rs @@ -6,17 +6,17 @@ crate mod macro_check; crate mod macro_parser; crate mod macro_rules; +crate mod metavar_expr; crate mod quoted; crate mod transcribe; +use metavar_expr::MetaVarExpr; use rustc_ast::token::{self, NonterminalKind, Token, TokenKind}; use rustc_ast::tokenstream::DelimSpan; - +use rustc_data_structures::sync::Lrc; use rustc_span::symbol::Ident; use rustc_span::Span; -use rustc_data_structures::sync::Lrc; - /// Contains the sub-token-trees of a "delimited" token tree, such as the contents of `(`. Note /// that the delimiter itself might be `NoDelim`. #[derive(Clone, PartialEq, Encodable, Decodable, Debug)] @@ -73,8 +73,8 @@ enum KleeneOp { ZeroOrOne, } -/// Similar to `tokenstream::TokenTree`, except that `$i`, `$i:ident`, and `$(...)` -/// are "first-class" token trees. Useful for parsing macros. +/// Similar to `tokenstream::TokenTree`, except that `$i`, `$i:ident`, `$(...)`, +/// and `${...}` are "first-class" token trees. Useful for parsing macros. #[derive(Debug, Clone, PartialEq, Encodable, Decodable)] enum TokenTree { Token(Token), @@ -85,6 +85,8 @@ enum TokenTree { MetaVar(Span, Ident), /// e.g., `$var:expr`. This is only used in the left hand side of MBE macros. MetaVarDecl(Span, Ident /* name to bind */, Option), + /// A meta-variable expression inside `${...}` + MetaVarExpr(DelimSpan, MetaVarExpr), } impl TokenTree { @@ -139,7 +141,9 @@ impl TokenTree { TokenTree::Token(Token { span, .. }) | TokenTree::MetaVar(span, _) | TokenTree::MetaVarDecl(span, _, _) => span, - TokenTree::Delimited(span, _) | TokenTree::Sequence(span, _) => span.entire(), + TokenTree::Delimited(span, _) + | TokenTree::MetaVarExpr(span, _) + | TokenTree::Sequence(span, _) => span.entire(), } } diff --git a/compiler/rustc_expand/src/mbe/macro_check.rs b/compiler/rustc_expand/src/mbe/macro_check.rs index 3497e5ad543a1..88e1169322093 100644 --- a/compiler/rustc_expand/src/mbe/macro_check.rs +++ b/compiler/rustc_expand/src/mbe/macro_check.rs @@ -278,6 +278,8 @@ fn check_binders( binders.insert(name, BinderInfo { span, ops: ops.into() }); } } + // `MetaVarExpr` can not appear in the LHS of a macro arm + TokenTree::MetaVarExpr(..) => {} TokenTree::Delimited(_, ref del) => { for tt in &del.tts { check_binders(sess, node_id, tt, macros, binders, ops, valid); @@ -335,6 +337,8 @@ fn check_occurrences( let name = MacroRulesNormalizedIdent::new(name); check_ops_is_prefix(sess, node_id, macros, binders, ops, span, name); } + // FIXME(c410-f3r) Check token (https://github.com/rust-lang/rust/issues/93902) + TokenTree::MetaVarExpr(..) => {} TokenTree::Delimited(_, ref del) => { check_nested_occurrences(sess, node_id, &del.tts, macros, binders, ops, valid); } diff --git a/compiler/rustc_expand/src/mbe/macro_parser.rs b/compiler/rustc_expand/src/mbe/macro_parser.rs index bb36dfd793d4a..04137086088dd 100644 --- a/compiler/rustc_expand/src/mbe/macro_parser.rs +++ b/compiler/rustc_expand/src/mbe/macro_parser.rs @@ -200,7 +200,7 @@ struct MatcherPos<'root, 'tt> { // This type is used a lot. Make sure it doesn't unintentionally get bigger. #[cfg(all(target_arch = "x86_64", target_pointer_width = "64"))] -rustc_data_structures::static_assert_size!(MatcherPos<'_, '_>, 192); +rustc_data_structures::static_assert_size!(MatcherPos<'_, '_>, 240); impl<'root, 'tt> MatcherPos<'root, 'tt> { /// Generates the top-level matcher position in which the "dot" is before the first token of @@ -321,10 +321,13 @@ pub(super) fn count_names(ms: &[TokenTree]) -> usize { ms.iter().fold(0, |count, elt| { count + match *elt { - TokenTree::Sequence(_, ref seq) => seq.num_captures, TokenTree::Delimited(_, ref delim) => count_names(&delim.tts), TokenTree::MetaVar(..) => 0, TokenTree::MetaVarDecl(..) => 1, + // FIXME(c410-f3r) MetaVarExpr should be handled instead of being ignored + // https://github.com/rust-lang/rust/issues/9390 + TokenTree::MetaVarExpr(..) => 0, + TokenTree::Sequence(_, ref seq) => seq.num_captures, TokenTree::Token(..) => 0, } }) @@ -436,7 +439,9 @@ fn nameize>( } Occupied(..) => return Err((sp, format!("duplicated bind name: {}", bind_name))), }, - TokenTree::MetaVar(..) | TokenTree::Token(..) => (), + // FIXME(c410-f3r) MetaVar and MetaVarExpr should be handled instead of being ignored + // https://github.com/rust-lang/rust/issues/9390 + TokenTree::MetaVar(..) | TokenTree::MetaVarExpr(..) | TokenTree::Token(..) => {} } Ok(()) @@ -650,7 +655,7 @@ fn inner_parse_loop<'root, 'tt>( // rules. NOTE that this is not necessarily an error unless _all_ items in // `cur_items` end up doing this. There may still be some other matchers that do // end up working out. - TokenTree::Token(..) | TokenTree::MetaVar(..) => {} + TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarExpr(..) => {} } } } diff --git a/compiler/rustc_expand/src/mbe/macro_rules.rs b/compiler/rustc_expand/src/mbe/macro_rules.rs index 62ea3aa76a46c..c3b1b34aa29b9 100644 --- a/compiler/rustc_expand/src/mbe/macro_rules.rs +++ b/compiler/rustc_expand/src/mbe/macro_rules.rs @@ -580,7 +580,10 @@ fn check_lhs_no_empty_seq(sess: &ParseSess, tts: &[mbe::TokenTree]) -> bool { use mbe::TokenTree; for tt in tts { match *tt { - TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => (), + TokenTree::Token(..) + | TokenTree::MetaVar(..) + | TokenTree::MetaVarDecl(..) + | TokenTree::MetaVarExpr(..) => (), TokenTree::Delimited(_, ref del) => { if !check_lhs_no_empty_seq(sess, &del.tts) { return false; @@ -669,7 +672,10 @@ impl FirstSets { let mut first = TokenSet::empty(); for tt in tts.iter().rev() { match *tt { - TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => { + TokenTree::Token(..) + | TokenTree::MetaVar(..) + | TokenTree::MetaVarDecl(..) + | TokenTree::MetaVarExpr(..) => { first.replace_with(tt.clone()); } TokenTree::Delimited(span, ref delimited) => { @@ -731,7 +737,10 @@ impl FirstSets { for tt in tts.iter() { assert!(first.maybe_empty); match *tt { - TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => { + TokenTree::Token(..) + | TokenTree::MetaVar(..) + | TokenTree::MetaVarDecl(..) + | TokenTree::MetaVarExpr(..) => { first.add_one(tt.clone()); return first; } @@ -907,7 +916,10 @@ fn check_matcher_core( // First, update `last` so that it corresponds to the set // of NT tokens that might end the sequence `... token`. match *token { - TokenTree::Token(..) | TokenTree::MetaVar(..) | TokenTree::MetaVarDecl(..) => { + TokenTree::Token(..) + | TokenTree::MetaVar(..) + | TokenTree::MetaVarDecl(..) + | TokenTree::MetaVarExpr(..) => { if token_can_be_followed_by_any(token) { // don't need to track tokens that work with any, last.replace_with_irrelevant(); diff --git a/compiler/rustc_expand/src/mbe/metavar_expr.rs b/compiler/rustc_expand/src/mbe/metavar_expr.rs new file mode 100644 index 0000000000000..6c5a755da6f40 --- /dev/null +++ b/compiler/rustc_expand/src/mbe/metavar_expr.rs @@ -0,0 +1,157 @@ +use rustc_ast::token; +use rustc_ast::tokenstream::{Cursor, TokenStream, TokenTree}; +use rustc_ast::{LitIntType, LitKind}; +use rustc_ast_pretty::pprust; +use rustc_errors::{Applicability, PResult}; +use rustc_session::parse::ParseSess; +use rustc_span::symbol::Ident; +use rustc_span::Span; + +/// A meta-variable expression, for expansions based on properties of meta-variables. +#[derive(Debug, Clone, PartialEq, Encodable, Decodable)] +crate enum MetaVarExpr { + /// The number of repetitions of an identifier, optionally limited to a number + /// of outer-most repetition depths. If the depth limit is `None` then the depth is unlimited. + Count(Ident, Option), + + /// Ignore a meta-variable for repetition without expansion. + Ignore(Ident), + + /// The index of the repetition at a particular depth, where 0 is the inner-most + /// repetition. The `usize` is the depth. + Index(usize), + + /// The length of the repetition at a particular depth, where 0 is the inner-most + /// repetition. The `usize` is the depth. + Length(usize), +} + +impl MetaVarExpr { + /// Attempt to parse a meta-variable expression from a token stream. + crate fn parse<'sess>( + input: &TokenStream, + outer_span: Span, + sess: &'sess ParseSess, + ) -> PResult<'sess, MetaVarExpr> { + let mut tts = input.trees(); + let ident = parse_ident(&mut tts, sess, outer_span)?; + let Some(TokenTree::Delimited(_, token::Paren, args)) = tts.next() else { + let msg = "meta-variable expression parameter must be wrapped in parentheses"; + return Err(sess.span_diagnostic.struct_span_err(ident.span, msg)); + }; + check_trailing_token(&mut tts, sess)?; + let mut iter = args.trees(); + let rslt = match &*ident.as_str() { + "count" => parse_count(&mut iter, sess, ident.span)?, + "ignore" => MetaVarExpr::Ignore(parse_ident(&mut iter, sess, ident.span)?), + "index" => MetaVarExpr::Index(parse_depth(&mut iter, sess, ident.span)?), + "length" => MetaVarExpr::Length(parse_depth(&mut iter, sess, ident.span)?), + _ => { + let err_msg = "unrecognized meta-variable expression"; + let mut err = sess.span_diagnostic.struct_span_err(ident.span, err_msg); + err.span_suggestion( + ident.span, + "supported expressions are count, ignore, index and length", + String::new(), + Applicability::MachineApplicable, + ); + return Err(err); + } + }; + check_trailing_token(&mut iter, sess)?; + Ok(rslt) + } + + crate fn ident(&self) -> Option<&Ident> { + match self { + MetaVarExpr::Count(ident, _) | MetaVarExpr::Ignore(ident) => Some(&ident), + MetaVarExpr::Index(..) | MetaVarExpr::Length(..) => None, + } + } +} + +// Checks if there are any remaining tokens. For example, `${ignore(ident ... a b c ...)}` +fn check_trailing_token<'sess>(iter: &mut Cursor, sess: &'sess ParseSess) -> PResult<'sess, ()> { + if let Some(tt) = iter.next() { + let mut diag = sess.span_diagnostic.struct_span_err( + tt.span(), + &format!("unexpected token: {}", pprust::tt_to_string(&tt)), + ); + diag.span_note(tt.span(), "meta-variable expression must not have trailing tokens"); + Err(diag) + } else { + Ok(()) + } +} + +/// Parse a meta-variable `count` expression: `count(ident[, depth])` +fn parse_count<'sess>( + iter: &mut Cursor, + sess: &'sess ParseSess, + span: Span, +) -> PResult<'sess, MetaVarExpr> { + let ident = parse_ident(iter, sess, span)?; + let depth = if try_eat_comma(iter) { Some(parse_depth(iter, sess, span)?) } else { None }; + Ok(MetaVarExpr::Count(ident, depth)) +} + +/// Parses the depth used by index(depth) and length(depth). +fn parse_depth<'sess>( + iter: &mut Cursor, + sess: &'sess ParseSess, + span: Span, +) -> PResult<'sess, usize> { + let Some(tt) = iter.next() else { return Ok(0) }; + let TokenTree::Token(token::Token { + kind: token::TokenKind::Literal(lit), .. + }) = tt else { + return Err(sess.span_diagnostic.struct_span_err( + span, + "meta-variable expression depth must be a literal" + )); + }; + if let Ok(lit_kind) = LitKind::from_lit_token(lit) + && let LitKind::Int(n_u128, LitIntType::Unsuffixed) = lit_kind + && let Ok(n_usize) = usize::try_from(n_u128) + { + Ok(n_usize) + } + else { + let msg = "only unsuffixes integer literals are supported in meta-variable expressions"; + Err(sess.span_diagnostic.struct_span_err(span, msg)) + } +} + +/// Parses an generic ident +fn parse_ident<'sess>( + iter: &mut Cursor, + sess: &'sess ParseSess, + span: Span, +) -> PResult<'sess, Ident> { + let err_fn = |msg| sess.span_diagnostic.struct_span_err(span, msg); + if let Some(tt) = iter.next() && let TokenTree::Token(token) = tt { + if let Some((elem, false)) = token.ident() { + return Ok(elem); + } + let token_str = pprust::token_to_string(&token); + let mut err = err_fn(&format!("expected identifier, found `{}`", &token_str)); + err.span_suggestion( + token.span, + &format!("try removing `{}`", &token_str), + String::new(), + Applicability::MaybeIncorrect, + ); + return Err(err); + } + Err(err_fn("expected identifier")) +} + +/// 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 Cursor) -> bool { + if let Some(TokenTree::Token(token::Token { kind: token::Comma, .. })) = iter.look_ahead(0) { + let _ = iter.next(); + return true; + } + false +} diff --git a/compiler/rustc_expand/src/mbe/quoted.rs b/compiler/rustc_expand/src/mbe/quoted.rs index dedc6c618b9a4..12c5dac9e0bf4 100644 --- a/compiler/rustc_expand/src/mbe/quoted.rs +++ b/compiler/rustc_expand/src/mbe/quoted.rs @@ -1,13 +1,13 @@ use crate::mbe::macro_parser; -use crate::mbe::{Delimited, KleeneOp, KleeneToken, SequenceRepetition, TokenTree}; +use crate::mbe::{Delimited, KleeneOp, KleeneToken, MetaVarExpr, SequenceRepetition, TokenTree}; use rustc_ast::token::{self, Token}; use rustc_ast::tokenstream; use rustc_ast::{NodeId, DUMMY_NODE_ID}; use rustc_ast_pretty::pprust; use rustc_feature::Features; -use rustc_session::parse::ParseSess; -use rustc_span::symbol::{kw, Ident}; +use rustc_session::parse::{feature_err, ParseSess}; +use rustc_span::symbol::{kw, sym, Ident}; use rustc_span::edition::Edition; use rustc_span::{Span, SyntaxContext}; @@ -25,22 +25,22 @@ const VALID_FRAGMENT_NAMES_MSG: &str = "valid fragment specifiers are \ /// # Parameters /// /// - `input`: a token stream to read from, the contents of which we are parsing. -/// - `expect_matchers`: `parse` can be used to parse either the "patterns" or the "body" of a -/// macro. Both take roughly the same form _except_ that in a pattern, metavars are declared with -/// their "matcher" type. For example `$var:expr` or `$id:ident`. In this example, `expr` and -/// `ident` are "matchers". They are not present in the body of a macro rule -- just in the -/// pattern, so we pass a parameter to indicate whether to expect them or not. +/// - `parsing_patterns`: `parse` can be used to parse either the "patterns" or the "body" of a +/// macro. Both take roughly the same form _except_ that: +/// - In a pattern, metavars are declared with their "matcher" type. For example `$var:expr` or +/// `$id:ident`. In this example, `expr` and `ident` are "matchers". They are not present in the +/// body of a macro rule -- just in the pattern. +/// - Metavariable expressions are only valid in the "body", not the "pattern". /// - `sess`: the parsing session. Any errors will be emitted to this session. /// - `node_id`: the NodeId of the macro we are parsing. /// - `features`: language features so we can do feature gating. -/// - `edition`: the edition of the crate defining the macro /// /// # Returns /// /// A collection of `self::TokenTree`. There may also be some errors emitted to `sess`. pub(super) fn parse( input: tokenstream::TokenStream, - expect_matchers: bool, + parsing_patterns: bool, sess: &ParseSess, node_id: NodeId, features: &Features, @@ -55,9 +55,9 @@ pub(super) fn parse( while let Some(tree) = trees.next() { // Given the parsed tree, if there is a metavar and we are expecting matchers, actually // parse out the matcher (i.e., in `$id:ident` this would parse the `:` and `ident`). - let tree = parse_tree(tree, &mut trees, expect_matchers, sess, node_id, features, edition); + let tree = parse_tree(tree, &mut trees, parsing_patterns, sess, node_id, features, edition); match tree { - TokenTree::MetaVar(start_sp, ident) if expect_matchers => { + TokenTree::MetaVar(start_sp, ident) if parsing_patterns => { let span = match trees.next() { Some(tokenstream::TokenTree::Token(Token { kind: token::Colon, span })) => { match trees.next() { @@ -118,6 +118,14 @@ pub(super) fn parse( result } +/// Asks for the `macro_metavar_expr` feature if it is not already declared +fn maybe_emit_macro_metavar_expr_feature(features: &Features, sess: &ParseSess, span: Span) { + if !features.macro_metavar_expr { + let msg = "meta-variable expressions are unstable"; + feature_err(&sess, sym::macro_metavar_expr, span, msg).emit(); + } +} + /// Takes a `tokenstream::TokenTree` and returns a `self::TokenTree`. Specifically, this takes a /// generic `TokenTree`, such as is used in the rest of the compiler, and returns a `TokenTree` /// for use in parsing a macro. @@ -129,14 +137,13 @@ pub(super) fn parse( /// - `tree`: the tree we wish to convert. /// - `outer_trees`: an iterator over trees. We may need to read more tokens from it in order to finish /// converting `tree` -/// - `expect_matchers`: same as for `parse` (see above). +/// - `parsing_patterns`: same as [parse]. /// - `sess`: the parsing session. Any errors will be emitted to this session. /// - `features`: language features so we can do feature gating. -/// - `edition` - the edition of the crate defining the macro fn parse_tree( tree: tokenstream::TokenTree, outer_trees: &mut impl Iterator, - expect_matchers: bool, + parsing_patterns: bool, sess: &ParseSess, node_id: NodeId, features: &Features, @@ -158,24 +165,57 @@ fn parse_tree( } match next { - // `tree` is followed by a delimited set of token trees. This indicates the beginning - // of a repetition sequence in the macro (e.g. `$(pat)*`). - Some(tokenstream::TokenTree::Delimited(span, delim, tts)) => { - // Must have `(` not `{` or `[` - if delim != token::Paren { - let tok = pprust::token_kind_to_string(&token::OpenDelim(delim)); - let msg = format!("expected `(`, found `{}`", tok); - sess.span_diagnostic.span_err(span.entire(), &msg); + // `tree` is followed by a delimited set of token trees. + Some(tokenstream::TokenTree::Delimited(delim_span, delim, tts)) => { + if parsing_patterns { + if delim != token::Paren { + span_dollar_dollar_or_metavar_in_the_lhs_err( + sess, + &Token { kind: token::OpenDelim(delim), span: delim_span.entire() }, + ); + } + } else { + match delim { + token::Brace => { + // The delimiter is `{`. This indicates the beginning + // of a meta-variable expression (e.g. `${count(ident)}`). + // Try to parse the meta-variable expression. + match MetaVarExpr::parse(&tts, delim_span.entire(), sess) { + Err(mut err) => { + err.emit(); + // Returns early the same read `$` to avoid spanning + // unrelated diagnostics that could be performed afterwards + return TokenTree::token(token::Dollar, span); + } + Ok(elem) => { + maybe_emit_macro_metavar_expr_feature( + features, + sess, + delim_span.entire(), + ); + return TokenTree::MetaVarExpr(delim_span, elem); + } + } + } + token::Paren => {} + _ => { + let tok = pprust::token_kind_to_string(&token::OpenDelim(delim)); + let msg = format!("expected `(` or `{{`, found `{}`", tok); + sess.span_diagnostic.span_err(delim_span.entire(), &msg); + } + } } - // Parse the contents of the sequence itself - let sequence = parse(tts, expect_matchers, sess, node_id, features, edition); + // If we didn't find a metavar expression above, then we must have a + // repetition sequence in the macro (e.g. `$(pat)*`). Parse the + // contents of the sequence itself + let sequence = parse(tts, parsing_patterns, sess, node_id, features, edition); // Get the Kleene operator and optional separator let (separator, kleene) = - parse_sep_and_kleene_op(&mut trees, span.entire(), sess); + parse_sep_and_kleene_op(&mut trees, delim_span.entire(), sess); // Count the number of captured "names" (i.e., named metavars) let name_captures = macro_parser::count_names(&sequence); TokenTree::Sequence( - span, + delim_span, Lrc::new(SequenceRepetition { tts: sequence, separator, @@ -197,7 +237,20 @@ fn parse_tree( } } - // `tree` is followed by a random token. This is an error. + // `tree` is followed by another `$`. This is an escaped `$`. + Some(tokenstream::TokenTree::Token(Token { kind: token::Dollar, span })) => { + if parsing_patterns { + span_dollar_dollar_or_metavar_in_the_lhs_err( + sess, + &Token { kind: token::Dollar, span }, + ); + } else { + maybe_emit_macro_metavar_expr_feature(features, sess, span); + } + TokenTree::token(token::Dollar, span) + } + + // `tree` is followed by some other token. This is an error. Some(tokenstream::TokenTree::Token(token)) => { let msg = format!( "expected identifier, found `{}`", @@ -221,7 +274,7 @@ fn parse_tree( span, Lrc::new(Delimited { delim, - tts: parse(tts, expect_matchers, sess, node_id, features, edition), + tts: parse(tts, parsing_patterns, sess, node_id, features, edition), }), ), } @@ -309,3 +362,15 @@ fn parse_sep_and_kleene_op( // Return a dummy (None, KleeneToken::new(KleeneOp::ZeroOrMore, span)) } + +// `$$` or a meta-variable is the lhs of a macro but shouldn't. +// +// For example, `macro_rules! foo { ( ${length()} ) => {} }` +fn span_dollar_dollar_or_metavar_in_the_lhs_err<'sess>(sess: &'sess ParseSess, token: &Token) { + sess.span_diagnostic + .span_err(token.span, &format!("unexpected token: {}", pprust::token_to_string(token))); + sess.span_diagnostic.span_note_without_error( + token.span, + "`$$` and meta-variable expressions are not allowed inside macro parameter definitions", + ); +} diff --git a/compiler/rustc_expand/src/mbe/transcribe.rs b/compiler/rustc_expand/src/mbe/transcribe.rs index 760dea77f9c2b..b8d8394754134 100644 --- a/compiler/rustc_expand/src/mbe/transcribe.rs +++ b/compiler/rustc_expand/src/mbe/transcribe.rs @@ -255,6 +255,11 @@ pub(super) fn transcribe<'a>( } } + // Replace meta-variable expressions with the result of their expansion. + mbe::TokenTree::MetaVarExpr(sp, expr) => { + transcribe_metavar_expr(cx, expr, interp, &repeats, &mut result, &sp)?; + } + // If we are entering a new delimiter, we push its contents to the `stack` to be // processed, and we push all of the currently produced results to the `result_stack`. // We will produce all of the results of the inside of the `Delimited` and then we will @@ -391,6 +396,28 @@ fn lockstep_iter_size( _ => LockstepIterSize::Unconstrained, } } + TokenTree::MetaVarExpr(_, ref expr) => { + let default_rslt = LockstepIterSize::Unconstrained; + let Some(ident) = expr.ident() else { return default_rslt; }; + let name = MacroRulesNormalizedIdent::new(ident.clone()); + match lookup_cur_matched(name, interpolations, repeats) { + Some(MatchedSeq(ref ads)) => { + default_rslt.with(LockstepIterSize::Constraint(ads.len(), name)) + } + _ => default_rslt, + } + } TokenTree::Token(..) => LockstepIterSize::Unconstrained, } } + +fn transcribe_metavar_expr<'a>( + _cx: &ExtCtxt<'a>, + _expr: mbe::MetaVarExpr, + _interp: &FxHashMap, + _repeats: &[(usize, usize)], + _result: &mut Vec, + _sp: &DelimSpan, +) -> PResult<'a, ()> { + Ok(()) +} diff --git a/compiler/rustc_feature/src/active.rs b/compiler/rustc_feature/src/active.rs index a69d28b184aed..a91f05587b277 100644 --- a/compiler/rustc_feature/src/active.rs +++ b/compiler/rustc_feature/src/active.rs @@ -426,6 +426,8 @@ declare_features! ( (active, link_cfg, "1.14.0", Some(37406), None), /// Allows using `reason` in lint attributes and the `#[expect(lint)]` lint check. (active, lint_reasons, "1.31.0", Some(54503), None), + /// Give access to additional metadata about declarative macro meta-variables. + (active, macro_metavar_expr, "1.61.0", Some(83527), None), /// Allows `#[marker]` on certain traits allowing overlapping implementations. (active, marker_trait_attr, "1.30.0", Some(29864), None), /// A minimal, sound subset of specialization intended to be used by the diff --git a/compiler/rustc_span/src/symbol.rs b/compiler/rustc_span/src/symbol.rs index 3f44292e03425..0d87233d9d79d 100644 --- a/compiler/rustc_span/src/symbol.rs +++ b/compiler/rustc_span/src/symbol.rs @@ -845,6 +845,7 @@ symbols! { macro_export, macro_lifetime_matcher, macro_literal_matcher, + macro_metavar_expr, macro_reexport, macro_use, macro_vis_matcher, diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/dollar-dollar-has-correct-behavior.rs b/src/test/ui/macros/rfc-3086-metavar-expr/dollar-dollar-has-correct-behavior.rs new file mode 100644 index 0000000000000..ed94c27cf05ca --- /dev/null +++ b/src/test/ui/macros/rfc-3086-metavar-expr/dollar-dollar-has-correct-behavior.rs @@ -0,0 +1,28 @@ +// run-pass + +#![feature(macro_metavar_expr)] + +macro_rules! nested { + ( $a:ident ) => { + macro_rules! $a { + ( $$( $b:ident ),* ) => { + $$( + macro_rules! $b { + ( $$$$( $c:ident ),* ) => { + $$$$( + fn $c() -> &'static str { stringify!($c) } + ),* + }; + } + )* + }; + } + }; +} + +fn main() { + nested!(a); + a!(b); + b!(c); + assert_eq!(c(), "c"); +} diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/feature-gate-macro_metavar_expr.rs b/src/test/ui/macros/rfc-3086-metavar-expr/feature-gate-macro_metavar_expr.rs new file mode 100644 index 0000000000000..6434ecc7e092d --- /dev/null +++ b/src/test/ui/macros/rfc-3086-metavar-expr/feature-gate-macro_metavar_expr.rs @@ -0,0 +1,14 @@ +// run-pass + +#![feature(macro_metavar_expr)] + +macro_rules! ignore { + ( $( $i:ident ),* ) => {{ + let array: [i32; 0] = [$( ${ignore(i)} )*]; + array + }}; +} + +fn main() { + assert_eq!(ignore!(a, b, c), []); +} diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.rs b/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.rs new file mode 100644 index 0000000000000..cff6f29a15386 --- /dev/null +++ b/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.rs @@ -0,0 +1,9 @@ +macro_rules! count { + ( $( $e:stmt ),* ) => { + ${ count(e) } + //~^ ERROR meta-variable expressions are unstable + }; +} + +fn main() { +} diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.stderr b/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.stderr new file mode 100644 index 0000000000000..f573194479314 --- /dev/null +++ b/src/test/ui/macros/rfc-3086-metavar-expr/required-feature.stderr @@ -0,0 +1,12 @@ +error[E0658]: meta-variable expressions are unstable + --> $DIR/required-feature.rs:3:10 + | +LL | ${ count(e) } + | ^^^^^^^^^^^^ + | + = note: see issue #83527 for more information + = help: add `#![feature(macro_metavar_expr)]` to the crate attributes to enable + +error: aborting due to previous error + +For more information about this error, try `rustc --explain E0658`. diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs b/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs new file mode 100644 index 0000000000000..ea73fd0813c5b --- /dev/null +++ b/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.rs @@ -0,0 +1,148 @@ +#![feature(macro_metavar_expr)] + +// `curly` = Right hand side curly brackets +// `no_rhs_dollar` = No dollar sign at the right hand side meta variable "function" +// `round` = Left hand side round brackets + +macro_rules! curly__no_rhs_dollar__round { + ( $( $i:ident ),* ) => { ${ count(i) } }; +} + +macro_rules! curly__no_rhs_dollar__no_round { + ( $i:ident ) => { ${ count(i) } }; +} + +macro_rules! curly__rhs_dollar__round { + ( $( $i:ident ),* ) => { ${ count($i) } }; + //~^ ERROR expected identifier, found `$` + //~| ERROR expected expression, found `$` +} + +macro_rules! curly__rhs_dollar__no_round { + ( $i:ident ) => { ${ count($i) } }; + //~^ ERROR expected identifier, found `$` + //~| ERROR expected expression, found `$` +} + +macro_rules! no_curly__no_rhs_dollar__round { + ( $( $i:ident ),* ) => { count(i) }; + //~^ ERROR cannot find function `count` in this scope + //~| ERROR cannot find value `i` in this scope +} + +macro_rules! no_curly__no_rhs_dollar__no_round { + ( $i:ident ) => { count(i) }; + //~^ ERROR cannot find function `count` in this scope + //~| ERROR cannot find value `i` in this scope +} + +macro_rules! no_curly__rhs_dollar__round { + ( $( $i:ident ),* ) => { count($i) }; + //~^ ERROR variable 'i' is still repeating at this depth +} + +macro_rules! no_curly__rhs_dollar__no_round { + ( $i:ident ) => { count($i) }; + //~^ ERROR cannot find function `count` in this scope +} + +// Other scenarios + +macro_rules! dollar_dollar_in_the_lhs { + ( $$ $a:ident ) => { + //~^ ERROR unexpected token: $ + }; +} + +macro_rules! extra_garbage_after_metavar { + ( $( $i:ident ),* ) => { + ${count() a b c} + //~^ ERROR unexpected token: a + //~| ERROR expected expression, found `$` + ${count(i a b c)} + //~^ ERROR unexpected token: a + ${count(i, 1 a b c)} + //~^ ERROR unexpected token: a + ${count(i) a b c} + //~^ ERROR unexpected token: a + + ${ignore(i) a b c} + //~^ ERROR unexpected token: a + ${ignore(i a b c)} + //~^ ERROR unexpected token: a + + ${index() a b c} + //~^ ERROR unexpected token: a + ${index(1 a b c)} + //~^ ERROR unexpected token: a + + ${index() a b c} + //~^ ERROR unexpected token: a + ${index(1 a b c)} + //~^ ERROR unexpected token: a + }; +} + +const IDX: usize = 1; +macro_rules! metavar_depth_is_not_literal { + ( $( $i:ident ),* ) => { ${ index(IDX) } }; + //~^ ERROR meta-variable expression depth must be a literal + //~| ERROR expected expression, found `$` +} + +macro_rules! metavar_in_the_lhs { + ( ${ length() } ) => { + //~^ ERROR unexpected token: { + //~| ERROR expected one of: `*`, `+`, or `?` + }; +} + +macro_rules! metavar_token_without_ident { + ( $( $i:ident ),* ) => { ${ ignore() } }; + //~^ ERROR expected identifier + //~| ERROR expected expression, found `$` +} + +macro_rules! metavar_with_literal_suffix { + ( $( $i:ident ),* ) => { ${ index(1u32) } }; + //~^ ERROR only unsuffixes integer literals are supported in meta-variable expressions + //~| ERROR expected expression, found `$` +} + +macro_rules! metavar_without_parens { + ( $( $i:ident ),* ) => { ${ count{i} } }; + //~^ ERROR meta-variable expression parameter must be wrapped in parentheses + //~| ERROR expected expression, found `$` +} + +macro_rules! open_brackets_without_tokens { + ( $( $i:ident ),* ) => { ${ {} } }; + //~^ ERROR expected expression, found `$` + //~| ERROR expected identifier +} + +macro_rules! unknown_metavar { + ( $( $i:ident ),* ) => { ${ aaaaaaaaaaaaaa(i) } }; + //~^ ERROR unrecognized meta-variable expression + //~| ERROR expected expression +} + +fn main() { + curly__no_rhs_dollar__round!(a, b, c); + curly__no_rhs_dollar__no_round!(a); + curly__rhs_dollar__round!(a, b, c); + curly__rhs_dollar__no_round!(a); + no_curly__no_rhs_dollar__round!(a, b, c); + no_curly__no_rhs_dollar__no_round!(a); + no_curly__rhs_dollar__round!(a, b, c); + no_curly__rhs_dollar__no_round!(a); + //~^ ERROR cannot find value `a` in this scope + + extra_garbage_after_metavar!(a); + unknown_metavar!(a); + metavar_without_parens!(a); + metavar_token_without_ident!(a); + metavar_depth_is_not_literal!(a); + metavar_with_literal_suffix!(a); + open_brackets_without_tokens!(a) +} diff --git a/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr b/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr new file mode 100644 index 0000000000000..dc8b7a668c4ee --- /dev/null +++ b/src/test/ui/macros/rfc-3086-metavar-expr/syntax-errors.stderr @@ -0,0 +1,367 @@ +error: expected identifier, found `$` + --> $DIR/syntax-errors.rs:16:33 + | +LL | ( $( $i:ident ),* ) => { ${ count($i) } }; + | ^^^^^ - help: try removing `$` + +error: expected identifier, found `$` + --> $DIR/syntax-errors.rs:22:26 + | +LL | ( $i:ident ) => { ${ count($i) } }; + | ^^^^^ - help: try removing `$` + +error: unexpected token: $ + --> $DIR/syntax-errors.rs:52:8 + | +LL | ( $$ $a:ident ) => { + | ^ + +note: `$$` and meta-variable expressions are not allowed inside macro parameter definitions + --> $DIR/syntax-errors.rs:52:8 + | +LL | ( $$ $a:ident ) => { + | ^ + +error: unexpected token: a + --> $DIR/syntax-errors.rs:59:19 + | +LL | ${count() a b c} + | ^ + | +note: meta-variable expression must not have trailing tokens + --> $DIR/syntax-errors.rs:59:19 + | +LL | ${count() a b c} + | ^ + +error: unexpected token: a + --> $DIR/syntax-errors.rs:62:19 + | +LL | ${count(i a b c)} + | ^ + | +note: meta-variable expression must not have trailing tokens + --> $DIR/syntax-errors.rs:62:19 + | +LL | ${count(i a b c)} + | ^ + +error: unexpected token: a + --> $DIR/syntax-errors.rs:64:22 + | +LL | ${count(i, 1 a b c)} + | ^ + | +note: meta-variable expression must not have trailing tokens + --> $DIR/syntax-errors.rs:64:22 + | +LL | ${count(i, 1 a b c)} + | ^ + +error: unexpected token: a + --> $DIR/syntax-errors.rs:66:20 + | +LL | ${count(i) a b c} + | ^ + | +note: meta-variable expression must not have trailing tokens + --> $DIR/syntax-errors.rs:66:20 + | +LL | ${count(i) a b c} + | ^ + +error: unexpected token: a + --> $DIR/syntax-errors.rs:69:21 + | +LL | ${ignore(i) a b c} + | ^ + | +note: meta-variable expression must not have trailing tokens + --> $DIR/syntax-errors.rs:69:21 + | +LL | ${ignore(i) a b c} + | ^ + +error: unexpected token: a + --> $DIR/syntax-errors.rs:71:20 + | +LL | ${ignore(i a b c)} + | ^ + | +note: meta-variable expression must not have trailing tokens + --> $DIR/syntax-errors.rs:71:20 + | +LL | ${ignore(i a b c)} + | ^ + +error: unexpected token: a + --> $DIR/syntax-errors.rs:74:19 + | +LL | ${index() a b c} + | ^ + | +note: meta-variable expression must not have trailing tokens + --> $DIR/syntax-errors.rs:74:19 + | +LL | ${index() a b c} + | ^ + +error: unexpected token: a + --> $DIR/syntax-errors.rs:76:19 + | +LL | ${index(1 a b c)} + | ^ + | +note: meta-variable expression must not have trailing tokens + --> $DIR/syntax-errors.rs:76:19 + | +LL | ${index(1 a b c)} + | ^ + +error: unexpected token: a + --> $DIR/syntax-errors.rs:79:19 + | +LL | ${index() a b c} + | ^ + | +note: meta-variable expression must not have trailing tokens + --> $DIR/syntax-errors.rs:79:19 + | +LL | ${index() a b c} + | ^ + +error: unexpected token: a + --> $DIR/syntax-errors.rs:81:19 + | +LL | ${index(1 a b c)} + | ^ + | +note: meta-variable expression must not have trailing tokens + --> $DIR/syntax-errors.rs:81:19 + | +LL | ${index(1 a b c)} + | ^ + +error: meta-variable expression depth must be a literal + --> $DIR/syntax-errors.rs:88:33 + | +LL | ( $( $i:ident ),* ) => { ${ index(IDX) } }; + | ^^^^^ + +error: unexpected token: { + --> $DIR/syntax-errors.rs:94:8 + | +LL | ( ${ length() } ) => { + | ^^^^^^^^^^^^ + +note: `$$` and meta-variable expressions are not allowed inside macro parameter definitions + --> $DIR/syntax-errors.rs:94:8 + | +LL | ( ${ length() } ) => { + | ^^^^^^^^^^^^ + +error: expected one of: `*`, `+`, or `?` + --> $DIR/syntax-errors.rs:94:8 + | +LL | ( ${ length() } ) => { + | ^^^^^^^^^^^^ + +error: expected identifier + --> $DIR/syntax-errors.rs:101:33 + | +LL | ( $( $i:ident ),* ) => { ${ ignore() } }; + | ^^^^^^ + +error: only unsuffixes integer literals are supported in meta-variable expressions + --> $DIR/syntax-errors.rs:107:33 + | +LL | ( $( $i:ident ),* ) => { ${ index(1u32) } }; + | ^^^^^ + +error: meta-variable expression parameter must be wrapped in parentheses + --> $DIR/syntax-errors.rs:113:33 + | +LL | ( $( $i:ident ),* ) => { ${ count{i} } }; + | ^^^^^ + +error: expected identifier + --> $DIR/syntax-errors.rs:119:31 + | +LL | ( $( $i:ident ),* ) => { ${ {} } }; + | ^^^^^^ + +error: unrecognized meta-variable expression + --> $DIR/syntax-errors.rs:125:33 + | +LL | ( $( $i:ident ),* ) => { ${ aaaaaaaaaaaaaa(i) } }; + | ^^^^^^^^^^^^^^ help: supported expressions are count, ignore, index and length + +error: expected expression, found `$` + --> $DIR/syntax-errors.rs:16:30 + | +LL | ( $( $i:ident ),* ) => { ${ count($i) } }; + | ^ expected expression +... +LL | curly__rhs_dollar__round!(a, b, c); + | ---------------------------------- in this macro invocation + | + = note: this error originates in the macro `curly__rhs_dollar__round` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected expression, found `$` + --> $DIR/syntax-errors.rs:22:23 + | +LL | ( $i:ident ) => { ${ count($i) } }; + | ^ expected expression +... +LL | curly__rhs_dollar__no_round!(a); + | ------------------------------- in this macro invocation + | + = note: this error originates in the macro `curly__rhs_dollar__no_round` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: variable 'i' is still repeating at this depth + --> $DIR/syntax-errors.rs:40:36 + | +LL | ( $( $i:ident ),* ) => { count($i) }; + | ^^ + +error: expected expression, found `$` + --> $DIR/syntax-errors.rs:59:9 + | +LL | ${count() a b c} + | ^ expected expression +... +LL | extra_garbage_after_metavar!(a); + | ------------------------------- in this macro invocation + | + = note: this error originates in the macro `extra_garbage_after_metavar` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected expression, found `$` + --> $DIR/syntax-errors.rs:125:30 + | +LL | ( $( $i:ident ),* ) => { ${ aaaaaaaaaaaaaa(i) } }; + | ^ expected expression +... +LL | unknown_metavar!(a); + | ------------------- in this macro invocation + | + = note: this error originates in the macro `unknown_metavar` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected expression, found `$` + --> $DIR/syntax-errors.rs:113:30 + | +LL | ( $( $i:ident ),* ) => { ${ count{i} } }; + | ^ expected expression +... +LL | metavar_without_parens!(a); + | -------------------------- in this macro invocation + | + = note: this error originates in the macro `metavar_without_parens` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected expression, found `$` + --> $DIR/syntax-errors.rs:101:30 + | +LL | ( $( $i:ident ),* ) => { ${ ignore() } }; + | ^ expected expression +... +LL | metavar_token_without_ident!(a); + | ------------------------------- in this macro invocation + | + = note: this error originates in the macro `metavar_token_without_ident` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected expression, found `$` + --> $DIR/syntax-errors.rs:88:30 + | +LL | ( $( $i:ident ),* ) => { ${ index(IDX) } }; + | ^ expected expression +... +LL | metavar_depth_is_not_literal!(a); + | -------------------------------- in this macro invocation + | + = note: this error originates in the macro `metavar_depth_is_not_literal` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected expression, found `$` + --> $DIR/syntax-errors.rs:107:30 + | +LL | ( $( $i:ident ),* ) => { ${ index(1u32) } }; + | ^ expected expression +... +LL | metavar_with_literal_suffix!(a); + | ------------------------------- in this macro invocation + | + = note: this error originates in the macro `metavar_with_literal_suffix` (in Nightly builds, run with -Z macro-backtrace for more info) + +error: expected expression, found `$` + --> $DIR/syntax-errors.rs:119:30 + | +LL | ( $( $i:ident ),* ) => { ${ {} } }; + | ^ expected expression +... +LL | open_brackets_without_tokens!(a) + | -------------------------------- in this macro invocation + | + = note: this error originates in the macro `open_brackets_without_tokens` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0425]: cannot find function `count` in this scope + --> $DIR/syntax-errors.rs:28:30 + | +LL | ( $( $i:ident ),* ) => { count(i) }; + | ^^^^^ not found in this scope +... +LL | no_curly__no_rhs_dollar__round!(a, b, c); + | ---------------------------------------- in this macro invocation + | + = note: this error originates in the macro `no_curly__no_rhs_dollar__round` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0425]: cannot find value `i` in this scope + --> $DIR/syntax-errors.rs:28:36 + | +LL | ( $( $i:ident ),* ) => { count(i) }; + | ^ not found in this scope +... +LL | no_curly__no_rhs_dollar__round!(a, b, c); + | ---------------------------------------- in this macro invocation + | + = note: this error originates in the macro `no_curly__no_rhs_dollar__round` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0425]: cannot find function `count` in this scope + --> $DIR/syntax-errors.rs:34:23 + | +LL | ( $i:ident ) => { count(i) }; + | ^^^^^ not found in this scope +... +LL | no_curly__no_rhs_dollar__no_round!(a); + | ------------------------------------- in this macro invocation + | + = note: this error originates in the macro `no_curly__no_rhs_dollar__no_round` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0425]: cannot find value `i` in this scope + --> $DIR/syntax-errors.rs:34:29 + | +LL | ( $i:ident ) => { count(i) }; + | ^ not found in this scope +... +LL | no_curly__no_rhs_dollar__no_round!(a); + | ------------------------------------- in this macro invocation + | + = note: this error originates in the macro `no_curly__no_rhs_dollar__no_round` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0425]: cannot find function `count` in this scope + --> $DIR/syntax-errors.rs:45:23 + | +LL | ( $i:ident ) => { count($i) }; + | ^^^^^ not found in this scope +... +LL | no_curly__rhs_dollar__no_round!(a); + | ---------------------------------- in this macro invocation + | + = note: this error originates in the macro `no_curly__rhs_dollar__no_round` (in Nightly builds, run with -Z macro-backtrace for more info) + +error[E0425]: cannot find value `a` in this scope + --> $DIR/syntax-errors.rs:138:37 + | +LL | no_curly__rhs_dollar__no_round!(a); + | ^ not found in this scope + +error: aborting due to 37 previous errors + +For more information about this error, try `rustc --explain E0425`. From 109cdc754ed893edb25d2d2c1493023858c8eccb Mon Sep 17 00:00:00 2001 From: Michael Goulet Date: Thu, 3 Mar 2022 21:46:45 -0800 Subject: [PATCH 11/13] suggest enabling generic_const_exprs feature if const is unevaluatable --- compiler/rustc_trait_selection/src/lib.rs | 1 + .../src/traits/const_evaluatable.rs | 88 +++++++++++++------ .../auxiliary/issue-94287-aux.rs | 21 +++++ .../generic_const_exprs/issue-94287.rs | 10 +++ .../generic_const_exprs/issue-94287.stderr | 14 +++ 5 files changed, 109 insertions(+), 25 deletions(-) create mode 100644 src/test/ui/const-generics/generic_const_exprs/auxiliary/issue-94287-aux.rs create mode 100644 src/test/ui/const-generics/generic_const_exprs/issue-94287.rs create mode 100644 src/test/ui/const-generics/generic_const_exprs/issue-94287.stderr diff --git a/compiler/rustc_trait_selection/src/lib.rs b/compiler/rustc_trait_selection/src/lib.rs index 5569334ff3d20..0403c611d0b3f 100644 --- a/compiler/rustc_trait_selection/src/lib.rs +++ b/compiler/rustc_trait_selection/src/lib.rs @@ -17,6 +17,7 @@ #![feature(derive_default_enum)] #![feature(hash_drain_filter)] #![feature(label_break_value)] +#![feature(let_chains)] #![feature(let_else)] #![feature(never_type)] #![feature(crate_visibility_modifier)] diff --git a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs index 1994faed70c66..a3121a4da5047 100644 --- a/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs +++ b/compiler/rustc_trait_selection/src/traits/const_evaluatable.rs @@ -35,34 +35,14 @@ pub fn is_const_evaluatable<'cx, 'tcx>( span: Span, ) -> Result<(), NotConstEvaluatable> { debug!("is_const_evaluatable({:?})", uv); - if infcx.tcx.features().generic_const_exprs { - let tcx = infcx.tcx; + let tcx = infcx.tcx; + + if tcx.features().generic_const_exprs { match AbstractConst::new(tcx, uv)? { // We are looking at a generic abstract constant. Some(ct) => { - for pred in param_env.caller_bounds() { - match pred.kind().skip_binder() { - ty::PredicateKind::ConstEvaluatable(uv) => { - if let Some(b_ct) = AbstractConst::new(tcx, uv)? { - // Try to unify with each subtree in the AbstractConst to allow for - // `N + 1` being const evaluatable even if theres only a `ConstEvaluatable` - // predicate for `(N + 1) * 2` - let result = - walk_abstract_const(tcx, b_ct, |b_ct| { - match try_unify(tcx, ct, b_ct) { - true => ControlFlow::BREAK, - false => ControlFlow::CONTINUE, - } - }); - - if let ControlFlow::Break(()) = result { - debug!("is_const_evaluatable: abstract_const ~~> ok"); - return Ok(()); - } - } - } - _ => {} // don't care - } + if satisfied_from_param_env(tcx, ct, param_env)? { + return Ok(()); } // We were unable to unify the abstract constant with @@ -163,6 +143,33 @@ pub fn is_const_evaluatable<'cx, 'tcx>( } } + // If we're evaluating a foreign constant, under a nightly compiler without generic + // const exprs, AND it would've passed if that expression had been evaluated with + // generic const exprs, then suggest using generic const exprs. + if concrete.is_err() + && tcx.sess.is_nightly_build() + && !uv.def.did.is_local() + && !tcx.features().generic_const_exprs + && let Ok(Some(ct)) = AbstractConst::new(tcx, uv) + && satisfied_from_param_env(tcx, ct, param_env) == Ok(true) + { + tcx.sess + .struct_span_fatal( + // Slightly better span than just using `span` alone + if span == rustc_span::DUMMY_SP { tcx.def_span(uv.def.did) } else { span }, + "failed to evaluate generic const expression", + ) + .note("the crate this constant originates from uses `#![feature(generic_const_exprs)]`") + .span_suggestion_verbose( + rustc_span::DUMMY_SP, + "consider enabling this feature", + "#![feature(generic_const_exprs)]\n".to_string(), + rustc_errors::Applicability::MaybeIncorrect, + ) + .emit(); + rustc_errors::FatalError.raise(); + } + debug!(?concrete, "is_const_evaluatable"); match concrete { Err(ErrorHandled::TooGeneric) => Err(match uv.has_infer_types_or_consts() { @@ -178,6 +185,37 @@ pub fn is_const_evaluatable<'cx, 'tcx>( } } +fn satisfied_from_param_env<'tcx>( + tcx: TyCtxt<'tcx>, + ct: AbstractConst<'tcx>, + param_env: ty::ParamEnv<'tcx>, +) -> Result { + for pred in param_env.caller_bounds() { + match pred.kind().skip_binder() { + ty::PredicateKind::ConstEvaluatable(uv) => { + if let Some(b_ct) = AbstractConst::new(tcx, uv)? { + // Try to unify with each subtree in the AbstractConst to allow for + // `N + 1` being const evaluatable even if theres only a `ConstEvaluatable` + // predicate for `(N + 1) * 2` + let result = + walk_abstract_const(tcx, b_ct, |b_ct| match try_unify(tcx, ct, b_ct) { + true => ControlFlow::BREAK, + false => ControlFlow::CONTINUE, + }); + + if let ControlFlow::Break(()) = result { + debug!("is_const_evaluatable: abstract_const ~~> ok"); + return Ok(true); + } + } + } + _ => {} // don't care + } + } + + Ok(false) +} + /// A tree representing an anonymous constant. /// /// This is only able to represent a subset of `MIR`, diff --git a/src/test/ui/const-generics/generic_const_exprs/auxiliary/issue-94287-aux.rs b/src/test/ui/const-generics/generic_const_exprs/auxiliary/issue-94287-aux.rs new file mode 100644 index 0000000000000..df454dae7250f --- /dev/null +++ b/src/test/ui/const-generics/generic_const_exprs/auxiliary/issue-94287-aux.rs @@ -0,0 +1,21 @@ +#![feature(generic_const_exprs)] + +use std::str::FromStr; + +pub struct If; + +pub trait True {} + +impl True for If {} + +pub struct FixedI32; + +impl FromStr for FixedI32 +where + If<{ FRAC <= 32 }>: True, +{ + type Err = (); + fn from_str(_s: &str) -> Result { + unimplemented!() + } +} diff --git a/src/test/ui/const-generics/generic_const_exprs/issue-94287.rs b/src/test/ui/const-generics/generic_const_exprs/issue-94287.rs new file mode 100644 index 0000000000000..643126a4640a8 --- /dev/null +++ b/src/test/ui/const-generics/generic_const_exprs/issue-94287.rs @@ -0,0 +1,10 @@ +// aux-build:issue-94287-aux.rs +// build-fail + +extern crate issue_94287_aux; + +use std::str::FromStr; + +fn main() { + let _ = >::from_str(""); +} diff --git a/src/test/ui/const-generics/generic_const_exprs/issue-94287.stderr b/src/test/ui/const-generics/generic_const_exprs/issue-94287.stderr new file mode 100644 index 0000000000000..c918651ba62d9 --- /dev/null +++ b/src/test/ui/const-generics/generic_const_exprs/issue-94287.stderr @@ -0,0 +1,14 @@ +error: failed to evaluate generic const expression + --> $DIR/auxiliary/issue-94287-aux.rs:15:8 + | +LL | If<{ FRAC <= 32 }>: True, + | ^^^^^^^^^^^^^^ + | + = note: the crate this constant originates from uses `#![feature(generic_const_exprs)]` +help: consider enabling this feature + | +LL | #![feature(generic_const_exprs)] + | + +error: aborting due to previous error + From 9d857d95ba08764da67d9088906356f91d3b3e80 Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Wed, 2 Feb 2022 22:48:09 +0000 Subject: [PATCH 12/13] Make llvm-libunwind a per-target option --- config.toml.example | 25 +++++++++++++++---------- src/bootstrap/compile.rs | 2 +- src/bootstrap/config.rs | 23 ++++++++++++++++++----- src/bootstrap/lib.rs | 2 +- 4 files changed, 35 insertions(+), 17 deletions(-) diff --git a/config.toml.example b/config.toml.example index f24f8e81a7944..bd28c9d85cb9f 100644 --- a/config.toml.example +++ b/config.toml.example @@ -594,16 +594,9 @@ changelog-seen = 2 # development of NLL #test-compare-mode = false -# Use LLVM libunwind as the implementation for Rust's unwinder. -# Accepted values are 'in-tree' (formerly true), 'system' or 'no' (formerly false). -# This option only applies for Linux and Fuchsia targets. -# On Linux target, if crt-static is not enabled, 'no' means dynamic link to -# `libgcc_s.so`, 'in-tree' means static link to the in-tree build of llvm libunwind -# and 'system' means dynamic link to `libunwind.so`. If crt-static is enabled, -# the behavior is depend on the libc. On musl target, 'no' and 'in-tree' both -# means static link to the in-tree build of llvm libunwind, and 'system' means -# static link to `libunwind.a` provided by system. Due to the limitation of glibc, -# it must link to `libgcc_eh.a` to get a working output, and this option have no effect. +# Global default for llvm-libunwind for all targets. See the target-specific +# documentation for llvm-libunwind below. Note that the target-specific +# option will override this if set. #llvm-libunwind = 'no' # Enable Windows Control Flow Guard checks in the standard library. @@ -660,6 +653,18 @@ changelog-seen = 2 # not, you can specify an explicit file name for it. #llvm-filecheck = "/path/to/llvm-version/bin/FileCheck" +# Use LLVM libunwind as the implementation for Rust's unwinder. +# Accepted values are 'in-tree' (formerly true), 'system' or 'no' (formerly false). +# This option only applies for Linux and Fuchsia targets. +# On Linux target, if crt-static is not enabled, 'no' means dynamic link to +# `libgcc_s.so`, 'in-tree' means static link to the in-tree build of llvm libunwind +# and 'system' means dynamic link to `libunwind.so`. If crt-static is enabled, +# the behavior is depend on the libc. On musl target, 'no' and 'in-tree' both +# means static link to the in-tree build of llvm libunwind, and 'system' means +# static link to `libunwind.a` provided by system. Due to the limitation of glibc, +# it must link to `libgcc_eh.a` to get a working output, and this option have no effect. +#llvm-libunwind = 'no' if Linux, 'in-tree' if Fuchsia + # If this target is for Android, this option will be required to specify where # the NDK for the target lives. This is used to find the C compiler to link and # build native code. diff --git a/src/bootstrap/compile.rs b/src/bootstrap/compile.rs index e17de0ba49ebc..f794e04ad39e4 100644 --- a/src/bootstrap/compile.rs +++ b/src/bootstrap/compile.rs @@ -177,7 +177,7 @@ fn copy_third_party_objects( } if target == "x86_64-fortanix-unknown-sgx" - || builder.config.llvm_libunwind == LlvmLibunwind::InTree + || builder.config.llvm_libunwind(target) == LlvmLibunwind::InTree && (target.contains("linux") || target.contains("fuchsia")) { let libunwind_path = diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index 683cfc630e771..be943002d231b 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -68,7 +68,6 @@ pub struct Config { pub rustc_error_format: Option, pub json_output: bool, pub test_compare_mode: bool, - pub llvm_libunwind: LlvmLibunwind, pub color: Color, pub on_fail: Option, @@ -146,6 +145,7 @@ pub struct Config { pub rust_profile_generate: Option, pub llvm_profile_use: Option, pub llvm_profile_generate: bool, + pub llvm_libunwind_default: Option, pub build: TargetSelection, pub hosts: Vec, @@ -290,6 +290,7 @@ pub struct Target { pub llvm_config: Option, /// Some(path to FileCheck) if one was specified. pub llvm_filecheck: Option, + pub llvm_libunwind: Option, pub cc: Option, pub cxx: Option, pub ar: Option, @@ -574,6 +575,7 @@ derive_merge! { linker: Option, llvm_config: Option, llvm_filecheck: Option, + llvm_libunwind: Option, android_ndk: Option, sanitizers: Option, profiler: Option, @@ -921,10 +923,6 @@ impl Config { set(&mut config.rust_rpath, rust.rpath); set(&mut config.jemalloc, rust.jemalloc); set(&mut config.test_compare_mode, rust.test_compare_mode); - config.llvm_libunwind = rust - .llvm_libunwind - .map(|v| v.parse().expect("failed to parse rust.llvm-libunwind")) - .unwrap_or_default(); set(&mut config.backtrace, rust.backtrace); set(&mut config.channel, rust.channel); config.description = rust.description; @@ -947,6 +945,9 @@ impl Config { config.rust_thin_lto_import_instr_limit = rust.thin_lto_import_instr_limit; set(&mut config.rust_remap_debuginfo, rust.remap_debuginfo); set(&mut config.control_flow_guard, rust.control_flow_guard); + config.llvm_libunwind_default = rust + .llvm_libunwind + .map(|v| v.parse().expect("failed to parse rust.llvm-libunwind")); if let Some(ref backends) = rust.codegen_backends { config.rust_codegen_backends = @@ -973,6 +974,10 @@ impl Config { if let Some(ref s) = cfg.llvm_filecheck { target.llvm_filecheck = Some(config.src.join(s)); } + target.llvm_libunwind = cfg + .llvm_libunwind + .as_ref() + .map(|v| v.parse().expect("failed to parse rust.llvm-libunwind")); if let Some(ref s) = cfg.android_ndk { target.ndk = Some(config.src.join(s)); } @@ -1170,6 +1175,14 @@ impl Config { self.rust_codegen_backends.contains(&INTERNER.intern_str("llvm")) } + pub fn llvm_libunwind(&self, target: TargetSelection) -> LlvmLibunwind { + self.target_config + .get(&target) + .and_then(|t| t.llvm_libunwind) + .or(self.llvm_libunwind_default) + .unwrap_or(LlvmLibunwind::No) + } + pub fn submodules(&self, rust_info: &GitInfo) -> bool { self.submodules.unwrap_or(rust_info.is_git()) } diff --git a/src/bootstrap/lib.rs b/src/bootstrap/lib.rs index e5f84d417bf0b..857d74c6c913e 100644 --- a/src/bootstrap/lib.rs +++ b/src/bootstrap/lib.rs @@ -655,7 +655,7 @@ impl Build { fn std_features(&self, target: TargetSelection) -> String { let mut features = "panic-unwind".to_string(); - match self.config.llvm_libunwind { + match self.config.llvm_libunwind(target) { LlvmLibunwind::InTree => features.push_str(" llvm-libunwind"), LlvmLibunwind::System => features.push_str(" system-llvm-libunwind"), LlvmLibunwind::No => {} From 42624bad8a205ac1c97a328e050be9c63558ca33 Mon Sep 17 00:00:00 2001 From: Tyler Mandry Date: Thu, 10 Mar 2022 02:08:38 +0000 Subject: [PATCH 13/13] Use in-tree libunwind by default on Fuchsia --- src/bootstrap/config.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/bootstrap/config.rs b/src/bootstrap/config.rs index be943002d231b..99fb68489ea4c 100644 --- a/src/bootstrap/config.rs +++ b/src/bootstrap/config.rs @@ -1180,7 +1180,11 @@ impl Config { .get(&target) .and_then(|t| t.llvm_libunwind) .or(self.llvm_libunwind_default) - .unwrap_or(LlvmLibunwind::No) + .unwrap_or(if target.contains("fuchsia") { + LlvmLibunwind::InTree + } else { + LlvmLibunwind::No + }) } pub fn submodules(&self, rust_info: &GitInfo) -> bool {