Skip to content

Commit 90d3acf

Browse files
authored
Merge pull request #31078 from andrewbranch/bug/30752
Fix symbol merging of augmentations to pattern ambient modules
2 parents 9005449 + 973c3ca commit 90d3acf

16 files changed

+386
-7
lines changed

src/compiler/checker.ts

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,7 @@ namespace ts {
498498
* This is only used if there is no exact match.
499499
*/
500500
let patternAmbientModules: PatternAmbientModule[];
501+
let patternAmbientModuleAugmentations: Map<Symbol> | undefined;
501502

502503
let globalObjectType: ObjectType;
503504
let globalFunctionType: ObjectType;
@@ -888,7 +889,7 @@ namespace ts {
888889
* Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it.
889890
* If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it.
890891
*/
891-
function mergeSymbol(target: Symbol, source: Symbol): Symbol {
892+
function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol {
892893
if (!(target.flags & getExcludedSymbolFlags(source.flags)) ||
893894
(source.flags | target.flags) & SymbolFlags.Assignment) {
894895
Debug.assert(source !== target);
@@ -915,13 +916,16 @@ namespace ts {
915916
addRange(target.declarations, source.declarations);
916917
if (source.members) {
917918
if (!target.members) target.members = createSymbolTable();
918-
mergeSymbolTable(target.members, source.members);
919+
mergeSymbolTable(target.members, source.members, unidirectional);
919920
}
920921
if (source.exports) {
921922
if (!target.exports) target.exports = createSymbolTable();
922-
mergeSymbolTable(target.exports, source.exports);
923+
mergeSymbolTable(target.exports, source.exports, unidirectional
924+
);
925+
}
926+
if (!unidirectional) {
927+
recordMergedSymbol(target, source);
923928
}
924-
recordMergedSymbol(target, source);
925929
}
926930
else if (target.flags & SymbolFlags.NamespaceModule) {
927931
// Do not report an error when merging `var globalThis` with the built-in `globalThis`,
@@ -993,10 +997,10 @@ namespace ts {
993997
return combined;
994998
}
995999

996-
function mergeSymbolTable(target: SymbolTable, source: SymbolTable) {
1000+
function mergeSymbolTable(target: SymbolTable, source: SymbolTable, unidirectional = false) {
9971001
source.forEach((sourceSymbol, id) => {
9981002
const targetSymbol = target.get(id);
999-
target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol) : sourceSymbol);
1003+
target.set(id, targetSymbol ? mergeSymbol(targetSymbol, sourceSymbol, unidirectional) : sourceSymbol);
10001004
});
10011005
}
10021006

@@ -1026,7 +1030,22 @@ namespace ts {
10261030
// obtain item referenced by 'export='
10271031
mainModule = resolveExternalModuleSymbol(mainModule);
10281032
if (mainModule.flags & SymbolFlags.Namespace) {
1029-
mainModule = mergeSymbol(mainModule, moduleAugmentation.symbol);
1033+
// If we’re merging an augmentation to a pattern ambient module, we want to
1034+
// perform the merge unidirectionally from the augmentation ('a.foo') to
1035+
// the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you
1036+
// all the exports both from the pattern and from the augmentation, but
1037+
// 'getMergedSymbol()' on *.foo only gives you exports from *.foo.
1038+
if (some(patternAmbientModules, module => mainModule === module.symbol)) {
1039+
const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true);
1040+
if (!patternAmbientModuleAugmentations) {
1041+
patternAmbientModuleAugmentations = createMap();
1042+
}
1043+
// moduleName will be a StringLiteral since this is not `declare global`.
1044+
patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged);
1045+
}
1046+
else {
1047+
mergeSymbol(mainModule, moduleAugmentation.symbol);
1048+
}
10301049
}
10311050
else {
10321051
// moduleName will be a StringLiteral since this is not `declare global`.
@@ -2455,6 +2474,14 @@ namespace ts {
24552474
if (patternAmbientModules) {
24562475
const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference);
24572476
if (pattern) {
2477+
// If the module reference matched a pattern ambient module ('*.foo') but there’s also a
2478+
// module augmentation by the specific name requested ('a.foo'), we store the merged symbol
2479+
// by the augmentation name ('a.foo'), because asking for *.foo should not give you exports
2480+
// from a.foo.
2481+
const augmentation = patternAmbientModuleAugmentations && patternAmbientModuleAugmentations.get(moduleReference);
2482+
if (augmentation) {
2483+
return getMergedSymbol(augmentation);
2484+
}
24582485
return getMergedSymbol(pattern.symbol);
24592486
}
24602487
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
tests/cases/conformance/ambient/testB.ts(1,22): error TS2305: Module '"*.foo"' has no exported member 'onlyInA'.
2+
3+
4+
==== tests/cases/conformance/ambient/types.ts (0 errors) ====
5+
declare module "*.foo" {
6+
let everywhere: string;
7+
}
8+
9+
10+
==== tests/cases/conformance/ambient/testA.ts (0 errors) ====
11+
import { everywhere, onlyInA } from "a.foo";
12+
declare module "a.foo" {
13+
let onlyInA: number;
14+
}
15+
16+
==== tests/cases/conformance/ambient/testB.ts (1 errors) ====
17+
import { everywhere, onlyInA } from "b.foo"; // Error
18+
~~~~~~~
19+
!!! error TS2305: Module '"*.foo"' has no exported member 'onlyInA'.
20+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
//// [tests/cases/conformance/ambient/ambientDeclarationsPatterns_merging1.ts] ////
2+
3+
//// [types.ts]
4+
declare module "*.foo" {
5+
let everywhere: string;
6+
}
7+
8+
9+
//// [testA.ts]
10+
import { everywhere, onlyInA } from "a.foo";
11+
declare module "a.foo" {
12+
let onlyInA: number;
13+
}
14+
15+
//// [testB.ts]
16+
import { everywhere, onlyInA } from "b.foo"; // Error
17+
18+
19+
//// [types.js]
20+
//// [testA.js]
21+
"use strict";
22+
exports.__esModule = true;
23+
//// [testB.js]
24+
"use strict";
25+
exports.__esModule = true;
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
=== tests/cases/conformance/ambient/types.ts ===
2+
declare module "*.foo" {
3+
>"*.foo" : Symbol("*.foo", Decl(types.ts, 0, 0))
4+
5+
let everywhere: string;
6+
>everywhere : Symbol(everywhere, Decl(types.ts, 1, 5))
7+
}
8+
9+
10+
=== tests/cases/conformance/ambient/testA.ts ===
11+
import { everywhere, onlyInA } from "a.foo";
12+
>everywhere : Symbol(everywhere, Decl(testA.ts, 0, 8))
13+
>onlyInA : Symbol(onlyInA, Decl(testA.ts, 0, 20))
14+
15+
declare module "a.foo" {
16+
>"a.foo" : Symbol("a.foo", Decl(testA.ts, 0, 44), Decl(types.ts, 0, 0))
17+
18+
let onlyInA: number;
19+
>onlyInA : Symbol(onlyInA, Decl(testA.ts, 2, 5))
20+
}
21+
22+
=== tests/cases/conformance/ambient/testB.ts ===
23+
import { everywhere, onlyInA } from "b.foo"; // Error
24+
>everywhere : Symbol(everywhere, Decl(testB.ts, 0, 8))
25+
>onlyInA : Symbol(onlyInA, Decl(testB.ts, 0, 20))
26+
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
=== tests/cases/conformance/ambient/types.ts ===
2+
declare module "*.foo" {
3+
>"*.foo" : typeof import("*.foo")
4+
5+
let everywhere: string;
6+
>everywhere : string
7+
}
8+
9+
10+
=== tests/cases/conformance/ambient/testA.ts ===
11+
import { everywhere, onlyInA } from "a.foo";
12+
>everywhere : string
13+
>onlyInA : number
14+
15+
declare module "a.foo" {
16+
>"a.foo" : typeof import("a.foo")
17+
18+
let onlyInA: number;
19+
>onlyInA : number
20+
}
21+
22+
=== tests/cases/conformance/ambient/testB.ts ===
23+
import { everywhere, onlyInA } from "b.foo"; // Error
24+
>everywhere : string
25+
>onlyInA : any
26+
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
tests/cases/conformance/ambient/testB.ts(1,22): error TS2305: Module '"*.foo"' has no exported member 'onlyInA'.
2+
tests/cases/conformance/ambient/testB.ts(1,31): error TS2305: Module '"*.foo"' has no exported member 'alsoOnlyInA'.
3+
4+
5+
==== tests/cases/conformance/ambient/types.ts (0 errors) ====
6+
declare module "*.foo" {
7+
let everywhere: string;
8+
}
9+
10+
11+
==== tests/cases/conformance/ambient/testA.ts (0 errors) ====
12+
import { everywhere, onlyInA, alsoOnlyInA } from "a.foo";
13+
declare module "a.foo" {
14+
let onlyInA: number;
15+
}
16+
17+
==== tests/cases/conformance/ambient/testB.ts (2 errors) ====
18+
import { everywhere, onlyInA, alsoOnlyInA } from "b.foo"; // Error
19+
~~~~~~~
20+
!!! error TS2305: Module '"*.foo"' has no exported member 'onlyInA'.
21+
~~~~~~~~~~~
22+
!!! error TS2305: Module '"*.foo"' has no exported member 'alsoOnlyInA'.
23+
declare module "a.foo" {
24+
let alsoOnlyInA: number;
25+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
//// [tests/cases/conformance/ambient/ambientDeclarationsPatterns_merging2.ts] ////
2+
3+
//// [types.ts]
4+
declare module "*.foo" {
5+
let everywhere: string;
6+
}
7+
8+
9+
//// [testA.ts]
10+
import { everywhere, onlyInA, alsoOnlyInA } from "a.foo";
11+
declare module "a.foo" {
12+
let onlyInA: number;
13+
}
14+
15+
//// [testB.ts]
16+
import { everywhere, onlyInA, alsoOnlyInA } from "b.foo"; // Error
17+
declare module "a.foo" {
18+
let alsoOnlyInA: number;
19+
}
20+
21+
//// [types.js]
22+
//// [testA.js]
23+
"use strict";
24+
exports.__esModule = true;
25+
//// [testB.js]
26+
"use strict";
27+
exports.__esModule = true;
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
=== tests/cases/conformance/ambient/types.ts ===
2+
declare module "*.foo" {
3+
>"*.foo" : Symbol("*.foo", Decl(types.ts, 0, 0))
4+
5+
let everywhere: string;
6+
>everywhere : Symbol(everywhere, Decl(types.ts, 1, 5))
7+
}
8+
9+
10+
=== tests/cases/conformance/ambient/testA.ts ===
11+
import { everywhere, onlyInA, alsoOnlyInA } from "a.foo";
12+
>everywhere : Symbol(everywhere, Decl(testA.ts, 0, 8))
13+
>onlyInA : Symbol(onlyInA, Decl(testA.ts, 0, 20))
14+
>alsoOnlyInA : Symbol(alsoOnlyInA, Decl(testA.ts, 0, 29))
15+
16+
declare module "a.foo" {
17+
>"a.foo" : Symbol("a.foo", Decl(testA.ts, 0, 57), Decl(types.ts, 0, 0), Decl(testB.ts, 0, 57))
18+
19+
let onlyInA: number;
20+
>onlyInA : Symbol(onlyInA, Decl(testA.ts, 2, 5))
21+
}
22+
23+
=== tests/cases/conformance/ambient/testB.ts ===
24+
import { everywhere, onlyInA, alsoOnlyInA } from "b.foo"; // Error
25+
>everywhere : Symbol(everywhere, Decl(testB.ts, 0, 8))
26+
>onlyInA : Symbol(onlyInA, Decl(testB.ts, 0, 20))
27+
>alsoOnlyInA : Symbol(alsoOnlyInA, Decl(testB.ts, 0, 29))
28+
29+
declare module "a.foo" {
30+
>"a.foo" : Symbol("a.foo", Decl(testA.ts, 0, 57), Decl(types.ts, 0, 0), Decl(testB.ts, 0, 57))
31+
32+
let alsoOnlyInA: number;
33+
>alsoOnlyInA : Symbol(alsoOnlyInA, Decl(testB.ts, 2, 5))
34+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
=== tests/cases/conformance/ambient/types.ts ===
2+
declare module "*.foo" {
3+
>"*.foo" : typeof import("*.foo")
4+
5+
let everywhere: string;
6+
>everywhere : string
7+
}
8+
9+
10+
=== tests/cases/conformance/ambient/testA.ts ===
11+
import { everywhere, onlyInA, alsoOnlyInA } from "a.foo";
12+
>everywhere : string
13+
>onlyInA : number
14+
>alsoOnlyInA : number
15+
16+
declare module "a.foo" {
17+
>"a.foo" : typeof import("a.foo")
18+
19+
let onlyInA: number;
20+
>onlyInA : number
21+
}
22+
23+
=== tests/cases/conformance/ambient/testB.ts ===
24+
import { everywhere, onlyInA, alsoOnlyInA } from "b.foo"; // Error
25+
>everywhere : string
26+
>onlyInA : any
27+
>alsoOnlyInA : any
28+
29+
declare module "a.foo" {
30+
>"a.foo" : typeof import("a.foo")
31+
32+
let alsoOnlyInA: number;
33+
>alsoOnlyInA : number
34+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
tests/cases/conformance/ambient/test.ts(6,6): error TS2339: Property 'a' does not exist on type 'OhNo'.
2+
3+
4+
==== tests/cases/conformance/ambient/types.ts (0 errors) ====
5+
declare module "*.foo" {
6+
export interface OhNo { star: string }
7+
}
8+
9+
==== tests/cases/conformance/ambient/test.ts (1 errors) ====
10+
declare module "a.foo" {
11+
export interface OhNo { a: string }
12+
}
13+
import { OhNo } from "b.foo"
14+
declare let ohno: OhNo;
15+
ohno.a // oh no
16+
~
17+
!!! error TS2339: Property 'a' does not exist on type 'OhNo'.
18+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [tests/cases/conformance/ambient/ambientDeclarationsPatterns_merging3.ts] ////
2+
3+
//// [types.ts]
4+
declare module "*.foo" {
5+
export interface OhNo { star: string }
6+
}
7+
8+
//// [test.ts]
9+
declare module "a.foo" {
10+
export interface OhNo { a: string }
11+
}
12+
import { OhNo } from "b.foo"
13+
declare let ohno: OhNo;
14+
ohno.a // oh no
15+
16+
17+
//// [types.js]
18+
//// [test.js]
19+
"use strict";
20+
exports.__esModule = true;
21+
ohno.a; // oh no
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
=== tests/cases/conformance/ambient/types.ts ===
2+
declare module "*.foo" {
3+
>"*.foo" : Symbol("*.foo", Decl(types.ts, 0, 0))
4+
5+
export interface OhNo { star: string }
6+
>OhNo : Symbol(OhNo, Decl(types.ts, 0, 24))
7+
>star : Symbol(OhNo.star, Decl(types.ts, 1, 25))
8+
}
9+
10+
=== tests/cases/conformance/ambient/test.ts ===
11+
declare module "a.foo" {
12+
>"a.foo" : Symbol("a.foo", Decl(test.ts, 0, 0), Decl(types.ts, 0, 0))
13+
14+
export interface OhNo { a: string }
15+
>OhNo : Symbol(OhNo, Decl(test.ts, 0, 24), Decl(types.ts, 0, 24))
16+
>a : Symbol(OhNo.a, Decl(test.ts, 1, 25))
17+
}
18+
import { OhNo } from "b.foo"
19+
>OhNo : Symbol(OhNo, Decl(test.ts, 3, 8))
20+
21+
declare let ohno: OhNo;
22+
>ohno : Symbol(ohno, Decl(test.ts, 4, 11))
23+
>OhNo : Symbol(OhNo, Decl(test.ts, 3, 8))
24+
25+
ohno.a // oh no
26+
>ohno : Symbol(ohno, Decl(test.ts, 4, 11))
27+

0 commit comments

Comments
 (0)