Skip to content

Commit b6de67f

Browse files
authored
Defer global ambient module merging in initializeChecker (#3770)
1 parent 48e2953 commit b6de67f

7 files changed

Lines changed: 225 additions & 7 deletions

internal/ast/utilities.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1619,6 +1619,10 @@ func IsAmbientModule(node *Node) bool {
16191619
return IsModuleDeclaration(node) && (node.AsModuleDeclaration().Name().Kind == KindStringLiteral || IsGlobalScopeAugmentation(node))
16201620
}
16211621

1622+
func IsAmbientModuleSymbolName(s string) bool {
1623+
return strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"")
1624+
}
1625+
16221626
func IsExternalModule(file *SourceFile) bool {
16231627
return file.ExternalModuleIndicator != nil
16241628
}

internal/checker/checker.go

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1284,6 +1284,7 @@ func (c *Checker) initializeIterationResolvers() {
12841284

12851285
func (c *Checker) initializeChecker() {
12861286
// Initialize global symbol table
1287+
var ambientModuleSymbols []*ast.Symbol
12871288
augmentations := make([][]*ast.Node, 0, len(c.files))
12881289
for _, file := range c.files {
12891290
if !ast.IsExternalOrCommonJSModule(file) {
@@ -1293,7 +1294,15 @@ func (c *Checker) initializeChecker() {
12931294
c.diagnostics.Add(NewDiagnosticForNode(d, diagnostics.Declaration_name_conflicts_with_built_in_global_identifier_0, "globalThis"))
12941295
}
12951296
}
1296-
c.mergeSymbolTable(c.globals, file.Locals, false, nil)
1297+
for _, symbol := range file.Locals {
1298+
// We defer merging of global ambient module declarations since they may require other global symbols
1299+
// and types to be resolved. See https://github.com/microsoft/typescript-go/issues/2953.
1300+
if symbol.Flags&ast.SymbolFlagsModule != 0 && ast.IsAmbientModuleSymbolName(symbol.Name) {
1301+
ambientModuleSymbols = append(ambientModuleSymbols, symbol)
1302+
} else {
1303+
c.mergeGlobalSymbol(symbol)
1304+
}
1305+
}
12971306
}
12981307
c.patternAmbientModules = append(c.patternAmbientModules, file.PatternAmbientModules...)
12991308
augmentations = append(augmentations, file.ModuleAugmentations)
@@ -1348,6 +1357,10 @@ func (c *Checker) initializeChecker() {
13481357
}
13491358
c.anyReadonlyArrayType = c.createTypeFromGenericGlobalType(c.globalReadonlyArrayType, []*Type{c.anyType})
13501359
c.globalThisType = c.getGlobalType("ThisType", 1 /*arity*/, false /*reportErrors*/)
1360+
// Now merge global ambient module declarations
1361+
for _, symbol := range ambientModuleSymbols {
1362+
c.mergeGlobalSymbol(symbol)
1363+
}
13511364
// merge _nonglobal_ module augmentations.
13521365
// this needs to be done after global symbol table is initialized to make sure that all ambient modules are indexed
13531366
for _, list := range augmentations {
@@ -1359,6 +1372,17 @@ func (c *Checker) initializeChecker() {
13591372
}
13601373
}
13611374

1375+
func (c *Checker) mergeGlobalSymbol(symbol *ast.Symbol) {
1376+
globalSymbol := c.globals[symbol.Name]
1377+
var merged *ast.Symbol
1378+
if globalSymbol != nil {
1379+
merged = c.mergeSymbol(globalSymbol, symbol, false /*unidirectional*/)
1380+
} else {
1381+
merged = c.getMergedSymbol(symbol)
1382+
}
1383+
c.globals[symbol.Name] = merged
1384+
}
1385+
13621386
func (c *Checker) mergeModuleAugmentation(moduleName *ast.Node) {
13631387
moduleNode := moduleName.Parent
13641388
moduleAugmentation := moduleNode.AsModuleDeclaration()

internal/checker/nodebuilderimpl.go

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1176,10 +1176,6 @@ func (b_ *NodeBuilderImpl) sortByBestName(a sortedSymbolNamePair, b sortedSymbol
11761176
return b_.ch.compareSymbols(a.sym, b.sym) // must sort symbols for stable ordering
11771177
}
11781178

1179-
func isAmbientModuleSymbolName(s string) bool {
1180-
return strings.HasPrefix(s, "\"") && strings.HasSuffix(s, "\"")
1181-
}
1182-
11831179
func canHaveModuleSpecifier(node *ast.Node) bool {
11841180
if node == nil {
11851181
return false
@@ -1269,12 +1265,12 @@ func (b *NodeBuilderImpl) getSpecifierForModuleSymbol(symbol *ast.Symbol, overri
12691265
}
12701266

12711267
if file == nil {
1272-
if isAmbientModuleSymbolName(symbol.Name) {
1268+
if ast.IsAmbientModuleSymbolName(symbol.Name) {
12731269
return stringutil.StripQuotes(symbol.Name)
12741270
}
12751271
}
12761272
if b.ctx.enclosingFile == nil {
1277-
if isAmbientModuleSymbolName(symbol.Name) {
1273+
if ast.IsAmbientModuleSymbolName(symbol.Name) {
12781274
return stringutil.StripQuotes(symbol.Name)
12791275
}
12801276
return ast.GetSourceFileOfModule(symbol).FileName()
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/a.d.ts(1,63): error TS2451: Cannot redeclare block-scoped variable 'foo'.
2+
/b.d.ts(1,39): error TS2451: Cannot redeclare block-scoped variable 'foo'.
3+
4+
5+
==== /node_modules/foo/index.d.ts (0 errors) ====
6+
declare function foo(): void;
7+
declare namespace foo { export const items: string[]; }
8+
export = foo;
9+
10+
==== /a.d.ts (1 errors) ====
11+
declare module 'mymod' { import * as foo from 'foo'; export { foo }; }
12+
~~~
13+
!!! error TS2451: Cannot redeclare block-scoped variable 'foo'.
14+
!!! related TS6203 /b.d.ts:1:39: 'foo' was also declared here.
15+
16+
==== /b.d.ts (1 errors) ====
17+
declare module 'mymod' { export const foo: number; }
18+
~~~
19+
!!! error TS2451: Cannot redeclare block-scoped variable 'foo'.
20+
!!! related TS6203 /a.d.ts:1:63: 'foo' was also declared here.
21+
22+
==== /augment.ts (0 errors) ====
23+
declare global {
24+
interface Array<T> {
25+
customMethod(): T;
26+
}
27+
}
28+
export {};
29+
30+
==== /index.ts (0 errors) ====
31+
import * as foo from 'foo';
32+
const items = foo.items;
33+
const result: string = items.customMethod();
34+
35+
const fresh: string[] = [];
36+
const result2: string = fresh.customMethod();
37+
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//// [tests/cases/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.ts] ////
2+
3+
=== /node_modules/foo/index.d.ts ===
4+
declare function foo(): void;
5+
>foo : Symbol(foo, Decl(index.d.ts, 0, 0), Decl(index.d.ts, 0, 29))
6+
7+
declare namespace foo { export const items: string[]; }
8+
>foo : Symbol(foo, Decl(index.d.ts, 0, 0), Decl(index.d.ts, 0, 29))
9+
>items : Symbol(items, Decl(index.d.ts, 1, 36))
10+
11+
export = foo;
12+
>foo : Symbol(foo, Decl(index.d.ts, 0, 0), Decl(index.d.ts, 0, 29))
13+
14+
=== /a.d.ts ===
15+
declare module 'mymod' { import * as foo from 'foo'; export { foo }; }
16+
>'mymod' : Symbol("mymod", Decl(a.d.ts, 0, 0), Decl(b.d.ts, 0, 0))
17+
>foo : Symbol(foo, Decl(a.d.ts, 0, 31))
18+
>foo : Symbol(foo, Decl(a.d.ts, 0, 61))
19+
20+
=== /b.d.ts ===
21+
declare module 'mymod' { export const foo: number; }
22+
>'mymod' : Symbol("mymod", Decl(a.d.ts, 0, 0), Decl(b.d.ts, 0, 0))
23+
>foo : Symbol(foo, Decl(b.d.ts, 0, 37))
24+
25+
=== /augment.ts ===
26+
declare global {
27+
>global : Symbol(global, Decl(augment.ts, 0, 0))
28+
29+
interface Array<T> {
30+
>Array : Symbol(Array, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --), Decl(augment.ts, 0, 16))
31+
>T : Symbol(T, Decl(lib.es5.d.ts, --, --), Decl(augment.ts, 1, 20))
32+
33+
customMethod(): T;
34+
>customMethod : Symbol(Array.customMethod, Decl(augment.ts, 1, 24))
35+
>T : Symbol(T, Decl(lib.es5.d.ts, --, --), Decl(augment.ts, 1, 20))
36+
}
37+
}
38+
export {};
39+
40+
=== /index.ts ===
41+
import * as foo from 'foo';
42+
>foo : Symbol(foo, Decl(index.ts, 0, 6))
43+
44+
const items = foo.items;
45+
>items : Symbol(items, Decl(index.ts, 1, 5))
46+
>foo.items : Symbol(items, Decl(index.d.ts, 1, 36))
47+
>foo : Symbol(foo, Decl(index.ts, 0, 6))
48+
>items : Symbol(items, Decl(index.d.ts, 1, 36))
49+
50+
const result: string = items.customMethod();
51+
>result : Symbol(result, Decl(index.ts, 2, 5))
52+
>items.customMethod : Symbol(Array.customMethod, Decl(augment.ts, 1, 24))
53+
>items : Symbol(items, Decl(index.ts, 1, 5))
54+
>customMethod : Symbol(Array.customMethod, Decl(augment.ts, 1, 24))
55+
56+
const fresh: string[] = [];
57+
>fresh : Symbol(fresh, Decl(index.ts, 4, 5))
58+
59+
const result2: string = fresh.customMethod();
60+
>result2 : Symbol(result2, Decl(index.ts, 5, 5))
61+
>fresh.customMethod : Symbol(Array.customMethod, Decl(augment.ts, 1, 24))
62+
>fresh : Symbol(fresh, Decl(index.ts, 4, 5))
63+
>customMethod : Symbol(Array.customMethod, Decl(augment.ts, 1, 24))
64+
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//// [tests/cases/compiler/globalArrayAugmentationWithAmbientModuleReexportMerge1.ts] ////
2+
3+
=== /node_modules/foo/index.d.ts ===
4+
declare function foo(): void;
5+
>foo : typeof foo
6+
7+
declare namespace foo { export const items: string[]; }
8+
>foo : typeof foo
9+
>items : string[]
10+
11+
export = foo;
12+
>foo : typeof foo
13+
14+
=== /a.d.ts ===
15+
declare module 'mymod' { import * as foo from 'foo'; export { foo }; }
16+
>'mymod' : typeof import("mymod")
17+
>foo : typeof foo
18+
>foo : typeof foo
19+
20+
=== /b.d.ts ===
21+
declare module 'mymod' { export const foo: number; }
22+
>'mymod' : typeof import("mymod")
23+
>foo : number
24+
25+
=== /augment.ts ===
26+
declare global {
27+
>global : any
28+
29+
interface Array<T> {
30+
customMethod(): T;
31+
>customMethod : () => T
32+
}
33+
}
34+
export {};
35+
36+
=== /index.ts ===
37+
import * as foo from 'foo';
38+
>foo : typeof foo
39+
40+
const items = foo.items;
41+
>items : string[]
42+
>foo.items : string[]
43+
>foo : typeof foo
44+
>items : string[]
45+
46+
const result: string = items.customMethod();
47+
>result : string
48+
>items.customMethod() : string
49+
>items.customMethod : () => string
50+
>items : string[]
51+
>customMethod : () => string
52+
53+
const fresh: string[] = [];
54+
>fresh : string[]
55+
>[] : never[]
56+
57+
const result2: string = fresh.customMethod();
58+
>result2 : string
59+
>fresh.customMethod() : string
60+
>fresh.customMethod : () => string
61+
>fresh : string[]
62+
>customMethod : () => string
63+
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
// @target: es2015
2+
// @lib: es5
3+
// @noEmit: true
4+
5+
// @Filename: /node_modules/foo/index.d.ts
6+
declare function foo(): void;
7+
declare namespace foo { export const items: string[]; }
8+
export = foo;
9+
10+
// @Filename: /a.d.ts
11+
declare module 'mymod' { import * as foo from 'foo'; export { foo }; }
12+
13+
// @Filename: /b.d.ts
14+
declare module 'mymod' { export const foo: number; }
15+
16+
// @Filename: /augment.ts
17+
declare global {
18+
interface Array<T> {
19+
customMethod(): T;
20+
}
21+
}
22+
export {};
23+
24+
// @Filename: /index.ts
25+
import * as foo from 'foo';
26+
const items = foo.items;
27+
const result: string = items.customMethod();
28+
29+
const fresh: string[] = [];
30+
const result2: string = fresh.customMethod();

0 commit comments

Comments
 (0)