-
Notifications
You must be signed in to change notification settings - Fork 12.7k
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Fix reference marking of values merged with unresolvable type-only imports #54799
Conversation
src/compiler/checker.ts
Outdated
const typeOnlyDeclaration = excludeTypeOnlyMeanings && getTypeOnlyAliasDeclaration(symbol); | ||
const typeOnlyResolution = typeOnlyDeclaration && resolveAlias(typeOnlyDeclaration.symbol); | ||
let flags = symbol.flags; | ||
let seenSymbols; | ||
while (symbol.flags & SymbolFlags.Alias) { | ||
const target = resolveAlias(symbol); | ||
if (target === typeOnlyResolution) { | ||
break; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The indentation on this function was messed up, so I recommend viewing it with whitespace changes hidden. These few lines are the core change that I mentioned in the PR description.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(dprint when?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My thoughts exactly 👀
Looks like you're introducing a change to the public API surface area. If this includes breaking changes, please document them on our wiki's API Breaking Changes page. Also, please make sure @DanielRosenwasser and @RyanCavanaugh are aware of the changes, just as a heads up. |
exports.x = void 0; | ||
exports.x = exports.A = void 0; | ||
var A = a.A; // Error | ||
exports.A = A; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some illegal merges that were previously counted as unreferenced for purposes of emit are now counted as referenced. Stuff is going to crash either way, and I don’t really think one is particularly more correct than the other.
@typescript-bot test this |
Heya @jakebailey, I've started to run the diff-based user code test suite on this PR at 4d07d5f. You can monitor the build here. Update: The results are in! |
Heya @jakebailey, I've started to run the diff-based top-repos suite on this PR at 4d07d5f. You can monitor the build here. Update: The results are in! |
Heya @jakebailey, I've started to run the extended test suite on this PR at 4d07d5f. You can monitor the build here. |
Heya @jakebailey, I've started to run the perf test suite on this PR at 4d07d5f. You can monitor the build here. Update: The results are in! |
Heya @jakebailey, I've started to run the parallelized Definitely Typed test suite on this PR at 4d07d5f. You can monitor the build here. Update: The results are in! |
@jakebailey Here are the results of running the user test suite comparing There were infrastructure failures potentially unrelated to your change:
Otherwise... Something interesting changed - please have a look. Details
|
@jakebailey Here they are:
CompilerComparison Report - main..54799
System
Hosts
Scenarios
TSServerComparison Report - main..54799
System
Hosts
Scenarios
StartupComparison Report - main..54799
System
Hosts
Scenarios
Developer Information: |
@jakebailey Here are the results of running the top-repos suite comparing Everything looks good! |
Hey @jakebailey, the results of running the DT tests are ready. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I understood what is going on here. At least, enough for me to make comments about function naming.
symbol = target; | ||
} | ||
return flags; | ||
function getSymbolFlags(symbol: Symbol, excludeTypeOnlyMeanings?: boolean, excludeLocalMeanings?: boolean): SymbolFlags { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this name makes it sound like all uses of symbol.flags
should become getSymbolFlags(symbol)
. Is that true? It doesn't look like it from the diff.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I tried to audit all uses of symbol.flags
when I first wrote getAllSymbolFlags
and did make many substitutions. The remaining uses, as best I could tell, intentionally only look at local meanings. The name getAllSymbolFlags
no longer felt appropriate with arguments that explicitly filter them down from “all.” Perhaps a way to say getSymbolFlagsIncludingNonLocalOnes
would make a better name.
typeOnlyDeclarationIsExportStar | ||
? resolveExternalModuleName(typeOnlyDeclaration.moduleSpecifier, typeOnlyDeclaration.moduleSpecifier, /*ignoreErrors*/ true) | ||
: resolveAlias(typeOnlyDeclaration.symbol)); | ||
const typeOnlyExportStarTargets = typeOnlyDeclarationIsExportStar && typeOnlyResolution ? getExportsOfModule(typeOnlyResolution) : undefined; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This name is a little confusing since it's hard to notice that it's the exports of a module. But maybe the property that it's targets of resolution is more important.
In #50853, I identified several cases where a symbol was incorrectly being reported as unusable in emit locations due to being a type-only import, when it was actually a local value merged with a type-only import:
The original code simply looked to see if the symbol resolved to any type-only import or export, which was obviously insufficient for merged symbols. Ideally, we wanted to know which declarations contributed which meanings to the merged symbol’s flags. That information isn’t tracked, though, so in #50853 I implemented a bit of proxy logic to get close: if the yet-unresolved alias symbol has a value meaning, and the type-only import symbol doesn’t, then we know the value meaning could not have come from the type-only import, so it’s safe to use. In code, that was something like
So in the example above,
symbol.flags
would haveAlias|Variable
(andVariable
counts as a Value), and the resolution ofA
starting at the type-only import would haveInterface
. The new logic correctly allowedA
to be referenced as a value.So we were kind of picking apart the pieces of the merged symbol, performing some alias resolution independently on the parts, and using the process of elimination to determine where the
SymbolFlags.Value
meaning must have come from. (The key thing that makes this work is that separate values are not allowed to merge across modules through an import—e.g., even though a function and a namespace can merge in the same file, you can’t import a functionf
and then declare an instantiatednamespace f
. That’s always been an error.)However, this logic breaks down if the type-only import cannot be resolved, as in
ts.transpileModule
. The symbol returned byresolveAlias
for an unresolvable alias isunknownSymbol
, which has all symbol flags, to denote that it could be anything. So looking at the example through the old code again:symbol.flags & SymbolFlags.Value
is still true, but this time,resolveAlias(getTypeOnlyAliasDeclaration(symbol).symbol)
isunknownSymbol
, which also hasSymbolFlags.Value
, so the incorrect assumption is that the value meaning forA
must have come from the type-only import.This PR replaces that strategy with one that collects flags starting at the merged symbol, and stops just before it gets to the resolution of the type-only import.
Fixes #54526