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

[CS2] Fix handling of parameters that are complex #4430

Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ed6d9c6
Add failing test per #4406
GeoffreyBooth Jan 21, 2017
5f5c039
If a parameter is a function call, define it in an expression within …
GeoffreyBooth Jan 21, 2017
a3f8091
Remove the space between `function` and `*` for generator functions, …
GeoffreyBooth Jan 21, 2017
658f503
We can collapse `isCall` into `isComplex`
GeoffreyBooth Jan 21, 2017
baac18e
Merge branch '2' of github.com:jashkenas/coffeescript into destructur…
GeoffreyBooth Jan 21, 2017
42a2039
Don’t need existence check here
GeoffreyBooth Jan 21, 2017
dbd8274
Merge branch '2' of github.com:jashkenas/coffeescript into destructur…
GeoffreyBooth Jan 24, 2017
c4b6d27
Correct destructured parameter default evaluation order with an incre…
GeoffreyBooth Jan 24, 2017
8fe3242
Try to pull complex parameters out of the parameter list if their ord…
GeoffreyBooth Jan 24, 2017
f96ce05
Add lots of comments about node special properties
GeoffreyBooth Jan 24, 2017
6e2e254
Follow the ES and CS2 convention of assigning parameter default value…
GeoffreyBooth Jan 25, 2017
67ff10c
Err on the side of caution in deciding whether a complex parameter is…
GeoffreyBooth Jan 25, 2017
afd66d6
Along with arrays and empty objects, also let values whose bases are …
GeoffreyBooth Jan 25, 2017
e64bb8e
Merge branch '2' into destructured-parameter-evaluation-order
GeoffreyBooth Jan 25, 2017
ce9e04d
Better way to check for undefined parameters when declaring them in a…
GeoffreyBooth Jan 26, 2017
c581e9e
Once we’ve put a complex parameter in the function body, all followin…
GeoffreyBooth Jan 26, 2017
7867cd5
Rename `isComplex` to `shouldCache` for clarity
GeoffreyBooth Jan 27, 2017
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
65 changes: 54 additions & 11 deletions lib/coffee-script/nodes.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

88 changes: 74 additions & 14 deletions src/nodes.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -220,17 +220,39 @@ exports.Base = class Base

# Default implementations of the common node properties and methods. Nodes
# will override these with custom logic, if needed.

# `children` are the properties to recurse into when tree walking. The
# `children` list *is* the structure of the AST. The `parent` pointer, and
# the pointer to the `children` are how you can traverse the tree.
children: []

isStatement : NO
jumps : NO
isComplex : YES
isChainable : NO
isAssignable : NO
isNumber : NO
# `isStatement` has to do with “everything is an expression”. A few things
# can’t be expressions, such as `break`. Things that `isStatement` returns
# `true` for are things that can’t be used as expressions. There are some
# error messages that come from `nodes.coffee` due to statements ending up
# in expression position.
isStatement: NO

# `jumps` tells you if an expression, or an internal part of an expression
# has a flow control construct (like `break`, or `continue`, or `return`,
# or `throw`) that jumps out of the normal flow of control and can’t be
# used as a value. This is important because things like this make no sense;
# we have to disallow them.
jumps: NO

# If `node.isComplex() is false`, it is safe to use `node` more than once.
# Otherwise you need to store the value of `node` in a variable and output
# that variable several times instead. Kind of like this: `5` is not complex.
# `returnFive()` is complex. It’s all about “do we need to cache this
# expression?”
isComplex: YES

unwrap : THIS
unfoldSoak : NO
isChainable: NO
isAssignable: NO
isNumber: NO

unwrap: THIS
unfoldSoak: NO

# Is this node used to assign a certain variable?
assigns: NO
Expand Down Expand Up @@ -1946,12 +1968,18 @@ exports.Code = class Code extends Base
# the function definition.
else
if param.isComplex()
# This parameter is destructured. So add a statement to the function
# body assigning it, e.g. `(arg) => { var a = arg.a; }` or with a
# default value if it has one.
# This parameter cannot be declared or assigned in the parameter
# list. So add a statement to the function body assigning it, e.g.
# `(arg) => { var a = arg.a; }` or with a default value if it has
# one.
val = ref = param.asReference o
val = new Op '?', ref, param.value if param.value
exprs.push new Assign new Value(param.name), val, '=', param: yes
if param.value?
condition = new Literal param.name.value + ' === undefined'
Copy link
Collaborator

Choose a reason for hiding this comment

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

$ coffee -bpe 'undefined'
void 0;

Should we replace undefined with void 0 for consistency?

Copy link
Collaborator

Choose a reason for hiding this comment

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

This could be (roughly) condition = new Op '==', param, new UndefinedLiteral, and would then remain consistent.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Why are we using void 0 in the first place? Because it’s a few characters shorter? Because apparently many years ago it was possible to redefine undefined? SO.

I would think that undefined is more readable, and since one of our goals is to produce human-readable code, we should replace all void 0s with undefined.

ifTrue = new Assign new Value(param.name), param.value, '=', param: yes
exprs.push new If condition, ifTrue
else
val = new Op '?', ref, param.value if param.value
exprs.push new Assign new Value(param.name), val, '=', param: yes

# If this parameter comes before the splat or expansion, it will go
# in the function definition parameter list.
Expand Down Expand Up @@ -2112,7 +2140,39 @@ exports.Param = class Param extends Base
@reference = node

isComplex: ->
@name.isComplex() or @value instanceof Call
# Should this parameter be declared in the function body, rather than in
# the parameter list? We don’t want to pull out any parameter that
# has a value, or else we break default values; but we want things like
# generator functions and iterators to evaluate in the correct order,
# which they might not do unless we pull them out into the function body.
if @name.isComplex()
yes
else if @value? and @value.isComplex()
if @value instanceof Value
unless @value.base.isComplex()
no
else
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd either flip this unless…else around or change it into if not…else.

# Objects and arrays are always complex, but unless they contain any
# complex children, they’re safe to leave in the parameter list.
if @value.base instanceof Arr or @value.base instanceof Obj
hasComplexChild = no
@value.base.traverseChildren no, (node) ->
if node.isComplex()
# This can be further refined. An empty object will evaluate as
# not having any complex children, but as long as it has at least
# one property it will be considered complex.
Copy link
Collaborator

Choose a reason for hiding this comment

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

Could you give examples on ...

  • things that this code does not consider complex
  • things that this code does consider complex, but could be improved no to in the future?

hasComplexChild = yes
no # Stop traversing.
hasComplexChild
else
yes
else
# Other types of `@value`s in the CoffeeScript codebase:
# `Call`, `Class`, `SuperCall`, `Op`.
# All of these should be declared and assigned in the function body.
yes
else
no

# Iterates the name or names of a `Param`.
# In a sense, a destructured parameter represents multiple JS parameters. This
Expand Down
7 changes: 6 additions & 1 deletion test/functions.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,12 @@ test "#1038 Optimize trailing return statements", ->
return
""")

test "#4406 Destructured parameter default evaluation order", ->
test "#4406 Destructured parameter default evaluation order with incrementing variable", ->
i = 0
f = ({ a = ++i }, b = ++i) -> [a, b]
arrayEq f({}), [1, 2]

test "#4406 Destructured parameter default evaluation order with generator function", ->
current = 0
next = -> ++current
foo = ({ a = next() }, b = next()) -> [ a, b ]
Expand Down