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

Spec updates #8609

Merged
merged 3 commits into from
Dec 4, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion meetings/2024/LDM-2024-04-15.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ These proposed changes were approved.

## Interceptors

- [Open issues for interceptors](https://github.com/dotnet/csharplang/blob/interceptors-2024-01/proposals/interceptors-issues-2024-01.md#add-interceptslocationstring-locationspecifier)
- [Open issues for interceptors](https://github.com/dotnet/csharplang/blob/ff2c82c8d702d70e2704cd9265c97859cc2eb296/proposals/interceptors-issues-2024-01.md)

### File location

Expand Down
91 changes: 91 additions & 0 deletions proposals/block-bodied-switch-expression-arms.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Block-bodied switch expression arms

## Summary
[summary]: #summary

This proposal is an enhancement to the new switch expressions added in C# 8.0: allowing multiple statements in a switch expression arm. We permit braces after the arrow, and use `break value;` to return a value from the switch expression arm.

## Motivation
[motivation]: #motivation

This addresses a common complaint we've heard since the release of switch expressions: users would like to execute multiple things in a switch-expression arm before returning a value. We knew that this would be a top request after initial release, and this is a proposal to address that. This is not a fully-featured proposal to replace [`sequence expressions`](https://github.com/dotnet/csharplang/issues/377). Rather, it is constrained to just address the complaints around switch expressions specifically. It could serve as a prototype for adding sequence expressions to the language at a later date in a similar manner, but isn't intended to support or replace them.

## Detailed design
[design]: #detailed-design

We allow users to put brackets after the arrow in a switch expression, instead of a single statement. These brackets contain a standard statement list, and the user must use a `break` statement to "return" a value from the block. The end of the block must not be reachable, as in a non-void returning method body. In other words, control is not permitted to flow off the end of this block. Any switch arm can choose to either have a block body, or a single expression body as currently. As an example:

```cs
void M(List<object> myObjects)
{
var stringified = myObjects switch {
List<string> strings => string.Join(strings, ","),
List<MyType> others => {
string result = string.Empty;
foreach (var other in others)
{
if (other.IsFaulted) return;
else if (other.IsLastItem) break; // This breaks the foreach, not the switch

result += other.ToString();
}

break result;
},
_ => {
var message = $"Unexpected type {myObjects.GetType()}";
Logger.Error(message);
throw new InvalidOperationException(message);
}
};

Console.WriteLine(stringified);
}
```

We make the following changes to the grammar:

```antlr
switch_expression_arm
: pattern case_guard? '=>' expression
| pattern case_guard? '=>' block
;

break_statement
: 'break' expression? ';'
;
```

It is an error for the endpoint of a switch expression arm's block to be reachable. `break` with an expression is only allowed when the nearest enclosing `switch`, `while`, `do`, `for`, or `foreach` statement is a block-bodied switch expression arm. Additionally, when the nearest enclosing `switch`, `while`, `do`, `for`, or `foreach` statement is a block-bodied switch expression arm, an expressionless `break` is a compile-time error. When a pattern and case guard evaluate to true, the block is executed with control entering at the first statement of the block. The type of the switch expression is determined with the same algorithm as it does today, except that, for every block, all expressions used in a `break expression;` statement are used in determining the _best common type_ of the switch. As an example:

```cs
bool b = ...;
var o = ...;
_ = o switch {
1 => (byte)1,
2 => {
if (b) break (short)2;
else break 3;
}
_ => 4L;
};
```

The arms contribute `byte`, `short`, `int`, and `long` as possible types, and the best common type algorithm will choose `long` as the resulting type of the switch expression.

## Drawbacks
[drawbacks]: #drawbacks

As with any proposals, we will be complicating the language further by doing these proposals. With this proposal, we will effectively lock ourselves into a design for sequence expressions (should we ever decide to do them), or be left with an ugly wart on the language where we have two different syntax for similar end results.

## Alternatives
[alternatives]: #alternatives

An alternative is the more general-purpose sequence expressions proposal, https://github.com/dotnet/csharplang/issues/377. This (as currently proposed) would enable a more restrictive, but also more widely usable, feature that could be applied to solve the problems this proposal is addressing. Even if we don't do general purpose sequence expressions at the same time as this proposal, doing this form of block-bodied switch expressions would essentially serve as a prototype for how we'd do sequence expressions in the future (if we decide to do them at all), so we likely need to design ahead and ensure that we'd either be ok with this syntax in a general-purpose scenario, or that we're ok with rejecting general purpose sequence expressions as a whole.

## Unresolved questions
[unresolved]: #unresolved-questions

* Should we allow labels/gotos in the body? We need to make sure that any branches out of block bodies clean up the stack appropriately and that labels inside the body are scoped appropriately.

* In a similar vein, should we allow return statements in the block body? The example shown above has these, but there might be unresolved questions around stack spilling, and this will be the first time we would introduce the ability to return from _inside_ an expression.
75 changes: 75 additions & 0 deletions proposals/enhanced-switch-statements.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# Enhanced switch statements

## Summary
[summary]: #summary

This proposal is an enhancement to the existing switch statement syntax to permit switch-expression-style arms instead of using case statements. This feature is orthogonal to https://github.com/dotnet/csharplang/issues/3037 and can be done independently, but was conceived of as taking https://github.com/dotnet/csharplang/issues/3037's switch expressions and applying them to switch statements.

## Motivation
[motivation]: #motivation

This is an attempt to bring more recent C# design around switch expressions into a statement form, that can be used when there is nothing to return. The existing switch statement construct, while very familiar to C/C++ programmers, has several design choices that can feel dated by current C# standards. These include requiring `break;` statements in each case, even though there is no implicit fall-through in C#, and variable scopes that extend across all cases of the switch statement for variables declared _in_ the body, but not for variables declared in patterns in the case labels. We attempt to modernize this by providing an alternate syntax based on the switch expression syntax added in C# 8.0 and improved with the first part of this proposal.

## Detailed design
[design]: #detailed-design

We enhance the grammar of switch statements, creating an alternate form based on the grammar of switch expressions. This alternate form is not compatible with the existing form of switch statements: you must use either the new form or the old form, but not both.

```cs
var o = ...;
switch (o)
{
1 => Console.WriteLine("o is 1"),
string s => Console.WriteLine($"o is string {s}"),
List<string> l => {
Console.WriteLine("o is a list of strings:");
foreach (var s in l)
{
Console.WriteLine($"\t{s}");
}
}
}
```

We make the following changes to the grammar:

```antlr
switch_block
: '{' switch_section* '}'
| '{' switch_statement_arms ','? '}'
;

switch_statement_arms
: switch_statement_arm
| switch_statement_arms ',' switch_statement_arm
;

switch_statement_arm
: pattern case_guard? '=>' statement_expression
| pattern case_guard? '=>' block
;
```

Unlike a switch expression, an enhanced switch statement does not require the switch_statement_arms to have a best common type or to be target-typed. Whether the arm conditions have to be exhaustive like a switch expression is currently an open design question. Block-bodied statement arms are not required to have the end of the block be unreachable, and while a `break;` in the body will exit the switch arm, it is not required at the end of the block like in traditional switch statements.

## Drawbacks
[drawbacks]: #drawbacks

As with any proposals, we will be complicating the language further by doing these proposals. In particular, we will be adding another syntactic form to switch statements that has some very different semantics to existing forms.

## Alternatives
[alternatives]: #alternatives

https://github.com/dotnet/csharplang/issues/2632: The original issue proposed that we allow C# 8.0 switch expressions as `expression_statement`s. We had a few initial problems with this proposal:

* We're uncomfortable making these a top-level statement without the ability to put more than 1 statement in an arm.
* There's some concern that making an infix expression a top-level statement is not very CSharpy.
* Requiring a semicolon at the end of a switch expression in an expression-statement context feels bad like a mistake, but we also don't want to convolute the grammar in such a way as to fix this issue.

## Unresolved questions
[unresolved]: #unresolved-questions

* Should enhanced switch statement arms be an all-or-nothing choice? ie, should you be able to use an old-style `case` label and a new-style arrow arm in the same statement? This proposal takes the opinion that this should be an exclusive choice: you use one or the other. This enables a debate about the second unresolved question for enhanced switch, whether they should be exhaustive. If we decide that enhanced switch should not be exhaustive, then this debate becomes largely a syntactic question.
* An important note about modern switch expression arms is that you cannot have multiple `when` clauses with fallthrough, like you can with traditional switch statements today. If this is an all-or-nothing choice, this means that the moment you need multiple when clauses you fall off the rails and must convert the whole thing back to a traditional switch statement.

* Should enhanced switch be exhaustive? Switch expressions, where enhanced switch statements are inspired from, are exhaustive. However, while the exhaustivity makes sense in an expression context where something must be returned in all cases, this makes less sense in a statement context. Until we get discriminated unions in the language, the only times we can be truly exhaustive in C# is when operating on a closed type heirarchy that includes a catch-all case, or we are operating on a value type. And if the user is required to add a catch-all do nothing case, then purpose of exhaustivity has been largely obviated.
File renamed without changes.
1 change: 0 additions & 1 deletion proposals/low-level-struct-improvements.md

This file was deleted.

File renamed without changes.