Skip to content

Double declarations - #2279

Merged
lukastaegert merged 5 commits into
masterfrom
double-declarations
Jun 20, 2018
Merged

Double declarations#2279
lukastaegert merged 5 commits into
masterfrom
double-declarations

Conversation

@lukastaegert

Copy link
Copy Markdown
Member

Resolves #2275.

When a variable has more than one declaration, the reassignment tracking logic could trigger a bug as it was engaged before the inter module bindings were set up.

While fixing this, some other issues popped up that are fixed as well:

  • When a variable has more than one declaration, reassignments were not tracked properly for all initialisers
  • Reassignments of pattern defaults were not tracked properly
  • Reassignments of elements of array literals were not tracked properly. For this one, a very simple logic was implemented that just marks all array elements as "reassigned". In the future, a more elaborate logic could be implemented similar to the object expression logic which would allow values to be tracked across tuples.

All of these issues are now properly tested and fixed.

@guybedford guybedford left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks for the speedy fix. A few comments, mostly questions as usual.

!(<LocalVariable>this.variable).bound
) {
(<LocalVariable>this.variable).bind();
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I take it this is the fix? Is there no way to ensure a strong separation of the binding phase? How is this timing issue occurring in the first place?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Unfortunately at least at the moment, I do not know if a separation will be possible at all. Here is an outline of the issue:

The issue

Before the actual tree-shaking, we pass through three phases:

  • the initialization or construction phase in which rollup's AST nodes are constructed, the scopes are created and the declaration nodes register themselves on the scope and create the corresponding local variables.
  • the link phase where the inter-module links are established
  • the bind phase during which the remaining identifiers are associated with the declarations and all reassignments are collected.

The bind phase relies on all links being in place and the variables having their proper initializers set. This is the reason why before this PR, double declarations were normalized during construction by setting the "init" to "unknown".

However as this means we are losing track of the initializers, it is important we mark all their paths as "reassigned" so that their usages are never dropped in case they are mutated somewhere.

Reassigning paths, however, is not always possible during initialization in case of imported variables which was the reason for this issue.

Why do we need to track the "bound" state of variables?

When using functions, it is easy to access a variable the definition of which is further down in the file. As the binding phase just works from top to bottom, we can therefore not rely on the declarations of the variable being consolidated before any of the functions that can be used during bind access them (those being reassignPath, getReturnExpressionWhenCalledAtPath and getLiteralValueAtPath, all of which can pass through several variables to do their work).

A separate phase between link and bind would also not solve the issue entirely as it is still possible that the necessary reassignments would reach a variable that is not bound yet.

Another approach could be to not consolidate declarations at all. But this would make all of LocalVariable more complicated and definitely slow down tree-shaking while I think the optimization benefit would be small due to the fact that double declarations are rather rare. I'll think about how this can be made a little less convoluted.

Comment thread src/ast/variables/LocalVariable.ts Outdated
super(name);
this.declarations = declarator ? [declarator] : [];
this.init = init;
this.initialisers = [init];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I love how this project is committed to being rebelliously European :)

I hate to be difficult, but surely we shouldn't need to track all initializers here, since initializer precedence can always apply (#2278)? Or do we specifically need to apply reassignPath(UNKNOWN_PATH) to each initializer? Can you perhaps help clarify why tracking this information on the variable is not enough?

Alternatively could this.init be replaced to always reference this.initialisers[0]? I just hate to see the redundancy.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Without TDZ/flow analysis, we do not know which definition applies where. Thus the approach is to make the variable "unknown". As we can no longer track initializer references in such a situation, all initializers need the reassignPath(UNKNOWN_PATH).
I could however simplify this to get rid of the redundancy.

Comment thread src/ast/variables/LocalVariable.ts Outdated
path: ObjectPath,
recursionTracker: ImmutableEntityPathTracker
): LiteralValueOrUnknown {
if (!this.bound) this.bind();

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'd prefer to fix root cause if we can... this feels a bit like trying to plug a leak.

@guybedford

Copy link
Copy Markdown
Contributor

Another thing - surely if the initialisers array here solves the double declaration issue allowing the variable to be properly defined, why is it still necessary to the specific binding checks?

@lukastaegert

Copy link
Copy Markdown
Member Author

We could also track this as an either-or case but I feel this would make everything slower just for an edge case which is usually a bug.

@lukastaegert

Copy link
Copy Markdown
Member Author

Actually found a way to get rid of the bound variable by separating the collection of initializers and setting the init value during construction and only doing the reassignments during bind (in which case order is no longer important).

@lukastaegert

Copy link
Copy Markdown
Member Author

Ready for another review.

@guybedford guybedford left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Just the one question but assume the answer is as expected, LGTM.

}
this.init = UNKNOWN_EXPRESSION;
this.additionalInitializers = null;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Can we not apply initializer.reassignPath in addDeclaration? Or is this the whole issue with the .bound checks?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes, this was actually the initial approach that was the root cause of #2275. addDeclaration needs to be called during the initialization phase so that after that we can rely on all variables being known. reassignPath, however, cannot be called before the inter-module bindings have been established which can only happen after that phase.

@lukastaegert lukastaegert added this to the 0.61.0 milestone Jun 20, 2018
@lukastaegert
lukastaegert force-pushed the double-declarations branch from c69bd68 to 8cacd62 Compare June 20, 2018 15:01
@lukastaegert
lukastaegert merged commit 8cacd62 into master Jun 20, 2018
@lukastaegert
lukastaegert deleted the double-declarations branch June 20, 2018 15:33
calebeby referenced this pull request in Pigmice2733/scouting-frontend Jun 21, 2018
This Pull Request updates dependency [rollup](https://github.com/rollup/rollup) from `v0.60.7` to `v0.61.1`



<details>
<summary>Release Notes</summary>

### [`v0.61.1`](https://github.com/rollup/rollup/blob/master/CHANGELOG.md#&#8203;0611)
[Compare Source](rollup/rollup@v0.61.0...697f36d)
*2018-06-21*
* Do not try to deconflict "undefined" ([#&#8203;2291](`https://github.com/rollup/rollup/pull/2291`))
* Properly track values for loop interator declarations and reassigned namespaces, add smoke test ([#&#8203;2292](`https://github.com/rollup/rollup/pull/2292`))

---

### [`v0.61.0`](https://github.com/rollup/rollup/blob/master/CHANGELOG.md#&#8203;0610)
[Compare Source](rollup/rollup@v0.60.7...v0.61.0)
*2018-06-20*
* Declare file dependencies via transform plugin hooks ([#&#8203;2259](`https://github.com/rollup/rollup/pull/2259`))
* Handle undefined values when evaluating conditionals ([#&#8203;2264](`https://github.com/rollup/rollup/pull/2264`))
* Handle known undefined properties when evaluating conditionals ([#&#8203;2265](`https://github.com/rollup/rollup/pull/2265`))
* Access watch events via the plugin context ([#&#8203;2261](`https://github.com/rollup/rollup/pull/2261`))
* Add option to suppress `__esModule` flag in output ([#&#8203;2287](`https://github.com/rollup/rollup/pull/2287`))
* Fix issue when re-declaring variables, track reassignments in more cases ([#&#8203;2279](`https://github.com/rollup/rollup/pull/2279`))
* Add VSCode debug settings ([#&#8203;2276](`https://github.com/rollup/rollup/pull/2276`))

---

</details>




---

This PR has been generated by [Renovate Bot](https://renovatebot.com).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TypeError: Cannot read property 'isExternal' of null

2 participants