-
Notifications
You must be signed in to change notification settings - Fork 12.7k
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
Auto-imports: fix some exports being incorrectly stored as re-exports of others due to key conflict #45792
Conversation
@typescript-bot pack this |
Heya @andrewbranch, I've started to run the tarball bundle task on this PR at 57220f2. You can monitor the build here. |
Hey @andrewbranch, I've packed this into an installable tgz. You can install it for testing by referencing it in your
and then running There is also a playground for this build and an npm module you can use via |
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.
A couple of questions to check my understanding.
The reason I haven’t explained what the heck is happening in this PR is I’m hoping to have some time to do a more thorough fix by saving a reference to the originally-exporting module and the name of the export in the module’s symbol table at the end of alias resolution in the checker. This is precisely the information we need to be able to identify every unique export, and the checker already has that information during alias resolution, but it discards it. |
Can this PR fix #46115? |
Probably, feel free to try it |
I just tested this and it works fine for my |
57220f2
to
fef7e8c
Compare
const origin: SymbolOriginInfoExport = { | ||
kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport), | ||
const fileName = isExternalModuleNameRelative(stripQuotes(moduleSymbol.name)) ? getSourceFileOfModule(moduleSymbol)?.fileName : undefined; | ||
const { moduleSpecifier } = codefix.getModuleSpecifierForBestExportInfo([{ | ||
exportKind: ExportKind.Named, | ||
moduleFileName: fileName, | ||
isFromPackageJson: false, | ||
moduleSymbol, | ||
isDefaultExport: false, | ||
symbolName: firstAccessibleSymbol.name, | ||
exportName: firstAccessibleSymbol.name, | ||
fileName: isExternalModuleNameRelative(stripQuotes(moduleSymbol.name)) ? cast(moduleSymbol.valueDeclaration, isSourceFile).fileName : undefined, | ||
}; | ||
symbolToOriginInfoMap[index] = origin; | ||
symbol: firstAccessibleSymbol, | ||
targetFlags: skipAlias(firstAccessibleSymbol, typeChecker).flags, | ||
}], sourceFile, program, host, preferences) || {}; | ||
|
||
if (moduleSpecifier) { | ||
const origin: SymbolOriginInfoResolvedExport = { | ||
kind: getNullableSymbolOriginInfoKind(SymbolOriginInfoKind.SymbolMemberExport), | ||
moduleSymbol, | ||
isDefaultExport: false, | ||
symbolName: firstAccessibleSymbol.name, | ||
exportName: firstAccessibleSymbol.name, | ||
fileName, | ||
moduleSpecifier, | ||
}; | ||
symbolToOriginInfoMap[index] = origin; | ||
} |
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 is one of the larger set of changes in the PR, but it’s kind of a drive-by fix for a probable ancient bug that the real change, the addition of exportMapKey
to SymbolOriginInfoExport
, forced me to consider. This has always been a weird ad-hoc auto-import for when you need to import a symbol in order to do a property completion, as shown in this test.
I think there have likely been cases where fetching the actual auto-import (in completion details) for this completion would have failed due to some oversimplified assumptions here. I’m now guarding against that a bit more by resolving its module specifier up front and ensuring that the symbol can definitely be re-retrieved by looking for its name in the exports of the module. I could have sort of ignored this problem, but it felt better to deal with it.
@@ -46,8 +46,8 @@ namespace ts { | |||
isUsableByFile(importingFile: Path): boolean; | |||
clear(): void; | |||
add(importingFile: Path, symbol: Symbol, key: __String, moduleSymbol: Symbol, moduleFile: SourceFile | undefined, exportKind: ExportKind, isFromPackageJson: boolean, scriptTarget: ScriptTarget, checker: TypeChecker): void; | |||
get(importingFile: Path, importedName: string, symbol: Symbol, moduleName: string, checker: TypeChecker): readonly SymbolExportInfo[] | undefined; | |||
forEach(importingFile: Path, action: (info: readonly SymbolExportInfo[], name: string, isFromAmbientModule: boolean) => void): void; | |||
get(importingFile: Path, key: string): readonly SymbolExportInfo[] | 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.
The central insight of this PR is that the cache key does not actually need to be derivable from an updated program later on, because the only thing that ever calls get
is continuePreviousIncompleteResponse
in completions.ts, and rather than recomputing the key from symbols it currently has access to, we can just save the key we originally came up with on CompletionEntryData
and use that to access the cache in continuePreviousIncompleteResponse
. This means the only criteria that the key needs to satisfy is that it produces proper grouping while building the map. This opens the door to using getSymbolId(skipAlias(someExport))
in the key, which doesn't have the same very weird pitfalls as trying to find a way to uniquely identify the target symbol that remains stable over program updates.
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'm happy with the concept and the protocol changes and I'm satisfied that we have an escape hatch if it breaks. I agree with @DanielRosenwasser that a little more baking time wouldn't hurt.
There’s some explanation in review comments
Fixes #45763
Fixes #45784