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

let statement may break compatibility with a user-defined type named let #10624

Closed
gafter opened this issue Apr 15, 2016 · 20 comments
Closed

let statement may break compatibility with a user-defined type named let #10624

gafter opened this issue Apr 15, 2016 · 20 comments
Assignees
Labels
Area-Language Design Language-C# New Language Feature - Pattern Matching Pattern Matching Tenet-Compatibility Violation of forwards/backwards compatibility in a design-time piece.
Milestone

Comments

@gafter
Copy link
Member

gafter commented Apr 15, 2016

The parser doesn't know whether or not there is a user-defined type named let, so it parses a let statement assuming that no such type exists. As a practical matter than would break existing code that uses let as the name of a type. Do we want to do anything about this?

@gafter gafter self-assigned this Apr 15, 2016
@gafter gafter added this to the 2.0 (RC) milestone Apr 15, 2016
@gafter gafter added the Tenet-Compatibility Violation of forwards/backwards compatibility in a design-time piece. label Apr 15, 2016
@DavidArno
Copy link

DavidArno commented Apr 18, 2016

People would be able to work around this by using @let to refer to the type. So, whilst a breaking change, it's easily fixed. The benefits that let will bring to the language far outweigh the tiny inconvenience this will cause to, at most, a few developers.

@bbarry
Copy link

bbarry commented Apr 18, 2016

It is used in the same position as var, it should behave the same way I think in the presence of a let type in scope.

@DavidArno
Copy link

DavidArno commented Apr 18, 2016

@bbarry,

Good point. The following code is valid and compiles with C# 6, so the C# 7 compiler ought to behave the same when encountering a let type:

public class Foo 
{
    static void Main()
    {
        var x = new var();
    }
}

public class var {}

However, there is a potential problem in handling the following code, which also shouldn't in a "x is readonly" type error:

public class Foo 
{
    static void Main()
    {
        let x = new let();
        x = new let();
    }
}

public class let {}

@gafter
Copy link
Member Author

gafter commented Apr 18, 2016

@DavidArno Re "People would be able to work around this by using @let to refer to the type"

Since they do not already do so, it would be a breaking change to require that.

@bbarry Re "It is used in the same position as var, it should behave the same way I think in the presence of a let type in scope"

The contextual keyword var does not change the way a statement is parsed. But the let statement has a different syntax than a local variable declaration. We don't use semantic information to guide parsing.

@DavidArno
Copy link

DavidArno commented Apr 18, 2016

@gafter,

Since they do not already do so, it would be a breaking change to require that.

And? Surely sometimes the team has to be pragmatic over the "never introduce a breaking change"?

@alrz
Copy link
Contributor

alrz commented Apr 19, 2016

Should these be allowed?

  • let var x = 1;
  • let int[] x = {5};
  • let x = e1, y = e2;
  • let x = e1 when x > 0 else return;
  • let x = e1 when x > 0, y = e2 when y > 0 else return;
  • let pat = e1 when x > 0 let pat = e2 when y > 0 else return;

To make it even more complicated, add #10642.

@gafter
Copy link
Member Author

gafter commented Apr 19, 2016

Should these be allowed?

  • let var x = 1;

Yes, as currently specified.

  • let int[] x = {5};

No, because the right-hand-side isn't a valid expression. Patterns do not "target-type" the expression.

  • let x = e1, y = e2;

No, there is only one pattern-match in the syntax.

  • let x = e1 when x > 0 else return;

Yes.

  • let x = e1 when x > 0, y = e2 when y > 0 else return;

No, there is only one pattern-match in the syntax.

  • let pat = e1 when x > 0 let pat = e2 when y > 0 else return;

No, there is no "let expression". Or I have no idea what that let in the middle means.

To make it even more complicated, add #10642.

These are not declarators. They are patterns.

@alrz
Copy link
Contributor

alrz commented Apr 20, 2016

@gafter

Patterns do not "target-type" the expression.

So I suppose this is not exactly a shortcut for #115, since the semantics are quite different.

let x = e1 when x > 0 else return;

Yes.

According to the latest spec (#10644) it's not. But the following is,

let var x = e1 when x > 0 else return;

Isn't this considered as inconsistent?

If this is intended to work, perhaps let syntax should be merged with local declarations, meaning that the parser would always expect when and else clauses.

there is only one pattern-match in the syntax.

More to the point of this issue, right now let x = e1, y = e2; is legal as long as we have a type named let, right? I don't see why it should not be legal otherwise when we do have a let statement. I'm trying to say that this code is and will be syntactically correct. It'd be unfortunate for it to not work even if we hadlet statements in the language. It would be a good alternative to #5048 specially because let is not a placeholder for a type so allowing something like let x = 1.0, y = 1; would not nullify this fact.

pattern:
let-pattern (instead of var-pattern)
identifier-pattern (has overlap with constant-pattern, see below)
 ...

identifier-pattern:
identifier

property-subpattern:
identifier : pattern

let-pattern: (aka value-binding-pattern)
let pattern

local-variable-declarator:
identifier (only when type is specified)
identifier = array-initializer (only when type is specified)
pattern = expression (pattern must be an identifier if not used with let)
pattern = expression when expression (only when used with let)

declaration-statement:
local-variable-declaration (no semicolon)

local-variable-declaration: (value-binding-pattern rules apply)
local-variable-type local-variable-declarators ; (only a single declarator is allowed with var)
let local-variable-declarators else embedded-statement (only with fallible patterns)

local-variable-type:
type
var
let (not a type per se, but we will check this in pattern-matching context)

Identifier patterns within a value-binding pattern bind new named variables to their matching values.

let (x, y) = e; // introduces variables `x` and `y`

Value-binding patterns cannot be nested.

let (let x, let y) = e; // illegal 

In pattern-matching context, let will be resolved as a keyword even though a type with the same name exists.

@DavidArno
Copy link

So I suppose this is not exactly a shortcut for #115, since the semantics are quite different.

I'm really confused by this. According to the spec:

let_statement
: 'let' identifier '=' expression ';'
...
is shorthand for

let var identifier = expression ;`

(i.e. a var_pattern) and is a convenient way for declaring a read-only local variable.

This appears to be exactly what #115 is asking for (WRT read-only locals; it won't cover parameters). Am I missing something?

@alrz
Copy link
Contributor

alrz commented Apr 20, 2016

@DavidArno Yes, but still you are matching the expression against a pattern (in this case, a var pattern) not a simple assignment, so it makes sense to don't "target-type" here. However, all bound variables will be read-only, hence the sentence "a convenient way for declaring a read-only local variable."

@DavidArno
Copy link

@alrz,

Ah, that makes sense. The fact that the vars coming out of patterns are always read-only is something I'd missed. So that means that the following would be a compilation error?

var t = (1,1);
switch (t)
{
    case (var x, 1): 
        x = 2; // Cannot redefine `x`
...

@HaloFour
Copy link

@DavidArno It's a little buried in the spec, but:

The use of a pattern variables is a value, not a variable. In other words pattern variables are read-only.

@timgoodman
Copy link

I would think user-defined types named "let" would be extremely rare, given that "let" is both lowercase and a verb. It's great that you take breaking changes so seriously, but in this case I think it's worth it. I really like the proposed use of the let keyword.

@Thaina
Copy link

Thaina commented Apr 27, 2016

I am very aware of this problem and go against the idea of let keyword from the start

And we should reuse keyword const or readonly instead of let

Because it is already keyword so it backward compatible with old code

const x = new let() will not breaking any existing code

@HaloFour
Copy link

@Thaina

If the let statement was only used to declare readonly locals then you might have a point. But if that were the case there would also be no parsing concerns as let would be treated as a contextual keyword, just like var. The parsing concerns are with using the let statement to decompose a pattern, and neither const nor readonly keywords make any amount of sense to be used in that context.

@DavidArno
Copy link

@Thaina,

To clarify @HaloFour's point, the following is handled just fine by the compiler:

class var {}
...
var x = 1;
var y = new var();

The compiler can determine that var is being used instead of int, in the first case and is referring to the type in the second. If let were merely a read-only replacement for var, then the following would be fine too:

class let {}
...
let x = 1;
let y = new let();

The issue is that let isn't proposed as a simple readonly variable declaration keyword. It's much more powerful as it allows pattern expression evaluation, for example:

let (x, 1) = F(z) else return;
// z is now a read-only value returned in a tuple from F, 
// only if the other value returned was 1.

I'm not sure if I have the exact syntax right in the above, but it gives you the idea.

So we have a choice:
1, Offer up a powerful means of assigning a value if an expression matches and take another action if not.
2. Protect folk who ignored two language guidelines (don't use lowercase letters for the first character of class names and don't use verbs for class names) from the trivial inconvenience of adding @ to their let class references in order to up and compiling once more.

Opting for the second option strikes me as taking the "no breaking changes" rule to absurd extremes.

@HaloFour
Copy link

@gafter Couldn't the parser take additional tokens into consideration when parsing the let statement? I would think that the only time that it would matter if let were declared as a type is when there is no pattern on the left-hand side of the assignment.

class let { }

void Foo() {
    let x = 123; // CS0029 Cannot implicitly convert type 'int' to 'let'
    let var x = 123; // fine
    let int x = 123; // fine
    let (int x, int y) = (123, 456); // fine
    object o = x;
    let int x = o else return; // fine
}

@DavidArno
Copy link

@HaloFour,

That makes sense as a work-around, just so long as let var is only mandatory when there is a let class in scope. Otherwise you would be inconveniencing 99.9% of us with extra syntax just to avoid inconveniencing the 0.1% with easy-fixed, broken code.

@HaloFour
Copy link

@DavidArno Exactly, it would only be necessary in that virtually non-existent case. To most people let x = 123; would be perfectly fine. That is assuming the parser can use the subsequent tokens to make the determination as to what kind of statement it is.

I'd feel bad for anyone working on a project where both var and let are defined as types. I've seen the former used by devs who hate the idea of inferred locals.

@DavidArno
Copy link

@HaloFour,

I'd feel bad for anyone working on a project where both var and let are defined as types. I've seen the former used by devs who hate the idea of inferred locals.

It makes sense that there is an opposite extreme case to my own. I configure Resharper to report all uses of explicit types, that could be replaced with var, as an error... 😀

But even in that case, your let var x = 123; example could work as the x distinguishes it from other let var and var let cases.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Area-Language Design Language-C# New Language Feature - Pattern Matching Pattern Matching Tenet-Compatibility Violation of forwards/backwards compatibility in a design-time piece.
Projects
None yet
Development

No branches or pull requests

7 participants