Skip to content

Commit 7409a04

Browse files
committed
Fix merging module augmentations to pattern ambient modules
1 parent 53c92d6 commit 7409a04

File tree

6 files changed

+140
-3
lines changed

6 files changed

+140
-3
lines changed

src/compiler/checker.ts

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@ namespace ts {
500500
* This is only used if there is no exact match.
501501
*/
502502
let patternAmbientModules: PatternAmbientModule[];
503+
let patternAmbientModuleAugmentations: Map<Symbol> | undefined;
503504

504505
let globalObjectType: ObjectType;
505506
let globalFunctionType: ObjectType;
@@ -890,7 +891,7 @@ namespace ts {
890891
* Note: if target is transient, then it is mutable, and mergeSymbol with both mutate and return it.
891892
* If target is not transient, mergeSymbol will produce a transient clone, mutate that and return it.
892893
*/
893-
function mergeSymbol(target: Symbol, source: Symbol): Symbol {
894+
function mergeSymbol(target: Symbol, source: Symbol, unidirectional = false): Symbol {
894895
if (!(target.flags & getExcludedSymbolFlags(source.flags)) ||
895896
(source.flags | target.flags) & SymbolFlags.Assignment) {
896897
Debug.assert(source !== target);
@@ -923,7 +924,9 @@ namespace ts {
923924
if (!target.exports) target.exports = createSymbolTable();
924925
mergeSymbolTable(target.exports, source.exports);
925926
}
926-
recordMergedSymbol(target, source);
927+
if (!unidirectional) {
928+
recordMergedSymbol(target, source);
929+
}
927930
}
928931
else if (target.flags & SymbolFlags.NamespaceModule) {
929932
error(getNameOfDeclaration(source.declarations[0]), Diagnostics.Cannot_augment_module_0_with_value_exports_because_it_resolves_to_a_non_module_entity, symbolToString(target));
@@ -1023,7 +1026,22 @@ namespace ts {
10231026
// obtain item referenced by 'export='
10241027
mainModule = resolveExternalModuleSymbol(mainModule);
10251028
if (mainModule.flags & SymbolFlags.Namespace) {
1026-
mainModule = mergeSymbol(mainModule, moduleAugmentation.symbol);
1029+
// If we’re merging an augmentation to a pattern ambient module, we want to
1030+
// perform the merge unidirectionally from the augmentation ('a.foo') to
1031+
// the pattern ('*.foo'), so that 'getMergedSymbol()' on a.foo gives you
1032+
// all the exports both from the pattern and from the augmentation, but
1033+
// 'getMergedSymbol()' on *.foo only gives you exports from *.foo.
1034+
if (some(patternAmbientModules, module => mainModule === module.symbol)) {
1035+
const merged = mergeSymbol(moduleAugmentation.symbol, mainModule, /*unidirectional*/ true);
1036+
if (!patternAmbientModuleAugmentations) {
1037+
patternAmbientModuleAugmentations = createMap();
1038+
}
1039+
// moduleName will be a StringLiteral since this is not `declare global`.
1040+
patternAmbientModuleAugmentations.set((moduleName as StringLiteral).text, merged);
1041+
}
1042+
else {
1043+
mergeSymbol(mainModule, moduleAugmentation.symbol);
1044+
}
10271045
}
10281046
else {
10291047
// moduleName will be a StringLiteral since this is not `declare global`.
@@ -2391,6 +2409,14 @@ namespace ts {
23912409
if (patternAmbientModules) {
23922410
const pattern = findBestPatternMatch(patternAmbientModules, _ => _.pattern, moduleReference);
23932411
if (pattern) {
2412+
// If the module reference matched a pattern ambient module ('*.foo') but there’s also a
2413+
// module augmentation by the specific name requested ('a.foo'), we store the merged symbol
2414+
// by the augmentation name ('a.foo'), because asking for *.foo should not give you exports
2415+
// from a.foo.
2416+
const augmentation = patternAmbientModuleAugmentations && patternAmbientModuleAugmentations.get(moduleReference);
2417+
if (augmentation) {
2418+
return getMergedSymbol(augmentation);
2419+
}
23942420
return getMergedSymbol(pattern.symbol);
23952421
}
23962422
}
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: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @filename: types.ts
2+
declare module "*.foo" {
3+
let everywhere: string;
4+
}
5+
6+
7+
// @filename: testA.ts
8+
import { everywhere, onlyInA } from "a.foo";
9+
declare module "a.foo" {
10+
let onlyInA: number;
11+
}
12+
13+
// @filename: testB.ts
14+
import { everywhere, onlyInA } from "b.foo"; // Error

0 commit comments

Comments
 (0)