Skip to content

Better JS container binding#24367

Merged
sandersn merged 28 commits into
masterfrom
js/better-initializer-binding
May 31, 2018
Merged

Better JS container binding#24367
sandersn merged 28 commits into
masterfrom
js/better-initializer-binding

Conversation

@sandersn
Copy link
Copy Markdown
Member

@sandersn sandersn commented May 23, 2018

This PR improves the binding of JS containers. It no longer binds function expressions, class expressions, object literals or IIFEs as declarations. Instead, the actual declaration is the one that merges with subsequent special assignment.

For example,

var C = function() {
  this.x = 1
}
C.prototype.y = 3
C.z = 2

Previously put x and y on the members of the function expression and z on its exports. Now, it puts x on the members of the function expression (since it is declared there), y on the members of C and z on the exports of C. When the checker requests the type of the function expression, getTypeOfFuncClassEnumModule checks whether the declaration (C in this case) has any special exports or members and merges them if so. The resulting type is the type of the function expression as well as C.

This has three advantages:

  1. Better type printing in many cases.
  2. Simpler code in bindPropertyAssignment.
  3. Simpler code in mergeSymbol.

Both these pieces of code were previously bug farms.

Also, since the checker now produces the combined types instead of the binder, IIFE type checking is better; the returned type of the IIFE is intersected with an anonymous type containing exports produced by special JS assignments.

Fixes #24110
Fixes #24111

sandersn added 21 commits May 17, 2018 10:02
Also update SymbolLinks in getTypeOfFuncClassEnumModule so that the
type gets cached correctly.
Also include a couple of improved baselines
The utility is horrible and needs to change, but at least it's in one
place.

Next step is to make the utility like getDeclarationOfAlias, except
getDeclarationOfJSAlias.
Merging even seems to work ok.
Perhaps in the wrong way. I have an idea how to simplify them.
1. It's not completely removed; the checker code in
getJavascriptClassType needs to be fixed, among other places.
2. I didn't actually remove the code so that it will be easier to see
what used to be there on Monday.

Regardless, the code will be much simpler and seems to be mostly
improved with very little work so far.
1. Remove a few TODOs
2. Remove extraneous SymbolFlag
3. Simplify isSameDefaultedName
@sandersn sandersn requested a review from mhegazy May 23, 2018 23:26
@sandersn
Copy link
Copy Markdown
Member Author

Running Ryan's fuzzer in its original editor-mode configuration doesn't show any oomemory or crashes for over 1,000,000 iterations. I'll run it in get-diagnostics-mode configuration once I change the checker to handle more kinds of Javascript merges in my next PR. (Right now our pre-existing lack of handling crazy merges causes us to hit asserts in various branches of getTypeOfSymbol.)

Comment thread src/compiler/checker.ts Outdated
const jsDeclaration = getDeclarationOfJSInitializer(symbol.valueDeclaration);
if (jsDeclaration) {
const jsSymbol = getMergedSymbol(jsDeclaration.symbol);
if (jsSymbol && (jsSymbol.exports && jsSymbol.exports.size || jsSymbol.members && jsSymbol.members.size)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

If jsSymbol === symbol (somehow) does this work need to be done?

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.

No, and it would be really bad because you would be merging symbols with themselves. But it would hit the new assert in mergeSymbol, so it doesn’t happen in our tests at least.

But you make a valid point; getDeclarationOfJSInitializer should (1) check that its node is in a js file (2) check that the provided value declaration is the initializer of the js declaration that it returns.

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.

Fixed. (As is the call to getSymbolOfNode.)

Comment thread src/compiler/checker.ts Outdated
if (!links.type) {
const jsDeclaration = getDeclarationOfJSInitializer(symbol.valueDeclaration);
if (jsDeclaration) {
const jsSymbol = getMergedSymbol(jsDeclaration.symbol);
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.

This should just be a call to getsymbolofnode I think.

sandersn added 4 commits May 25, 2018 13:00
Change getDeclarationOfJSInitializer to require that the provided js
initializer be in a javascript file, and that it is the initializer of
the retrieved declaration.
These are cases in a really embarrassing check, in which we admit that
the symbol flags steered us wrong and switch to
getTypeOfFuncClassEnumModule instead (which never asserts).
@sandersn
Copy link
Copy Markdown
Member Author

sandersn commented May 25, 2018

I ran Ryan's fuzzer with getSemanticDiagnostics after every iteration after fixing the asserts it found. It didn't show any crashes, oomemory or asserts after 2,500 iterations (it runs a lot slower since in this mode, it checks each generated program.) The fix is small, so I threw it into this PR as well.

Edit: Over 6,500 iterations.

@sandersn
Copy link
Copy Markdown
Member Author

ping @weswigham @mhegazy

Comment thread src/compiler/checker.ts Outdated
if (!(target.flags & SymbolFlags.Transient)) {
target = cloneSymbol(target);
}
Debug.assert(source !== target);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Should this be above the Transitent check, or is it OK for a nontransient target to merge into "itself" (its transient copy)?

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.

Strictly speaking, it doesn't matter, because source will never equal target just after running cloneSymbol. I think it is clearer beforehand though.

Comment thread src/compiler/checker.ts Outdated
const jsDeclaration = getDeclarationOfJSInitializer(symbol.valueDeclaration);
if (jsDeclaration) {
const jsSymbol = getSymbolOfNode(jsDeclaration);
if (jsSymbol && (jsSymbol.exports && jsSymbol.exports.size || jsSymbol.members && jsSymbol.members.size)) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Style: Maybe we should add a size helper function that handles undefined like how we have length for arrays?

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.

Let me see how many times we check for null before accessing size. The level of inconvenience seems worth about 5-6 checks before making a helper.

Later: it's over 10 now. I'll add the helper.

Comment thread src/compiler/checker.ts Outdated
if (isInJavaScriptFile(node)) {
const decl = getDeclarationOfJSInitializer(node);
if (decl) {
const jsSymbol = getMergedSymbol(decl.symbol);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use getSymbolOfNode(decl) - unless you're intentionally avoiding the late bound node check for some reason.

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.

No, I just forgot again. Thanks.

Copy link
Copy Markdown
Member

@weswigham weswigham left a comment

Choose a reason for hiding this comment

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

Some comments above, but looks good.

@sandersn sandersn merged commit d187de2 into master May 31, 2018
@sandersn sandersn deleted the js/better-initializer-binding branch May 31, 2018 18:41
@microsoft microsoft locked and limited conversation to collaborators Jul 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants