18

unreachable!("{}") works on Rust 2021 · Issue #92137 · rust-lang/rust...

 2 years ago
source link: https://github.com/rust-lang/rust/issues/92137
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Copy link

Member

m-ou-se commented on Dec 20, 2021

edited

The behavior of unreachable does not depend the edition of the crate that invoked the macro. That's fine for todo!() and unimplemented!() due to the way they are implemented, but in #92068, it was discovered that unreachable!() behaves subtly different, due to the way it has a special one-argument case. Looks like missed this when working on the 2021 panic macros. :(

m-ou-se

changed the title unimplemented!("{}") works on Rust 2021

unreachable!("{}") works on Rust 2021

on Dec 20, 2021

Copy link

Member

Author

m-ou-se commented on Dec 20, 2021

todo!() and unimplemented!() are implemented like this:

macro_rules! todo {

() => ($crate::panic!("not yet implemented"));

($($arg:tt)+) => ($crate::panic!("not yet implemented: {}", $crate::format_args!($($arg)+)));

}

They never accepted todo!("{}") or todo!(123) since their arguments are always directly passed to format_args!(). So, they didn't have to change for Rust 2021.

unreachable!() however, is implemented differently:

macro_rules! unreachable {

() => ({

$crate::panic!("internal error: entered unreachable code")

});

($msg:expr $(,)?) => ({

$crate::unreachable!("{}", $msg)

});

($fmt:expr, $($arg:tt)*) => ({

$crate::panic!($crate::concat!("internal error: entered unreachable code: ", $fmt), $($arg)*)

});

}

It doesn't just forward to panic!() or format_args!(), but instead it does its own concat shenanigans, and has special handling of the single argument case.

Copy link

Member

Author

m-ou-se commented on Dec 20, 2021

So then the question is: Do we consider this a bug in Rust 2021 and fix it in Rust 1.59 for that edition? Or leave this in and only fix it in a later edition (or never)?

There's quite a few cases on crates.io of Rust 2015/2018 code depending on this behavior, but Rust 2021 is very new at this point. Adding this change/fix to Rust 2021 probably doesn't break anyone, yet.

Copy link

Member

Author

m-ou-se commented on Dec 20, 2021

Behavior:

unreachable!();          // panic message: "internal error: entered unreachable code"
unreachable!(123);       // panic message: "internal error: entered unreachable code: 123"
unreachable!("{}");      // panic message: "internal error: entered unreachable code: {}"
unreachable!("{}", 123); // panic message: "internal error: entered unreachable code: 123"

Copy link

Member

Author

m-ou-se commented on Dec 20, 2021

We can't fix unreachable without making it a builtin macro, like panic!(). That's because (unlike e.g. debug_assert), this macro doesn't just forward to assert/panic, but has its own problematic implementation with a special case that shouldn't exist.

Copy link

Member

jyn514 commented on Dec 20, 2021

We can't fix unreachable without making it a builtin macro, like panic!(). That's because (unlike e.g. debug_assert), this macro doesn't just forward to assert/panic, but has its own problematic implementation with a special case that shouldn't exist.

I'm not sure I follow - can't we remove the concat special case and forward directly to panic, like todo does?

Copy link

Member

Author

m-ou-se commented on Dec 20, 2021

I'm not sure I follow - can't we remove the concat special case and forward directly to panic, like todo does?

@jyn514 Then unreachable!(123) would stop working on Rust 2015/2018.

Copy link

Member

Author

m-ou-se commented on Dec 20, 2021

edited

From a quick grep through crates.io, it looks like there's about 15 crates on crates.io depending on the current behavior that would break if we made it work like todo!(). Here's a few examples from the grep results:

unreachable!(message);
unreachable!(&error)
unreachable!(format!("Cannot compute gcd of {:?} and {:?}", x, y))
Err(err) => unreachable!(err),
_ => unreachable!(format!("Unsupported socket family {}!", raw.family)),
l => unreachable!(format!("got unexpected length {0:?}", l)),
x => unreachable!(x),
Error { message } => unreachable!(message),
unreachable!(false);
unreachable!(msg)
unreachable!(&format!("n = {:?}", n)),
_ => unreachable!(ERROR_MSG),
_ => unreachable!(format!("{} is invalid month!", month)),
hash => { unreachable!(hash); }

Making unreachable built-in to fix this on Rust 2021 seems reasonable to me.

Copy link

Contributor

jhpratt commented on Dec 21, 2021

IMO as long as there's a crater run fixing this is reasonable. It's certainly a bug.

Copy link

Member

camelid commented 24 days ago

It'd probably be better to fix this sooner rather than later, as the edition is still relatively new.

Copy link

Member

camelid commented 24 days ago

Assigning priority as discussed in the prioritization working group.

@rustbot label: +P-high -I-prioritize

Copy link

Contributor

jhpratt commented 15 days ago

Short of eager macro expansion, this would require unreachable! becoming a built-in macro, correct?

Copy link

Contributor

jhpratt commented 15 days ago

edited

Thanks; not sure how I missed that comment. I won't have time until Tuesday at the earliest (and likely not that early), but I can give it a shot if no one else wants to before I get to it.

Just to be sure, the proposed change will also fix the fact that unreachable!("{x}") and unreachable!("{}", x) don't behave the same in 1.58+ with E2021, right? (The former prints internal error: entered unreachable code: {x} because it's not parsed as a format string with implicit args.)

Copy link

Contributor

Urgau commented 8 days ago

@jhpratt I've give it a shot today and basically everything works except that unreachable!("{x}") doesn't because:

error: there is no argument named `x`
  --> /home/archie/Projects/RustProjects/rust/src/test/ui/macros/unreachable-2021.rs:8:5
   |
LL |     unreachable!("x is {x}");
   |     ^^^^^^^^^^^^^^^^^^^^^^^^
   |
   = note: did you intend to capture a variable `x` from the surrounding scope?
   = note: to avoid ambiguity, `format_args!` cannot capture variables when the format string is expanded from a macro
   = note: this error originates in the macro `$crate::concat` (in Nightly builds, run with -Z macro-backtrace for more info)

This is due to the fact that the macro append to the format the string "internal error: entered unreachable code: " with:

        $crate::panic!($crate::concat!("internal error: entered unreachable code: ", $fmt), $($arg)*)

If anyone has a idea on how to solve it, let me know or if you want you can also continue my work.

Copy link

Contributor

jhpratt commented 8 days ago

As mentioned, making it a built-in macro seems to be the way to go. My intent was to pass as much as possible off to the panic macro handler. Good luck!

Copy link

Contributor

Urgau commented 8 days ago

edited

@jhpratt I tried to do it the same way as the panic macro, make it a builtin macro that redirects toward one of these two new macro unreachable_2015 and unreachable_2021.

Btw, the compilation error comes format_args! who only accept literal string for the formatting one when trying to capture from a surrounding scope. The only way I see to resolve the compilation error is to find a way to bypass either concat or the check inside format_args.

Copy link

Member

camelid commented 8 days ago

@Urgau what about doing the concatenation using internal compiler APIs? I don't know them off the top of my head, but there's probably a way to concatenate the strings from within the compiler without using concat!.

Copy link

Contributor

jhpratt commented 8 days ago

My intended solution involved manually prepending the relevant text to the format and passing the result of that to the method that expands the panic! macro. This should handle the 2015/2021 edition split automatically. You won't be able to use the concat! macro because eager expansion doesn't exist.

Copy link

Contributor

Urgau commented 8 days ago

I managed to get it working by adding an unstable format_args_capture_non_literal feature to format_args!.
The actual the logic is the same as the previous one.

@camelid @jhpratt Do you think that this is an acceptable solution (at least for a PR) ?

Copy link

Contributor

jhpratt commented 8 days ago

A quick once-over of it shows no obvious issues. I'd personally prefer a solution like I described because I think it would limit the amount of compiler magic, but I'm not on any teams.

Copy link

Member

camelid commented 8 days ago

I'd also prefer avoiding a internal-use-only feature gate that turns off error checks.

Copy link

Contributor

Urgau commented 8 days ago

I'd also prefer avoiding a internal-use-only feature gate that turns off error checks.

Technically it doesn't turn off but instead turn on a future possibility of RFC that defined capturing arguments but I get your concern.

Over the concern you two have expressed I will not submit a pr and also will not try to implement @jhpratt proposed solution because it involves TokenStream and I don't know enough about them.

Copy link

Member

camelid commented 8 days ago

edited

You could still open a PR and get feedback from macro experts about how to do this without the feature gate. Your code could probably be adapted fairly easily.

Copy link

Contributor

Urgau commented 8 days ago

edited

Open #93179 with my solution. Let's see what the reviewer think's about it.

Copy link

Member

Author

m-ou-se commented 6 days ago

cc @rust-lang/libs-api @rust-lang/project-edition-2021

We have a few weeks left before the next beta branches off, so we have time for an FCP without delaying this to a later Rust version.

Summary: unreachable!() turns out to be broken in a similar way as panic and assert were in Rust 2015 and Rust 2018: unreachable!("{}") worked 'fine'. This was missed, because this isn't the case for todo!() and unimplemented!(). This was unfortunately not included in the 2021 edition panic changes, but it seems best to add it to the 2021 edition now, only a few versions late. This won't break any Rust 2015/2018 code, but this is breaking for any Rust 2021 code using unreachable!(var) or unreachable!(123) or unreachable!("{}"). All 'normal' cases of this macro are unaffected. There's probably not many problematic invocations of this macro in general, and much less in Rust 2021 code, considering how recently that edition has been available.

#93179 contains the fix and upgrades the panic migration lint to also cover this case. Every instance of the problematic case of unreachable!() in Rust 2015/2018 will get a diagnostic with a correct (machine applicable) suggestion.

I just see this as a bug that needs fixing, as the decision to make the panic macros consistent in 2021 has already been made. But since this is technically breakage, let's quickly check some boxes or raise some concerns:

@rfcbot merge

Copy link

rfcbot commented 6 days ago

edited by yaahc

Team member @m-ou-se has proposed to merge this. The next step is review by the rest of the tagged team members:

No concerns currently listed.

Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up!

See this document for info about what commands tagged team members can give me.

Copy link

rfcbot commented 5 days ago

bellThis is now entering its final comment period, as per the review above. bell

Copy link

Contributor

nikomatsakis commented 4 days ago

Just catching up on this. Nice catch, @m-ou-se, and good hustle on fixing it, @Urgau


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK