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

Edition 2024: don't special-case diverging blocks #123590

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

WaffleLapkin
Copy link
Member

@WaffleLapkin WaffleLapkin commented Apr 7, 2024

Normally, when a block has no tail-expression its type is ():

let a /*: ()*/ = {};
let a /*: ()*/ = {
    statement(); // note the `;`!
};

However, this is not the case when the block is known to diverge, in which case its type becomes !1:

let a /*: !*/ = {
    return; // note the `;`!
};

let a /*: !*/ = {
    returns_never();
    blah(); // note the `;`!
}

I think that this is a useless special case and unnecessary complicates the language. I propose that we remove it in the next edition.

Note that you can always fix the code that used this special case by removing a ; (and removing dead-code, if there is any). We already have a machine-applicable fix (as can be seen in the test added in this PR).

// from the above example, changes you'd need to do in the next edition

let a /*: !*/ = {
    return // no `;`!
};

let a /*: !*/ = {
    returns_never() // no `;`!
                    // removed `blah();` which was dead code
}

Also note that while this is related to the never type, this change is independent of the work on its stabilization. I personally think that if we are going to stabilize !, we should make it less weird and this is one of the ways to do that; but, this is not required for ! stabilization and is a completely separate cleanup.

The only hard part of this change is that rustfmt currently adds ; to returns which are at the end of blocks. We'll have to change rustfmt style in this regard in order to support the next edition. Out options are

  1. Make rustfmt keep ;, but not add ; (this won't break any existing users)
  2. Make rustfmt add ; in the edition<=2021 and remove ; in the edition>=2024 (this will automatically fix code when porting to the new edition) (does rustfmt even support editions?...)
  3. Make rustfmt remove ; (this will break all existing formatting, I don't think that's desirable)

r? compiler-errors

Footnotes

  1. it then immediately decays to () due to current never type fallback, but it does not matter -- you can still specify any type and the block will be coerced to that

@rustbot rustbot added S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. labels Apr 7, 2024
@WaffleLapkin WaffleLapkin added T-lang Relevant to the language team, which will review and decide on the PR/issue. needs-fcp This change is insta-stable, so needs a completed FCP to proceed. F-never_type `#![feature(never_type)]` A-rustfmt Area: Rustfmt I-lang-nominated The issue / PR has been nominated for discussion during a lang team meeting. A-edition-2024 Area: The 2024 edition labels Apr 7, 2024
@traviscross traviscross added the T-style Relevant to the style team, which will review and decide on the PR/issue. label Apr 7, 2024
@scottmcm
Copy link
Member

I wonder about maybe special-casing keyword control flow here. Trying to push a "no, you can't write return; any more" might not be worth it, but let Some(x) = foo() else { bar(); }; working with the semicolon for a function that happens to return ! feels much more weird to me.

Given that we could change panic!'s expansion to have a return internally, for example, that might help resolve the "wait, that's not how types work" concerns with less impact on relatively normal code? I don't know that I want to insist on changing

let Some(x) = foo else {
    return;
};

everywhere, especially since we've been pushing people to that.

@nikomatsakis

This comment was marked as outdated.

@rfcbot

This comment was marked as outdated.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Apr 10, 2024
@nikomatsakis

This comment was marked as outdated.

@rfcbot

This comment was marked as outdated.

@rfcbot rfcbot removed proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Apr 10, 2024
@nikomatsakis

This comment was marked as outdated.

@rustbot rustbot removed the T-compiler Relevant to the compiler team, which will review and decide on the PR/issue. label Apr 10, 2024
@nikomatsakis

This comment was marked as outdated.

@rfcbot

This comment was marked as outdated.

@rfcbot rfcbot added proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. labels Apr 10, 2024
@compiler-errors compiler-errors added the disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. label Apr 10, 2024
@nikomatsakis
Copy link
Contributor

@rfcbot merge

We discussed this in @rust-lang/lang today. My takeaways from the meeting (and I think roughly meeting consensus) was...

  • We agree that the current behavior can be confusing. In general, people find the rules around when a ; is required confusing, and this special case can make things even more so. In practice many people seem to just add or remove ; as prompted by the compiler until they get a feel for it, and so it seems likely that this may not be noticeable, or might even make developing those intutions easier, since the underlying system will be more straightforward.
  • But there are some examples where adding a ; felt like the obvious thing to do, e.g., {return foo;} or {panic!("foo");} -- to the extent that rustfmt even adds the ; automatically.
  • If we change the behavior in a future Rust edition, keeping with the Edition tenet that "Rust should feel like one language", we also want to add lints to adjust code in past editions even if its not needed.

Immediate next step being merged here: land this on nightly so that we can begin experimenting and make sure there aren't cases we've overlooked where this rule is important.

@compiler-errors compiler-errors removed the disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. label Apr 10, 2024
@rfcbot
Copy link

rfcbot commented Apr 10, 2024

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

Concerns:

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!

cc @rust-lang/lang-advisors: FCP proposed for lang, please feel free to register concerns.
See this document for info about what commands tagged team members can give me.

@rfcbot rfcbot added the disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. label Apr 10, 2024
@WaffleLapkin WaffleLapkin added the I-style-nominated The issue / PR has been nominated for discussion during a style team meeting. label Apr 10, 2024

/// Returns the edition-based default diverging block behavior
fn default_diverging_block_behavior(tcx: TyCtxt<'_>) -> DivergingBlockBehavior {
if tcx.sess.edition().at_least_rust_2024() {
Copy link
Member

Choose a reason for hiding this comment

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

Is this right? Couldn't the block come from an older-edition macro?

Copy link
Member Author

Choose a reason for hiding this comment

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

This is not right, good point. I was thinking so much about the never type fallback change (which has to be crate-global) that I forgot we can have more local things 🙈

Will change it to check the edition per-block.

Copy link
Member

Choose a reason for hiding this comment

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

Should we put this behind a feature gate? Especially if it's for experimenting. We can make a "stabilization PR" that removes the need for the gate once it's settled.

@scottmcm
Copy link
Member

@rfcbot concern return-churn

Given that we're been encouraging

letelse {
    return;
};

in the current edition, I think jumping directly to "nope, hard error" on an edition edge is too severe.

Since as I read this it would make this part of the 2024 edition and I think right now that I'd block on that going to stable, I think a concern is procedurally right here? I don't want to get to Sept and raise a concern then on the whole edition.

I'm absolutely in favour of experimenting with this in nightly, but maybe as a -Z flag or feature flag or lint or … instead?

(Or, as described above in #123590 (comment) , I'd also be happy with a less strong version of this that doesn't break keyworded divergence.)

@WaffleLapkin
Copy link
Member Author

@scottmcm I think calling functions returning ! is rare enough for the half-change you are proposing to not be useful. That is, the return, in my opinion, is the main source of the confusion and weirdness. Changing the special case from "! expressions" to "return, continue, break" feels to me like adding more special cases, not less1.

The reason I think this makes sense as an edition change is that the fix is so easy. Both in "I want to prepare for the next edition"2 and in "I changed the edition, now my code doesn't compile" ways3. So while this will affect many, I think the impact is very light.

Also of note that the change will only make a difference when the type of the block is expected to be something, so not all blocks with the return will break (although still, many).

If you have a specific experiment or a question we could test on nightly, I'm happy to do that. But otherwise, I'm not sure what we can do here.

(as an aside, I think the concern is 100% procedurally right)

Footnotes

  1. It's a funny case, I think "return, continue, break" is better than "! expressions", but doing "! expressions" and then switching to "return, continue, break" is worse, because now there is not only the difference between language behavior normally, and with !, but also between the editions

  2. We can easily add a lint for {return;} which has a machine applicable fix (allow by default, with the intention of people enabling it if they want to prepare for the edition)

  3. This PR makes sure we have a machine applicable fix for the type error that is caused by the change

@calebcartwright
Copy link
Member

I agree that t-style getting pulled into approval was undesirable, but this does seem to involve rustfmt & style to a significant enough extent that I'd like to ensure both teams are aware and following the conversation

cc @rust-lang/rustfmt for awareness

@WaffleLapkin
Copy link
Member Author

I don't think this is waiting on review... It's waiting either on fcp/team or blocked on rustfmt work, namely rust-lang/rustfmt#6149. I'm going to mark this as the latter. @rustbot blocked

@rustbot rustbot added S-blocked Status: Marked as blocked ❌ on something else such as an RFC or other implementation work. and removed S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. labels Apr 24, 2024
@jackh726
Copy link
Member

Just putting in my two cents here, as a lang advisor I guess:

I think this is one of those cases where on paper the motivation makes sense, but in practice it's pretty weird.

First, I would expect this change to be much bigger than the examples given in this thread already. The let else example @scottmcm gave also applies to match and if/else, right? Also, things like panic!(); at the end of blocks. I'm surprised there aren't test breakages - can we get a crater run?

Today, what is the type of a block where there is dead code after a diverging statement without a semicolon? If the answer is !, then I think we're consistent today (i.e. the rule is that if there diverging expressions in a block, the type of the block itself is diverging). If not, then I think that's a better change for consistency.

@scottmcm
Copy link
Member

The "hard edge" problem sounds like it's resolved, so that it's possible to check in rustfmt-compliant code that compiles before and after the edition, with the ; being optional, so I guess I can
@rfcbot resolve return-churn

(We might get a new one from the discussion in triage today, but I want to acknowledge the work done already.)

@tmandry
Copy link
Member

tmandry commented Apr 24, 2024

@rfcbot concern is it worth the churn

I'd like to get an idea of how much churn there is, so we can make a decision about the tradeoffs. So far this feels like more of a theoretical improvement, but one that has very concrete churn (though it is automated at least). I'm not sure if that churn is worth it.

In the lang team meeting today we discussed using the standard library as a testing ground.

First, I would expect this change to be much bigger than the examples given in this thread already.

Most tests are not using edition 2024, which is one reason the change wouldn't be large.

@traviscross
Copy link
Contributor

traviscross commented Apr 26, 2024

@rustbot labels -I-lang-nominated +S-waiting-on-author

As tmandry mentioned, we discussed this in the lang call on 2024-04-24. The specific next step to move this forward is to convert the standard library (and perhaps rustc generally) to follow this rule and to post that commit/PR so we can see the diff. People want to get a feel for the churn here.

There was discussion that if this seems like too much for Rust 2024, then perhaps we might plot a course for doing this in the edition after, as there seemed to be a general feeling that if we had a time machine, we would do this.

Please renominate when the diff is posted. Thanks to @WaffleLapkin for pushing this forward.

@rustbot rustbot added S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. and removed I-lang-nominated The issue / PR has been nominated for discussion during a lang team meeting. labels Apr 26, 2024
@WaffleLapkin
Copy link
Member Author

Today, what is the type of a block where there is dead code after a diverging statement without a semicolon? If the answer is !, then I think we're consistent today (i.e. the rule is that if there [is a] diverging expressions in a block, the type of the block itself is diverging).

@jackh726 the answer is indeed ! and the rule you described is exactly what rustc is using. But I'm not sure what this is consistent with? To me this seems like a bit of a weird behavior, and one which is never taught (although is used often!).

@WaffleLapkin
Copy link
Member Author

@tmandry here is a commit which fixes the standard library: 6e09358 (I'm not pushing it here, since it currently fails rustfmt).

Summary:

  • almost all the changes are just x fix library --stage 1 -- --broken-code
  • changes in macros can't be auto-applied
    • but note that macros from crates on older editions will still work (this is currently broken in this PR, but will be fixed)
  • we are lucking a suggestion for let-else
    • should be easy to add
  • I had to move a few functions

The last part is the only annoying thing in my opinion. Here is an example:

fn f() -> u8 {
    return helper();
    fn helper() -> u8 { 1 }
}

the return is sometimes written like this, to make the call before the helper function definition. However, this will not work with this PR in the next edition, so you have to move the helper function before the call:

fn f() -> u8 {
    fn helper() -> u8 { 1 }
    helper()
}

I think this looks fine, there aren't that many changes (git statistic looks bad, because it doesn't understand moves), given how big the standard libraries are and given that almost all changes can be auto-applied. That being said I'm not sure how often helper functions at the end of an outer function like the above are used.


@traviscross I did not change the compiler, because there is currently no way to change the behavior in all rustc_* crates. But let me know if I should experiment on the compiler crates too. (I'm not sure how (in)valid it is to look only at the std...)

@jackh726
Copy link
Member

jackh726 commented Apr 26, 2024

Today, what is the type of a block where there is dead code after a diverging statement without a semicolon? If the answer is !, then I think we're consistent today (i.e. the rule is that if there [is a] diverging expressions in a block, the type of the block itself is diverging).

@jackh726 the answer is indeed ! and the rule you described is exactly what rustc is using. But I'm not sure what this is consistent with? To me this seems like a bit of a weird behavior, and one which is never taught (although is used often!).

So, consistent is maybe not the completely correct word. I'm trying to come up with good examples to try to show my thoughts, but the biggest thing to me is that in every other piece of code, when we write a return something; we basically always write a semicolon. (Like, e.g. in an if block.) And today, that is also the case for returns written in a block that you want to be diverging and at the end of functions. So, essentially, it makes no difference if return statements have a semicolon or not. With this rule, it actually starts to matter. it's now "everywhere else, you use a semicolon on a return statement, but if you want to rely on the fact that block diverges, you don't add one". (This is most notable in match or if/else.)

This is also kind of true for returns at the end of functions (even though this isn't recommended style):

fn foo() -> u32 {
  return 0;
}

Under this PR, you could expect that with the semicolon, you effectively "desugar" to

fn foo() -> u32 {
  return 0;
  ()
}

(under the logic that a new statement of () is always inserted at the end of a block that doesn't have one), which is definitely not what we intend. (Of course, it's not 100% the same, since the return here actually corresponds to the "innermost" block itself.)

The last part is the only annoying thing in my opinion.

Thinking about it, the last point above is sort of why this is a thing: because we don't expect that a return statement in a function to add an implicit () statement at the end.


I guess, all in all, today we treat return X as a statement even though it's really an expression. But, that means that essentially everywhere we see return X; with the semicolon (see for example the reference. With this PR, this consistency starts to split depending on where the return is and how it's used to control the logic of a block.

I can see where the logic of this PR comes from: "blocks without a expression at the end get an implicit ()". But, it's not like there is "easy" logic to what the value of that expression is: return and break depend on what kind of block it is, for example.

I think if we wanted to make this change and it not be extremely disruptive (not necessarily just code, but to the way we think about writing Rust), we should probably do it in steps (similar to unsafe_block_in_unsafe_fn): I think to start, we would want to soft-deprecate the use of return X; and break X; in general, preferring just return X and break X. At some point, we could start to warn when return X or break X is a diverging value, then adjust rustfmt to format return X; to return X (and break). Then finally make this change.

@jackh726
Copy link
Member

Realizing that I didn't connect my thoughts back to my original question (and realizing that the phrasing of my question was nonsense anyways). And, the answer was in the OP the whole time anyways.

One thing to note is that with the changes here, I'm not sure it's possible to write a block with dead code after a diverging expression without also making the final expression be the real type or also diverging. It's very minor, but can make a debugging development cycle a bit more difficult. E.g. you can no longer stick a panic!() at the top of a block that are trying to figure out if it's being taken (and where). This is somewhat along the lines of what I was originally, to get at, I think: it doesn't matter where the diverging bit is in the block (it doesn't need to the return statement).

That being said, my above comment better explains my thoughts on consistency here anways.

@traviscross traviscross added I-lang-nominated The issue / PR has been nominated for discussion during a lang team meeting. and removed S-waiting-on-author Status: This is awaiting some action (such as code changes or more information) from the author. labels Apr 26, 2024
@WaffleLapkin
Copy link
Member Author

Thinking about it, the last point above is sort of why this is a thing: because we don't expect that a return statement in a function to add an implicit () statement at the end.

Inserting () is not a property of return here, it's a property of blocks, which already exists in rust -- { true } has type bool but { true; } has type (), which makes it basically equivalent to { true; () } which also has type ().

I do expect a () to be inserted if a block ends in a statement rather than an expression. At least that's the idea that I got while learning rust. And I don't really see why return expression must be different...

But, it's not like there is "easy" logic to what the value of that expression is: return and break depend on what kind of block it is, for example.

I'm not sure what this means.

At some point, we could start to warn when return X or break X is a diverging value, [...]

Again, not sure what this means, return ... is always a diverging expressions, it always has a type !. And same with break.

then adjust rustfmt to format return X; to return X (and break). Then finally make this change.

I don't think we need this and I don't think this can happen. rustfmt should not change semantics, and with this PR removing a semicolon does change things. So rustfmt should only preserve the existence or lack of the semicolon.

Adding a lint alike unsafe_block_in_unsafe_fn for things that would be broken by this PR is trivial though (indeed I planned to add it anyway, for edition migration reasons) -- you just check if a block has no tail expression and has type !.

@jackh726
Copy link
Member

Thinking about it, the last point above is sort of why this is a thing: because we don't expect that a return statement in a function to add an implicit () statement at the end.

Inserting () is not a property of return here, it's a property of blocks, which already exists in rust -- { true } has type bool but { true; } has type (), which makes it basically equivalent to { true; () } which also has type ().

I'm well aware of where the implicit empty tuple expression comes from. What I mean is that when somebody has written return X; at the end of a function, they don't expect that implicit expression to be added.

I do expect a () to be inserted if a block ends in a statement rather than an expression. At least that's the idea that I got while learning rust. And I don't really see why return expression must be different...

It's not that it must be different, it's that writing return X; is not only just a very familiar thing to do for a lot of people, but is also widely taught for Rust (see reference link above, for example). I'm all for consistency in our logic, but not when that not only is different from what most people expect and there is some consistency in what we already do (diverging expressions result in diverging blocks with no implicit return).

But, it's not like there is "easy" logic to what the value of that expression is: return and break depend on what kind of block it is, for example.

I'm not sure what this means.

If you think about a return expression at the end of a fn block versus a non-diverging expression: typically, the type of the last expression is the type of the block. So, diverging expressions also make the block diverging. But this not true of return: the type of the expression is diverging but the type of the block is not. Similar for break in other blocks.

At some point, we could start to warn when return X or break X is a diverging value, [...]

Again, not sure what this means, return ... is always a diverging expressions, it always has a type !. And same with break.

Not sure what I meant here.

then adjust rustfmt to format return X; to return X (and break). Then finally make this change.

I don't think we need this and I don't think this can happen. rustfmt should not change semantics, and with this PR removing a semicolon does change things. So rustfmt should only preserve the existence or lack of the semicolon.

Today they do mean the same thing though. And the code after this PR more closely matches the code without a semicolon.

Adding a lint alike unsafe_block_in_unsafe_fn for things that would be broken by this PR is trivial though (indeed I planned to add it anyway, for edition migration reasons) -- you just check if a block has no tail expression and has type !.

But I don't think that criteria goes far enough: then you end up in a situation where some returns have a semicolon and some don't.

@traviscross
Copy link
Contributor

traviscross commented May 1, 2024

We discussed this in the lang triage call today. Seeing the migration of the standard library was helpful. Thanks to @WaffleLapkin for that.

Many people found compelling the changes that looked like this:

 pub fn abort() -> ! {
-    crate::sys::abort_internal();
+    crate::sys::abort_internal()
 }
                     let Some(id) = last.checked_add(1) else {
-                        exhausted();
+                        exhausted()
                     };

...and wanted to accept the churn for these. However, people remain uncertain about cases like this:

             } else {
-                return false; // other is empty
+                return false // other is empty
             };

In particular, the concern here is a feeling of whiplash given that, due to rustfmt, we've in a sense been suggesting that people add these ;s, and it just might be too much too soon to flip this for Rust 2024.

In the meeting, interest converged around what we called Option 2, which is:

  1. In Rust 2024, narrow the scope of the special casing from all diverging expressions to just those where return, break, or continue are used syntactically and no dead code (that doesn't itself diverge) follows. Do this after macro expansion at the time of type checking (so that, e.g. let _: ! = { panic!(); } also works).
    • This would promote local reasoning, making the behavior less weird.
    • It would take most of the common cases off the table for the moment.
    • We could at any later time warn about these cases also.
    • Then, if we so choose, we could then take the next step in a later edition of disallowing these.

To solidify this tentative consensus, we wanted to first lay out and review some examples. Those examples follow.

Examples for further discussion

Rust currently rejects these:

fn foo(x: bool) {
    let true = x else {
        return; // or break, continue
        ()
    };
    //~^ ERROR `else` clause of `let...else` does not diverge
}
fn foo(x: bool) {
    let true = x else {
        ();
    };
    //~^ ERROR `else` clause of `let...else` does not diverge
}

Rust currently accepts these:

fn foo(x: bool) {
    let true = x else {
        return // or any diverging expression
    };
}
fn foo(x: bool) {
    let true = x else {
        return; // or any diverging expression
    };
}
fn foo(x: bool) {
    let true = x else {
        return; // or any diverging expression
        ();
    };
}
fn foo(x: bool) {
    let true = x else {
        panic!(); // or unimplemented!(), todo!(), etc.
    };
}
fn foo() -> ! {
    foo();
}

In Rust 2024, under Option 2, we would reject these:

fn foo(x: bool) {
    let true = x else {
        return; // or `continue` or `break`
        ();
        //~^ ERROR when the block must diverge, any dead code that
        //~|       follows `return` must also diverge
    };
}
fn foo() -> ! {
    foo();
    //~^ ERROR type mismatch
}
fn foo() -> ! {
    foo();
    ();
    //~^ ERROR type mismatch
}

In Rust 2024, we would still of course allow:

fn foo(x: bool) {
    let true = x else {
        return; // or any diverging expression
        ();
        todo!()
    };
}

And in Rust 2024, we would continue to allow:

fn foo(x: bool) {
    let true = x else {
        return; // or `continue` or `break`
    };
}

...and:

fn foo(x: bool) {
    let true = x else {
        panic!(); // or unimplemented!(), todo!(), etc.
    };
}

Trailing items

Independently of this, but prompted by the observation that some cases of where updates in the standard library were needed were where items followed the trailing expression in a function (rather than being at the top of the function), we discussed whether we should just make that work. E.g., we might want to allow:

fn foo() {
    return
    fn bar() {} // or other items, e.g., `const C: u32 = ..`
}
fn foo() -> u8 {
    42
    fn bar() {} // or other items, e.g., `const C: u32 = ..`
}

Next steps

We'll pick this back up in our next meeting.

@jackh726
Copy link
Member

jackh726 commented May 1, 2024

I honestly think the conclusion drawn in the lang meeting make a lot of sense and probably result in a net positive all around.

I could elaborate more, but I think my comments above probably say all that I could here anyways. I'm happy with the conclusions made.

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 A-rustfmt Area: Rustfmt disposition-merge This issue / PR is in PFCP or FCP with a disposition to merge it. F-never_type `#![feature(never_type)]` I-lang-nominated The issue / PR has been nominated for discussion during a lang team meeting. I-style-nominated The issue / PR has been nominated for discussion during a style team meeting. needs-fcp This change is insta-stable, so needs a completed FCP to proceed. proposed-final-comment-period Proposed to merge/close by relevant subteam, see T-<team> label. Will enter FCP once signed off. S-blocked Status: Marked as blocked ❌ on something else such as an RFC or other implementation work. T-lang Relevant to the language team, which will review and decide on the PR/issue.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet