Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Tracking Issue for Rust 2024: Make ! fall back to ! #123748

Open
2 of 10 tasks
traviscross opened this issue Apr 10, 2024 · 5 comments
Open
2 of 10 tasks

Tracking Issue for Rust 2024: Make ! fall back to ! #123748

traviscross opened this issue Apr 10, 2024 · 5 comments
Assignees
Labels
A-edition-2024 Area: The 2024 edition C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. F-never_type `#![feature(never_type)]` S-tracking-impl-incomplete Status: The implementation is incomplete. S-tracking-needs-documentation Status: Needs documentation. T-lang Relevant to the language team, which will review and decide on the PR/issue.

Comments

@traviscross
Copy link
Contributor

traviscross commented Apr 10, 2024

This is a tracking issue for the change to make fallback fall back from ! to ! (rather than to ()) in Rust 2024. This is part of a plan that leads to eventually stabilizing the never (!) type.

What this change means

This change means that ! does not spontaneously decay to (). For example, consider this code:

fn print_return_type<R>(_: fn() -> R) {
    println!("`{}`", std::any::type_name::<R>());
}

print_return_type(|| todo!()); // before this change: prints `()`
                               // after this change: prints `!`

todo!() "returns" !, but before this change this ! decayed to () when being returned from the closure. In general, this can happen at any coercion site, if the type is not set by something else.

If your code is broken with errors like `!: Trait` is not satisfied, then you need to make sure the type is specified explicitly. For example:

fn create_zst<T: From<()>>() -> T {
    ().into()
}

if condition {
    create_zst()
} else {
    return
};

Before this change ! from return decays to (), meaning that create_zst will also return (), and since () implements From<()> this was fine. With this change however, ! from return keeps being ! and inference makes create_zst also return !, however ! does not implement From<()> causing a compilation error.

The easiest fix is to specify the type explicitly:

if condition {
    create_zst::<()>()
} else {
    return
};

However, this can also be a sign that you don't care what the type is, and maybe the better solution is to refactor the code.

Also note that this "create something or return" pattern can also be caused by ? with code like this:

deserialize(input)?;

Because deserialize's output type is not bound to be anything, it gets inferred from ?'s desugaring that includes a return. (there is a separate initiative to stop ? from affecting inference like this: #122412)

About tracking issues

Tracking issues are used to record the overall progress of implementation. They are also used as hubs connecting to other relevant issues, e.g., bugs or open design questions. A tracking issue is however not meant for large scale discussion, questions, or bug reports about a feature. Instead, open a dedicated issue for the specific matter and add the relevant feature gate label.

Steps

Unresolved Questions

Implementation history

@traviscross traviscross added C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. T-lang Relevant to the language team, which will review and decide on the PR/issue. A-edition-2024 Area: The 2024 edition labels Apr 10, 2024
@WaffleLapkin WaffleLapkin self-assigned this Apr 11, 2024
@WaffleLapkin WaffleLapkin added the F-never_type `#![feature(never_type)]` label Apr 11, 2024
@traviscross traviscross changed the title Tracking Issue for making ! fall back to ! Tracking Issue for Rust 2024: Make ! fall back to ! Apr 19, 2024
@WaffleLapkin
Copy link
Member

WaffleLapkin commented May 17, 2024

Add a lint against fallback affecting non-diverging variables in weird ways / ? guiding inference.

It turned out that this is actually tricky/impossible to implement, because we can loose the relationships between inference variables by unifying them:

// easy to lint
// coercion graph edges: [?1t -> ?3t, ?2t -> ?3t]
match true {
    false => return /* ?1t */,
    true => Default::default() /* ?2t */,
} /* ?3t */;

// impossible to lint
// coercion graph edges: [?2t -> ?1t]
match true {
    true => Default::default() /* ?1t */,
    false => return /* ?2t */,
} /* ?1t */;

Even though these examples are very similar, they have enough of a difference in the coercion graph, that we can't lint them both reliably.

I talked with Niko at RustNL and we agreed that given these circumstances this lint can be omitted.

matthiaskrgr added a commit to matthiaskrgr/rust that referenced this issue May 20, 2024
…rovements, r=compiler-errors

Never type unsafe lint improvements

- Move linting code to a separate method
- Remove mentions of `core::convert::absurd` (rust-lang#124311 was rejected)
- Make the lint into FCW

The last thing is a bit weird though. On one hand it should be `EditionSemanticsChange(2024)`, but on the other hand it shouldn't, because we also plan to break it on all editions some time later. _Also_, it's weird that we don't have `FutureReleaseSemanticsChangeReportInDeps`, IMO "this might cause UB in a future release" is important enough to be reported in deps...

IMO we ought to have three enums instead of [`FutureIncompatibilityReason`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint_defs/enum.FutureIncompatibilityReason.html#):

```rust
enum IncompatibilityWhen {
     FutureRelease,
     Edition(Edition),
}

enum IncompatibilyWhat {
    Error,
    SemanticChange,
}

enum IncompatibilityReportInDeps {
    No,
    Yes,
}
```

Tracking:
- rust-lang#123748
rust-timer added a commit to rust-lang-ci/rust that referenced this issue May 20, 2024
Rollup merge of rust-lang#125282 - WaffleLapkin:never-type-unsafe-improvements, r=compiler-errors

Never type unsafe lint improvements

- Move linting code to a separate method
- Remove mentions of `core::convert::absurd` (rust-lang#124311 was rejected)
- Make the lint into FCW

The last thing is a bit weird though. On one hand it should be `EditionSemanticsChange(2024)`, but on the other hand it shouldn't, because we also plan to break it on all editions some time later. _Also_, it's weird that we don't have `FutureReleaseSemanticsChangeReportInDeps`, IMO "this might cause UB in a future release" is important enough to be reported in deps...

IMO we ought to have three enums instead of [`FutureIncompatibilityReason`](https://doc.rust-lang.org/nightly/nightly-rustc/rustc_lint_defs/enum.FutureIncompatibilityReason.html#):

```rust
enum IncompatibilityWhen {
     FutureRelease,
     Edition(Edition),
}

enum IncompatibilyWhat {
    Error,
    SemanticChange,
}

enum IncompatibilityReportInDeps {
    No,
    Yes,
}
```

Tracking:
- rust-lang#123748
@traviscross traviscross added S-tracking-impl-incomplete Status: The implementation is incomplete. S-tracking-needs-documentation Status: Needs documentation. labels May 21, 2024
@joshlf
Copy link
Contributor

joshlf commented May 22, 2024

zerocopy ran into a potentially sharp edge with the never_type_fallback_flowing_into_unsafe lint today. Not sure yet whether this is actually problematic, but it's at least worth letting folks know about. The docs say:

Instead of depending on the fallback, you should specify the type explicitly

We ran into this issue when updating to the latest nightly. Here's the error message:

warning: never type fallback affects this call to an `unsafe` function
  --> tests/ui-nightly/transmute-mut-src-dst-not-references.rs:17:44
   |
17 | const SRC_DST_NOT_REFERENCES: &mut usize = transmute_mut!(0usize);
   |                                            ^^^^^^^^^^^^^^^^^^^^^^
   |
   = warning: this will change its meaning in a future release!
   = note: for more information, see issue #123748 <https://github.com/rust-lang/rust/issues/123748>
   = help: specify the type explicitly
   = note: `#[warn(never_type_fallback_flowing_into_unsafe)]` on by default
   = note: this warning originates in the macro `$crate::assert_size_eq` which comes from the expansion of the macro `transmute_mut` (in Nightly builds, run with -Z macro-backtrace for more info)

Here's the offending macro:

/// Do `t` and `u` have the same size?  If not, this macro produces a compile
/// error. It must be invoked in a dead codepath. This is used in
/// `transmute_ref!` and `transmute_mut!`.
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
#[macro_export]
macro_rules! assert_size_eq {
    ($t:ident, $u:ident) => {{
        // The comments here should be read in the context of this macro's
        // invocations in `transmute_ref!` and `transmute_mut!`.
        if false {
            // SAFETY: This code is never run.
            $u = unsafe {
                // Clippy:
                // - It's okay to transmute a type to itself.
                // - We can't annotate the types; this macro is designed to
                //   infer the types from the calling context.
                #[allow(clippy::useless_transmute, clippy::missing_transmute_annotations)]
                $crate::macro_util::core_reexport::mem::transmute($t)
            };
        } else {
            loop {}
        }
    }};
}

Note that $t and $u are values, not types. This macro is called from transmute_mut!, which is simply invoked as transmute_mut!(<expr>), and is structured so that Rust will correctly infer the source and destination types from the surrounding context. The punchline is: there's no way for us to name the correct type as suggested by the lint documentation.

I believe that this lint isn't actually an issue, and we could soundly allow it. In particular, this particular test case tests an intentionally-invalid invocation of the macro, which presumably causes type inference to not have as much information as it normally would, which in turn causes the fallback logic to be reached. Note that the warning is not generated in our various success tests.

However, I will admit to being spooked by this since it's new to me and I haven't spent the time to really wrap my head around it to convince myself that it's definitely fine. I might instead just rewrite that macro. In particular, it's always called from dead code, but has the if false { ... } else { loop {} } structure so that the macro itself doesn't have to be unsafe to call. I might just give up on that aspiration and rewrite it so that what is currently the body of if false { ... } becomes the entire macro, making it unsafe to call:

/// Do `t` and `u` have the same size?  If not, this macro produces a compile
/// error. It must be invoked in a dead codepath. This is used in
/// `transmute_ref!` and `transmute_mut!`.
///
/// # Safety
///
/// The code generated by this macro must never be executed at runtime.
#[doc(hidden)] // `#[macro_export]` bypasses this module's `#[doc(hidden)]`.
#[macro_export]
macro_rules! assert_size_eq {
    ($t:ident, $u:ident) => {{
        // Clippy:
        // - It's okay to transmute a type to itself.
        // - We can't annotate the types; this macro is designed to
        //   infer the types from the calling context.
        #[allow(clippy::useless_transmute, clippy::missing_transmute_annotations)]
        $crate::macro_util::core_reexport::mem::transmute($t)
    }};
}

@joshlf
Copy link
Contributor

joshlf commented May 22, 2024

Hmmm, confusingly the "remove the loop {}" approach didn't actually work - note that the lint is still triggered in google/zerocopy#1339

@WaffleLapkin
Copy link
Member

@joshlf note that this only happens when there is an error already, so I don't think this is a problem.

The reason warning is emitted is that compiler can't infer type of e (it knows it's &mut {something}, but not what {something} is; this is only the case when you assign a non-reference to e, which causes a type error). Then e gets unified with a diverging variable and ultimately gets passed to transmute. I believe the lint is correct. See [code I copied] and, more importantly, [my minimization with explanations].

@joshlf
Copy link
Contributor

joshlf commented May 23, 2024

@joshlf note that this only happens when there is an error already, so I don't think this is a problem.

The reason warning is emitted is that compiler can't infer type of e (it knows it's &mut {something}, but not what {something} is; this is only the case when you assign a non-reference to e, which causes a type error). Then e gets unified with a diverging variable and ultimately gets passed to transmute. I believe the lint is correct. See [code I copied] and, more importantly, [my minimization with explanations].

Okay that makes sense, thanks for investigating!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-edition-2024 Area: The 2024 edition C-tracking-issue Category: A tracking issue for an RFC or an unstable feature. F-never_type `#![feature(never_type)]` S-tracking-impl-incomplete Status: The implementation is incomplete. S-tracking-needs-documentation Status: Needs documentation. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

No branches or pull requests

3 participants