-
Notifications
You must be signed in to change notification settings - Fork 4k
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
Proposed changes for deconstruction, declaration expressions, and discards #14794
Comments
There is an interesting syntactic ambiguity arising from declaration expressions. Consider the code (A < B > C, X) = e; Here the "tuple expression" on the left has a declaration for a variable e = (A < B > C, X); has a first subexpression equivalent to |
Similarly, consider the code (A < B, D > C, X) = e; Here the "tuple expression" on the left has a declaration for a variable e = (A < B, D > C, X); has a first subexpression equivalent to |
Another advantage of adding support for wildcards today rather than later is that we can warn for unused pattern and out variables. |
While I understand the ambiguity issues of |
@gafter Can you show hte changes you intend to make to the foreach-syntax model? Thanks! |
Actually, n/m. I can see you describe it as:
|
@HaloFour You can't accidentally do anything. because basically any mistake is a compile-time error. And more to the point of this thread, I can understand how it's easier to represent in AST because it would be merely a semantical change. In fact, just because it's an identifier, it allows you to use |
@gafter Regarding declaration expression ambiguities, I think it's better to require them to always be initialized. Any use case involving an uninitialized declaration expression can use a pattern instead. |
That was our intent. Declaration expressions can appear in an |
Sure you can. It would be impossible for the compiler to tell if you intend to deconstruct into a local variable or field in scope that happens to be named int _ = 123;
var pt = new Point(0, 456);
int y = 789;
// later
(_, y) = pt; // oops, what did I really intend to do here? Is this necessarily a common scenario? Probably not. But as a legal identifier it could never be ruled out. |
for pt as 2D point |
Considering that parameters of deconstructor methods are always |
@HaloFour |
I'm not entirely sure what that has to do with this discussion. Either the compiler will find a suitable deconstructor method and |
@HaloFour |
I'm quite sure that this is not the case. If you wanted to deconstruct (x, _) = pt; // illegal
(x, _, _) = pt; // legal
(_, y, _) = pt; // legal
(_, _, z) = pt; // legal |
@HaloFour |
@gafter I'm saying that in all those places, instead of a declaration expression we use a pattern so that the following would be possible, F(out {X:var x});
foreach(let {Result:var result} in tasks) Or something like that. Perhaps, only complete patterns would be allowed in these contexts. @HaloFour This is also applicable to |
@HaloFour The easiest way out is to forbid (or warn against) tuple deconstruction, out var and pattern matching into variables named |
The difference would be much more subtle. The declaration of That's a pretty reasonable suggestion but how much it limit where wildcards could be used within the language? For example there are proposals to allow wildcards to ignore lambda arguments as well as to declare ignored variables. In both of those cases it's already perfectly legal today to use What I don't get is where the ambiguity lies with M(out int *) // wildcard out declaration
M(out int * x) // pointer out declaration
if (o is int *) // illegal in C# 1.0 - 6.0, wildcard in C# 7.0. Is it because that last case would make it seem like |
Another alternative is var (x, ...) = e;
(int x, int ...) = e;
M(out int ...);
switch (o)
{
case int ...:
case long ...:
Console.WriteLine("int or long");
break;
} |
This work is being placed into the branch https:/dotnet/roslyn/tree/features/wildcard during development. |
@HaloFour Thank you for explaining this. |
@gafter The link to the wildcard branch is incorrect. It should be: https:/dotnet/roslyn/tree/features/wildcard |
…ds. (#15548) * Combine deconstruction assignment and declaration, and support discards. - Combine deconstruction assignment and declaration, and support discards. - Add wildcards.work.md to track outstanding work. - Bind each type syntax once in a deconstruction. - Because tuples may contain declarations, adjust lambda disambiguation and adjust parsing of argument lists. - Diagnose tuple element names on the left of a deconstruction. - Add relational operators to disambiguating tokens in 7.5.4.2 * Disallow deconstruction declarations except at the statement level. This is now a semantic restriction (until we decide to remove it). * Revise logic to detect `var` in a declaration expression. Plus other changes per code review. * Add a test that GetTypeInfo on a discard expression doesn't crash. * Small changes per code review. * Add (skipped) test for var invocation in parens. * Rename "Discarded" to "Discard" * Changes recommended via code review. * Minor changes to the handling of declaration expressions per code review. * Addressing blocking feedback while Neal is OOF Fixes #14794 Fixes #14832
This work has been completed; closing. |
Issue moved to dotnet/csharplang #365 via ZenHub |
This week's LDM meetings (2016-10-25 and 2016-10-26) proposed some possible changes to the handling of deconstruction, declaration expressions, and wildcards that, even if done later, would affect the shape of compiler APIs today. That suggests we would want to consider what to do today so that we are compatible with that possible future. This is a summary of the proposed changes and their API impact.
Wildcards
The LDM is proposing to change the character that we would use to represent a "wildcard" from
*
to_
. We also considered some alternatives, but both*
and?
have a logical (if not technical) ambiguity, becauseint?
andint*
are valid types._
doesn't likely have the same kind of syntactic ambiguity, because it is already an identifier in all of the places where we want to support wildcards. But it may be a semantic ambiguity for that same reason. The reason we like_
is that users already introduce variables, for example parameters, named_
when their intention is to ignore them.We want to support both the "short form"
_
and the "long form"var _
just in case there is an ambiguity with, for example, a field in scope. We'll return to this later.Declaration Expressions
We currently represent an out variable declaration using a "declaration expression" node in the syntax model. That was done because we were thinking that we may want to generalize declaration expressions in the future.
There is some slight discomfort with declaration expressions as they appear in the current API and proposed language spec, because while they are expressions in the syntax model, they are not expressions in the draft spec, and do not have types, and therefore there are special rules called out for them wherever they may appear in the specification. Besides the mismatch between the spec and model, we expect we may want to allow declaration expressions in more contexts in the future, in which case we will want them to be treated as expressions. In that case we will be better served by treating them as expressions (i.e. they can have a type) today.
Deconstruction
Possibly generalizing declaration expressions in the future makes us want to reconsider our syntax model for deconstruction today. For example, given the statement
We can think of this as a special deconstruction declaration statement (as it is today), or alternatively we can think of it as a statement expressions containing an assignment expression with a tuple expression on the left-hand-side. In that case the tuple expression contains two declaration expressions. The latter formulation makes more sense in the possible future in which we generalize declaration expressions.
Similarly, given the statement
We can think of this as a special deconstruction declaration statement (as it is today), or alternatively we can think of it as a statement expressions containing an assignment expression with a declaration expression on the left-hand-side. The latter formulation makes more sense in the possible future in which we generalize declaration expressions.
This reformulation of deconstruction allows us to remove from the syntax model the new statement form for a deconstruction declaration. It also allows us to generalize what we allow in the future:
Here, the left-hand-side contains a mixture of already-existing variables (in this case
x
) and newly declared variables (int y
). And it can be used in an expression context as well (e.g. as the body of an expression-bodied method).Wildcards (revisited)
Given this new understanding of the direction of the syntax, there are four forms that wildcards can take. First, it can take the place of an identifier in a designator (i.e. in a declaration expression):
Since
_
is already an identifier, no syntax model change is required. However, semantically we want this to create an anonymous variable, and shadow any true variable (e.g. parameter or field) from an enclosing scope named_
. There is no name conflict error if wildcards are declared this way more than once in a scope.Second, it can similarly be used to declare a pattern variable:
Third, it can take the place of an identifier in a simple expression where an lvalue is expected and is used as a target in a deconstruction assignment or out parameter, but in that case its special behavior as a wildcard only occurs if looking up
_
doesn't find a variable of that nameThis special name lookup is similar to the way we handle
var
.Finally, it can be used where a parameter can be declared today. However, we relax the single-definition rule to allow multiple conflicting declarations (same scope or nested scopes), in which case the identifier
_
binds as a wildcard.We have to be careful with these changes so that any program that uses
_
as an identifier and is legal today continues to compile with the same meaning under these revised rules.Syntax model changes
This allows us to simplify the handling of the
for
loop to handle deconstruction. Now the deconstruction is just one of the expressions in the expression list of the initializer part, and doesn't require its own placeholder in the syntax. That means that the syntax node for thefor
loop remains unchanged from the C# 6 version.This requires a change to the way we handle the deconstruction form of the
foreach
loop. Because we want the left-hand-side to be capable of representing all of these formswe now need to use expression for the syntax node before the
in
keyword.We can remove the syntax node for the deconstruction declaration statement, because that is just an assignment statement in this model.
Syntax.xml changes
The following changes are proposed compared to the current implementation in master. We remove
and we remove the
Deconstruction
field from theForStatementSyntax
We change the
VariableComponent
field ofForEachComponentStatementSyntax
to be anExpressionSyntax
, and probably change the name ofForEachComponentStatementSyntax
.And we change
to
We leave unchanged
SemanticModel changes
We may want to change the behavior of
GetTypeInfo
on a declaration expression, depending on how the shape of the specification evolves.We probably need to consider what the behavior of
SemanticModel
APIs should be on wildcards.Summary
The changes to declaration expressions and deconstruction should be done today so that we don't have an incompatible change later.
Wildcards are an interesting problem. Even if we don't want to implement them for C# 7, we want to wall off the semantic space so that valid C# 7 programs don't change meaning or become invalid in a later language version. I suspect the simplest way to do that is to implement wildcards today.
/cc @dotnet/ldm @dotnet/roslyn-compiler
The text was updated successfully, but these errors were encountered: