Skip to content

Fix named export aliases merging with export =#3157

Merged
andrewbranch merged 5 commits intomicrosoft:mainfrom
andrewbranch:copilot/steep-sloth
Mar 20, 2026
Merged

Fix named export aliases merging with export =#3157
andrewbranch merged 5 commits intomicrosoft:mainfrom
andrewbranch:copilot/steep-sloth

Conversation

@andrewbranch
Copy link
Member

#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:

class Foo {}
class Bar {}
export = Foo;
export { Bar }; // was missing error because `Bar` here is SymbolFlagsAlias, not SymbolFlagsValue
import { SomeTypeAlias } from "./t";
class Foo {}
export = Foo;
export { SomeTypeAlias }; // should ideally work, but didn't work and didn't error!

Copilot AI review requested due to automatic review settings March 19, 2026 00:29
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

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.

Comment on lines 15646 to +15652
// 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 &&
Copy link
Member Author

Choose a reason for hiding this comment

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

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.

Comment on lines 5559 to 5563
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
}
}
Copy link
Member Author

Choose a reason for hiding this comment

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

No, type-only export aliases do have value meanings even if they can't be used in emitting positions; this is working as designed.

Comment on lines +15649 to +15653
flags := c.getSymbolFlags(symbol)
if symbol.Name != ast.InternalSymbolNameExportEquals &&
flags&(ast.SymbolFlagsType|ast.SymbolFlagsNamespace) != 0 &&
flags&ast.SymbolFlagsValue == 0 &&
exports[symbol.Name] == nil {
Copy link
Member Author

Choose a reason for hiding this comment

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

Again, correct observation, but working as designed.

Comment on lines +9 to +16
~~~~~~~~~~~~~
!!! 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();
Copy link
Member

Choose a reason for hiding this comment

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

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...

@andrewbranch
Copy link
Member Author

Hey look, a diff was deleted!

@andrewbranch andrewbranch requested a review from ahejlsberg March 19, 2026 15:12
Copy link
Member

@ahejlsberg ahejlsberg left a comment

Choose a reason for hiding this comment

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

Approved with the suggested change.

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 &&
Copy link
Member

Choose a reason for hiding this comment

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

Maybe compute flags after you check for ast.InternalSymbolNameExportEquals, just to not spend time computing something you're going to throw away.

@andrewbranch andrewbranch added this pull request to the merge queue Mar 20, 2026
Merged via the queue into microsoft:main with commit 418d206 Mar 20, 2026
21 checks passed
@andrewbranch andrewbranch deleted the copilot/steep-sloth branch March 20, 2026 21:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants