Fix named export aliases merging with export =#3157
Fix named export aliases merging with export =#3157andrewbranch merged 5 commits intomicrosoft:mainfrom
export =#3157Conversation
There was a problem hiding this comment.
Pull request overview
Fixes CommonJS export = module export checking/resolution so named export aliases are handled correctly, aligning the Go checker’s behavior with the intended TypeScript semantics (and the #2946 export-assignment merging work).
Changes:
- Resolve alias targets when determining whether an
export =module also exports value members (so TS2309 is correctly produced). - When importing from an
export =module, look up supplemental type/namespace exports on the original module symbol (so named exports can resolve). - Add compiler test cases + reference baselines for the two reported scenarios (value export should error; type-only export should resolve).
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
internal/checker/checker.go |
Updates alias-aware symbol-flag checks and adjusts export container selection for export = modules. |
testdata/tests/cases/compiler/exportAssignmentMerging7.ts |
New repro: export = plus named value export alias should produce TS2309 + downstream import error. |
testdata/tests/cases/compiler/exportAssignmentMerging8.ts |
New repro: export = plus named type alias export should be importable without error. |
testdata/baselines/reference/compiler/exportAssignmentMerging7.types |
Reference types baseline for test 7. |
testdata/baselines/reference/compiler/exportAssignmentMerging7.symbols |
Reference symbols baseline for test 7. |
testdata/baselines/reference/compiler/exportAssignmentMerging7.js |
Reference JS/d.ts baseline for test 7. |
testdata/baselines/reference/compiler/exportAssignmentMerging7.errors.txt |
Reference error baseline for test 7 (TS2309/TS2305). |
testdata/baselines/reference/compiler/exportAssignmentMerging8.types |
Reference types baseline for test 8. |
testdata/baselines/reference/compiler/exportAssignmentMerging8.symbols |
Reference symbols baseline for test 8. |
testdata/baselines/reference/compiler/exportAssignmentMerging8.js |
Reference JS/d.ts baseline for test 8. |
You can also share your feedback on Copilot code review. Take the survey.
| // A CommonJS module defined by an 'export=' might also export typedefs, stored on the original module | ||
| if originalModule != nil && len(originalModule.Exports) > 1 { | ||
| for _, symbol := range originalModule.Exports { | ||
| if symbol.Flags&ast.SymbolFlagsType != 0 && symbol.Name != ast.InternalSymbolNameExportEquals && exports[symbol.Name] == nil { | ||
| flags := c.getSymbolFlags(symbol) | ||
| if symbol.Name != ast.InternalSymbolNameExportEquals && | ||
| flags&(ast.SymbolFlagsType|ast.SymbolFlagsNamespace) != 0 && | ||
| flags&ast.SymbolFlagsValue == 0 && |
There was a problem hiding this comment.
This combo of symbol flags was a Copilot contribution but I think it's right/desirable—we generally want to allow uninstantiated namespaces wherever we allow types. The comment was already talking about a narrower case than what was allowed, but reflects the motivation for the feature, not the breadth of all possibilities.
| for _, symbol := range moduleSymbol.Exports { | ||
| if symbol.Name != ast.InternalSymbolNameExportEquals && symbol.Flags&kind != 0 { | ||
| if symbol.Name != ast.InternalSymbolNameExportEquals && c.getSymbolFlags(symbol)&kind != 0 { | ||
| return true | ||
| } | ||
| } |
There was a problem hiding this comment.
No, type-only export aliases do have value meanings even if they can't be used in emitting positions; this is working as designed.
| flags := c.getSymbolFlags(symbol) | ||
| if symbol.Name != ast.InternalSymbolNameExportEquals && | ||
| flags&(ast.SymbolFlagsType|ast.SymbolFlagsNamespace) != 0 && | ||
| flags&ast.SymbolFlagsValue == 0 && | ||
| exports[symbol.Name] == nil { |
There was a problem hiding this comment.
Again, correct observation, but working as designed.
| ~~~~~~~~~~~~~ | ||
| !!! error TS2309: An export assignment cannot be used in a module with other exported elements. | ||
| export { Bar }; // Causes error | ||
| ==== b.ts (1 errors) ==== | ||
| import { Bar } from "./a"; | ||
| ~~~ | ||
| !!! error TS2305: Module '"./a"' has no exported member 'Bar'. | ||
| const b = new Bar(); |
There was a problem hiding this comment.
I wish we could solve this somehow by erroring but still constructing some sort of export that would make it work in skipLibCheck'd files...
|
Hey look, a diff was deleted! |
ahejlsberg
left a comment
There was a problem hiding this comment.
Approved with the suggested change.
internal/checker/checker.go
Outdated
| for _, symbol := range originalModule.Exports { | ||
| if symbol.Flags&ast.SymbolFlagsType != 0 && symbol.Name != ast.InternalSymbolNameExportEquals && exports[symbol.Name] == nil { | ||
| flags := c.getSymbolFlags(symbol) | ||
| if symbol.Name != ast.InternalSymbolNameExportEquals && |
There was a problem hiding this comment.
Maybe compute flags after you check for ast.InternalSymbolNameExportEquals, just to not spend time computing something you're going to throw away.
#2946 seemed to miss resolving aliases in collecting symbol flags in a couple places, leading to missing errors on some things that aren't supposed to work, and some things that are supposed to work (I guess) silently failing: