Skip to content
Closed
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3509,6 +3509,7 @@ dependencies = [
name = "rustc_builtin_macros"
version = "0.0.0"
dependencies = [
"rustc-literal-escaper",
"rustc_ast",
"rustc_ast_pretty",
"rustc_attr_parsing",
Expand Down
1 change: 1 addition & 0 deletions compiler/rustc_builtin_macros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ doctest = false

[dependencies]
# tidy-alphabetical-start
rustc-literal-escaper = "0.0.5"
rustc_ast = { path = "../rustc_ast" }
rustc_ast_pretty = { path = "../rustc_ast_pretty" }
rustc_attr_parsing = { path = "../rustc_attr_parsing" }
Expand Down
108 changes: 89 additions & 19 deletions compiler/rustc_builtin_macros/src/edition_panic.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,35 @@
use rustc_ast::token::Delimiter;
use rustc_ast::tokenstream::{DelimSpan, TokenStream};
use rustc_ast::token::{Delimiter, Lit, LitKind, TokenKind};
use rustc_ast::tokenstream::{TokenStream, TokenTree};
use rustc_ast::*;
use rustc_expand::base::*;
use rustc_span::edition::Edition;
use rustc_span::{Span, sym};
use rustc_span::{Ident, Span, Symbol, sym};

// Use an enum to ensure that no new macro calls are added without also updating the message in the
// optimized path below.
enum InnerCall {
Panic2015,
Panic2021,
Unreachable2015,
Unreachable2021,
}

impl InnerCall {
fn symbol(&self) -> Symbol {
match self {
Self::Panic2015 => sym::panic_2015,
Self::Panic2021 => sym::panic_2021,
Self::Unreachable2015 => sym::unreachable_2015,
Self::Unreachable2021 => sym::unreachable_2021,
}
}
}

/// This expands to either
/// - `$crate::panic::panic_2015!(...)` or
/// - `$crate::panic::panic_2021!(...)`
/// depending on the edition.
/// depending on the edition. If the entire message is known at compile time,
/// `core::panicking::panic` may be called as an optimization.
///
/// This is used for both std::panic!() and core::panic!().
///
Expand All @@ -19,50 +40,99 @@ pub(crate) fn expand_panic<'cx>(
sp: Span,
tts: TokenStream,
) -> MacroExpanderResult<'cx> {
let mac = if use_panic_2021(sp) { sym::panic_2021 } else { sym::panic_2015 };
let mac = if use_panic_2021(sp) { InnerCall::Panic2021 } else { InnerCall::Panic2015 };
expand(mac, cx, sp, tts)
}

/// This expands to either
/// - `$crate::panic::unreachable_2015!(...)` or
/// - `$crate::panic::unreachable_2021!(...)`
/// depending on the edition.
/// depending on the edition. If the entire message is known at compile time,
/// `core::panicking::panic` may be called as an optimization.
pub(crate) fn expand_unreachable<'cx>(
cx: &'cx mut ExtCtxt<'_>,
sp: Span,
tts: TokenStream,
) -> MacroExpanderResult<'cx> {
let mac = if use_panic_2021(sp) { sym::unreachable_2021 } else { sym::unreachable_2015 };
let mac =
if use_panic_2021(sp) { InnerCall::Unreachable2021 } else { InnerCall::Unreachable2015 };
expand(mac, cx, sp, tts)
}

fn expand<'cx>(
mac: rustc_span::Symbol,
mac: InnerCall,
cx: &'cx ExtCtxt<'_>,
sp: Span,
tts: TokenStream,
) -> MacroExpanderResult<'cx> {
let sp = cx.with_call_site_ctxt(sp);

// If the call is of the form `panic!(<string literal>)` and there are no formatting arguments
// in the string literal, we can call `core::panicking::panic` to centralize the panic logic.
if tts.len() == 1
&& let Some(TokenTree::Token(token, _)) = tts.get(0)
&& let TokenKind::Literal(lit) = &token.kind
&& let Lit { kind: LitKind::Str | LitKind::StrRaw(_), symbol, .. } = lit
&& let msg = symbol.as_str()
&& !msg.contains(|c| c == '{' || c == '}')
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very much a hack. It would be much better to use rustc_parse_format here.

In particular since I think your version would not optimize panic!("foo {{ bar }}"); since it doesn't know about the escaping.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm aware. I tried to use that crate but couldn't figure out how to get it to work despite multiple approaches.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should work:

    if tts.len() == 1
        && let Some(TokenTree::Token(token, _)) = tts.get(0)
        && let TokenKind::Literal(lit) = &token.kind
        && let Lit { kind: litkind, symbol, .. } = lit
        && let Some(style) = match litkind {
            LitKind::Str => Some(None),
            LitKind::StrRaw(n) => Some(Some(*n as usize)),
            _ => None,
        }
        && let mut parser = rustc_parse_format::Parser::new(
            symbol.as_str(),
            style,
            None,
            true,
            rustc_parse_format::ParseMode::Format,
        )
        && let Some(rustc_parse_format::Piece::Lit(msg)) = parser.next()
        && let None = parser.next()
    {
        let msg = match mac {
            InnerCall::Panic2015 | InnerCall::Panic2021 => cx.expr(sp, ExprKind::Lit(*lit)),
            InnerCall::Unreachable2015 | InnerCall::Unreachable2021 => cx.expr_str(
                sp,
                Symbol::intern(&format!("internal error: entered unreachable code: {msg}")),
            ),
        };

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup, that works for the most part! Just one test for a lint failing that I'll look into. That gets part of the way towards what I'd love to have, which is even leveraging the optimizations of inlining literals and similar.

{
let msg = match mac {
InnerCall::Panic2015 | InnerCall::Panic2021 => cx.expr(sp, ExprKind::Lit(*lit)),
InnerCall::Unreachable2015 | InnerCall::Unreachable2021 => {
let msg = if msg.contains('\\') {
let mut buf = String::with_capacity(msg.len());
// Force-inlining here is aggressive but the closure is
// called on every char in the string, so it can be hot in
// programs with many long strings containing escapes.
rustc_literal_escaper::unescape_str(
msg,
#[inline(always)]
|_, res| match res {
Ok(c) => buf.push(c),
Err(err) => {
assert!(!err.is_fatal(), "failed to unescape string literal")
}
},
);
buf
} else {
msg.to_owned()
};

cx.expr_str(
sp,
Symbol::intern(&format!("internal error: entered unreachable code: {msg}")),
)
}
};

return ExpandResult::Ready(MacEager::expr(cx.expr_call(
sp,
cx.expr_path(cx.path_global(
sp,
[sym::core, sym::panicking, sym::panic].map(|sym| Ident::new(sym, sp)).to_vec(),
)),
[msg].into(),
)));
}

ExpandResult::Ready(MacEager::expr(
cx.expr(
cx.expr_macro_call(
sp,
ExprKind::MacCall(Box::new(MacCall {
path: Path {
cx.macro_call(
sp,
Path {
span: sp,
segments: cx
.std_path(&[sym::panic, mac])
.std_path(&[sym::panic, mac.symbol()])
.into_iter()
.map(|ident| PathSegment::from_ident(ident))
.map(PathSegment::from_ident)
.collect(),
tokens: None,
},
args: Box::new(DelimArgs {
dspan: DelimSpan::from_single(sp),
delim: Delimiter::Parenthesis,
tokens: tts,
}),
})),
Delimiter::Parenthesis,
tts,
),
),
))
}
Expand Down
7 changes: 4 additions & 3 deletions library/core/src/macros/mod.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
#[doc = include_str!("panic.md")]
#[macro_export]
#[rustc_builtin_macro(core_panic)]
#[allow_internal_unstable(edition_panic)]
#[allow_internal_unstable(edition_panic, panic_internals)]
#[stable(feature = "core", since = "1.6.0")]
#[rustc_diagnostic_item = "core_panic_macro"]
macro_rules! panic {
// Expands to either `$crate::panic::panic_2015` or `$crate::panic::panic_2021`
// depending on the edition of the caller.
// depending on the edition of the caller. If the entire message is known at compile time,
// `core::panicking::panic` may be called as an optimization.
($($arg:tt)*) => {
/* compiler built-in */
};
Expand Down Expand Up @@ -705,7 +706,7 @@ macro_rules! writeln {
/// ```
#[macro_export]
#[rustc_builtin_macro(unreachable)]
#[allow_internal_unstable(edition_panic)]
#[allow_internal_unstable(edition_panic, panic_internals)]
#[stable(feature = "rust1", since = "1.0.0")]
#[rustc_diagnostic_item = "unreachable_macro"]
macro_rules! unreachable {
Expand Down
5 changes: 3 additions & 2 deletions library/std/src/macros.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
#[macro_export]
#[rustc_builtin_macro(std_panic)]
#[stable(feature = "rust1", since = "1.0.0")]
#[allow_internal_unstable(edition_panic)]
#[allow_internal_unstable(edition_panic, panic_internals)]
#[cfg_attr(not(test), rustc_diagnostic_item = "std_panic_macro")]
macro_rules! panic {
// Expands to either `$crate::panic::panic_2015` or `$crate::panic::panic_2021`
// depending on the edition of the caller.
// depending on the edition of the caller. If the entire message is known at compile time,
// `core::panicking::panic` may be called as an optimization.
($($arg:tt)*) => {
/* compiler built-in */
};
Expand Down
24 changes: 16 additions & 8 deletions src/tools/clippy/clippy_lints/src/incompatible_msrv.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ pub struct IncompatibleMsrv {
msrv: Msrv,
availability_cache: FxHashMap<(DefId, bool), Availability>,
check_in_tests: bool,
core_crate: Option<CrateNum>,
stdlib_crates: Vec<CrateNum>,

// The most recently called path. Used to skip checking the path after it's
// been checked when visiting the call expression.
Expand All @@ -96,11 +96,15 @@ impl IncompatibleMsrv {
msrv: conf.msrv,
availability_cache: FxHashMap::default(),
check_in_tests: conf.check_incompatible_msrv_in_tests,
core_crate: tcx
stdlib_crates: tcx
.crates(())
.iter()
.find(|krate| tcx.crate_name(**krate) == sym::core)
.copied(),
.filter(|krate| {
let name = tcx.crate_name(**krate);
name == sym::core || name == sym::alloc || name == sym::std
})
.copied()
.collect(),
called_path: None,
}
}
Expand Down Expand Up @@ -162,10 +166,14 @@ impl IncompatibleMsrv {
// Intentionally not using `.from_expansion()`, since we do still care about macro expansions
return;
}
// Functions coming from `core` while expanding a macro such as `assert*!()` get to cheat too: the
// macros may have existed prior to the checked MSRV, but their expansion with a recent compiler
// might use recent functions or methods. Compiling with an older compiler would not use those.
if Some(def_id.krate) == self.core_crate && expn_data.macro_def_id.map(|did| did.krate) == self.core_crate {
// Functions coming from standard library crates while expanding a macro such as
// `assert*!()` get to cheat too: the macros may have existed prior to the checked MSRV, but
// their expansion with a recent compiler might use recent functions or methods. Compiling
// with an older compiler would not use those.
if self.stdlib_crates.contains(&def_id.krate)
&& let Some(did) = expn_data.macro_def_id
&& self.stdlib_crates.contains(&did.krate)
{
return;
}

Expand Down
8 changes: 5 additions & 3 deletions src/tools/miri/tests/panic/panic1.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,12 @@ panicking from libstd
stack backtrace:
0: std::panicking::panic_handler
at RUSTLIB/std/src/panicking.rs:LL:CC
1: std::rt::panic_fmt
1: core::panicking::panic_fmt
at RUSTLIB/core/src/panicking.rs:LL:CC
2: main
2: core::panicking::panic
at RUSTLIB/core/src/panicking.rs:LL:CC
3: main
at tests/panic/panic1.rs:LL:CC
3: <fn() as std::ops::FnOnce<()>>::call_once - shim(fn())
4: <fn() as std::ops::FnOnce<()>>::call_once - shim(fn())
at RUSTLIB/core/src/ops/function.rs:LL:CC
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@
struct Bug([u8; panic!{"\t"}]);
//~^ ERROR evaluation panicked
//~| NOTE: in this expansion of panic!
//~| NOTE: failed here
18 changes: 10 additions & 8 deletions tests/ui/panics/short-ice-remove-middle-frames-2.run.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
thread 'main' ($TID) panicked at $DIR/short-ice-remove-middle-frames-2.rs:62:5:
debug!!!
stack backtrace:
0: std::panicking::begin_panic
1: short_ice_remove_middle_frames_2::eight
2: short_ice_remove_middle_frames_2::seven::{{closure}}
0: __rustc::rust_begin_unwind
1: core::panicking::panic_fmt
2: core::panicking::panic
3: short_ice_remove_middle_frames_2::eight
4: short_ice_remove_middle_frames_2::seven::{{closure}}
[... omitted 3 frames ...]
3: short_ice_remove_middle_frames_2::fifth
4: short_ice_remove_middle_frames_2::fourth::{{closure}}
5: short_ice_remove_middle_frames_2::fifth
6: short_ice_remove_middle_frames_2::fourth::{{closure}}
[... omitted 4 frames ...]
5: short_ice_remove_middle_frames_2::first
6: short_ice_remove_middle_frames_2::main
7: core::ops::function::FnOnce::call_once
7: short_ice_remove_middle_frames_2::first
8: short_ice_remove_middle_frames_2::main
9: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
20 changes: 11 additions & 9 deletions tests/ui/panics/short-ice-remove-middle-frames.run.stderr
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,16 @@
thread 'main' ($TID) panicked at $DIR/short-ice-remove-middle-frames.rs:58:5:
debug!!!
stack backtrace:
0: std::panicking::begin_panic
1: short_ice_remove_middle_frames::seven
2: short_ice_remove_middle_frames::sixth
3: short_ice_remove_middle_frames::fifth::{{closure}}
0: __rustc::rust_begin_unwind
1: core::panicking::panic_fmt
2: core::panicking::panic
3: short_ice_remove_middle_frames::seven
4: short_ice_remove_middle_frames::sixth
5: short_ice_remove_middle_frames::fifth::{{closure}}
[... omitted 4 frames ...]
4: short_ice_remove_middle_frames::second
5: short_ice_remove_middle_frames::first::{{closure}}
6: short_ice_remove_middle_frames::first
7: short_ice_remove_middle_frames::main
8: core::ops::function::FnOnce::call_once
6: short_ice_remove_middle_frames::second
7: short_ice_remove_middle_frames::first::{{closure}}
8: short_ice_remove_middle_frames::first
9: short_ice_remove_middle_frames::main
10: core::ops::function::FnOnce::call_once
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
Loading