Better JS container binding#24367
Conversation
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
|
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 |
| const jsDeclaration = getDeclarationOfJSInitializer(symbol.valueDeclaration); | ||
| if (jsDeclaration) { | ||
| const jsSymbol = getMergedSymbol(jsDeclaration.symbol); | ||
| if (jsSymbol && (jsSymbol.exports && jsSymbol.exports.size || jsSymbol.members && jsSymbol.members.size)) { |
There was a problem hiding this comment.
If jsSymbol === symbol (somehow) does this work need to be done?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Fixed. (As is the call to getSymbolOfNode.)
| if (!links.type) { | ||
| const jsDeclaration = getDeclarationOfJSInitializer(symbol.valueDeclaration); | ||
| if (jsDeclaration) { | ||
| const jsSymbol = getMergedSymbol(jsDeclaration.symbol); |
There was a problem hiding this comment.
This should just be a call to getsymbolofnode I think.
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).
|
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. |
|
ping @weswigham @mhegazy |
| if (!(target.flags & SymbolFlags.Transient)) { | ||
| target = cloneSymbol(target); | ||
| } | ||
| Debug.assert(source !== target); |
There was a problem hiding this comment.
Should this be above the Transitent check, or is it OK for a nontransient target to merge into "itself" (its transient copy)?
There was a problem hiding this comment.
Strictly speaking, it doesn't matter, because source will never equal target just after running cloneSymbol. I think it is clearer beforehand though.
| const jsDeclaration = getDeclarationOfJSInitializer(symbol.valueDeclaration); | ||
| if (jsDeclaration) { | ||
| const jsSymbol = getSymbolOfNode(jsDeclaration); | ||
| if (jsSymbol && (jsSymbol.exports && jsSymbol.exports.size || jsSymbol.members && jsSymbol.members.size)) { |
There was a problem hiding this comment.
Style: Maybe we should add a size helper function that handles undefined like how we have length for arrays?
There was a problem hiding this comment.
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.
| if (isInJavaScriptFile(node)) { | ||
| const decl = getDeclarationOfJSInitializer(node); | ||
| if (decl) { | ||
| const jsSymbol = getMergedSymbol(decl.symbol); |
There was a problem hiding this comment.
Use getSymbolOfNode(decl) - unless you're intentionally avoiding the late bound node check for some reason.
There was a problem hiding this comment.
No, I just forgot again. Thanks.
weswigham
left a comment
There was a problem hiding this comment.
Some comments above, but looks good.
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,
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,
getTypeOfFuncClassEnumModulechecks 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:
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