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

Disallow comma operator with side-effect-free left operands #10814

Closed
RyanCavanaugh opened this issue Sep 9, 2016 · 10 comments
Closed

Disallow comma operator with side-effect-free left operands #10814

RyanCavanaugh opened this issue Sep 9, 2016 · 10 comments
Assignees
Labels
Breaking Change Would introduce errors in existing code Fixed A PR has been merged for this issue Suggestion An idea for TypeScript

Comments

@RyanCavanaugh
Copy link
Member

It's easy to write code like this (#10802)

// This code does not do what it appears to!
let arr = [];
switch(arr.length) {
  case 0, 1:
    return 'zero or one';
  default:
    return 'more than one';
}

Or this

let x = Math.pow((3, 5)); // x = NaN, wat?

Or this:

let a = [(3 + 4), ((1 + 1, 8) * 4)]; // oops

We should disallow left comma operands when they are side-effect free. An expression is side-effect free (SEF) if it is:

  • A literal
  • An identifier
  • A unary operator whose operand is SEF
  • A binary operator, other than the assignment operators, whose operands are both SEF
  • A ternary operator whose operands are all SEF
  • A parenthesized expression which is SEF
  • A function expression
@RyanCavanaugh RyanCavanaugh added Suggestion An idea for TypeScript In Discussion Not yet reached consensus labels Sep 9, 2016
@yortus
Copy link
Contributor

yortus commented Sep 10, 2016

Tweaking what is/isn't SEF:

  • A class expression is also SEF
  • An identifier may not be SEF if it's a getter defined on the global or window object.
  • ++ and -- unary operators obviously aren't SEF

@RyanCavanaugh
Copy link
Member Author

RyanCavanaugh commented Sep 12, 2016

Thinking about this more, I think we should define SEF to be slightly more prescriptive than an actual runtime description. For example, an array-literal is known to be SEF if its elements are SEF, but we still don't want to allow this clearly-wrong (or at least clearly-wat) code:

let a = ([x++], 4);

Same for

let b = (x * ++y, 4);

Basically, if an expression's top-level form isn't one with potential side effects, we should error, because even if there's a nested non-SEF expression, the parent expression is doing something that doesn't go anywhere, which doesn't pass the smell test.

@yortus
Copy link
Contributor

yortus commented Sep 14, 2016

@RyanCavanaugh aren't these two separate issues? One is the OP issue with comma expressions, the other is about expressions whose top-level result is not used.

If TypeScript disallowed let b = (x * ++y, 4); on the basis that the result of x * ++y is not used, then perhaps it should also disallow x * ++y; as an ExpressionStatement.

(I'm assuming the guiding principle here is "statically identify constructs that are likely to be errors.")

@yortus
Copy link
Contributor

yortus commented Sep 14, 2016

After looking at the PR, it seems like it's just pragmatic to only check the top-level is SEF since it simplifies SEF checking of comma expressions, has the desired effect, and may catch a few additional programmer errors as a bonus.

I'd still argue for performing similar SEF checks on ExpressionStatements for consistency sake. I think you'd catch more 'constructs likely to be errors' of this kind in ExpressionStatetments than in rarely-used comma expressions. Of course "use strict"; would have to be allowed still.

@RyanCavanaugh
Copy link
Member Author

I agree in principle that x * ++y; should be disallowed as well. A "conflict of interest" is that we use those kinds of expression statements everywhere in our testbed to gather their types for testing purposes and it'll be a lot more work to fix all those.

The comma operator specifically was just really compelling because of the "looks like it works but really doesn't" aspect of case labels as well as messing up paren balancing in function calls / array literals. Accidently making a SEF ExpressionStatement seems to be slightly harder but I could be convinced otherwise.

@yortus
Copy link
Contributor

yortus commented Sep 14, 2016

Right, it's really convenient to use naked SEF ExpressionStatements to check type inference/narrowing in test code and code under development. It would be very annoying if all of these expressions were compiler errors.

Your PR uses the allowUnreachableCode flag to suppress SEF checks, would that work for ExpressionStatements as well?

@yortus
Copy link
Contributor

yortus commented Sep 14, 2016

One example of a SEF ExpressionStatement I have come across is a (possibly very long) IIFE that doesn't actually get executed. Something like this:

(function () {

    /* IIFE code... */

}) // oops forgot to call it

@mhegazy mhegazy added this to the TypeScript 2.1 milestone Sep 14, 2016
@mhegazy mhegazy added Fixed A PR has been merged for this issue and removed In Discussion Not yet reached consensus labels Sep 14, 2016
@RyanCavanaugh
Copy link
Member Author

RyanCavanaugh commented Sep 14, 2016

@mhegazy just showed me a great hit from our partner suite 😆

   if (...) {
      throw ("Unexpected thing {0}, expected {1}", actual, expected);
   }

@RReverser
Copy link
Contributor

Could this be allowed for things like (0, object.func)()? It's a common pattern to call function with no this context without extracting it into a temporary variable.

@RReverser
Copy link
Contributor

Just saw #12978 (comment), guess that's fine as a workaround.

@microsoft microsoft locked and limited conversation to collaborators Jun 19, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
Breaking Change Would introduce errors in existing code Fixed A PR has been merged for this issue Suggestion An idea for TypeScript
Projects
None yet
Development

No branches or pull requests

4 participants