Skip to content

Commit 3e86f15

Browse files
authored
Disambiguate types with same name from different namespaces in mapToTypeNodes (microsoft#37543)
* Disambiguate types with same name from different namespaces in mapToTypeNodes * Update baseline with additional example * Fix typo
1 parent c47aca0 commit 3e86f15

File tree

8 files changed

+211
-1
lines changed

8 files changed

+211
-1
lines changed

src/compiler/checker.ts

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4742,7 +4742,10 @@ namespace ts {
47424742
];
47434743
}
47444744
}
4745-
const result = [];
4745+
const mayHaveNameCollisions = !(context.flags & NodeBuilderFlags.UseFullyQualifiedType);
4746+
/** Map from type reference identifier text to [type, index in `result` where the type node is] */
4747+
const seenNames = mayHaveNameCollisions ? createUnderscoreEscapedMultiMap<[Type, number]>() : undefined;
4748+
const result: TypeNode[] = [];
47464749
let i = 0;
47474750
for (const type of types) {
47484751
i++;
@@ -4758,13 +4761,42 @@ namespace ts {
47584761
const typeNode = typeToTypeNodeHelper(type, context);
47594762
if (typeNode) {
47604763
result.push(typeNode);
4764+
if (seenNames && isIdentifierTypeReference(typeNode)) {
4765+
seenNames.add(typeNode.typeName.escapedText, [type, result.length - 1]);
4766+
}
47614767
}
47624768
}
47634769

4770+
if (seenNames) {
4771+
// To avoid printing types like `[Foo, Foo]` or `Bar & Bar` where
4772+
// occurrences of the same name actually come from different
4773+
// namespaces, go through the single-identifier type reference nodes
4774+
// we just generated, and see if any names were generated more than
4775+
// once while referring to different types. If so, regenerate the
4776+
// type node for each entry by that name with the
4777+
// `UseFullyQualifiedType` flag enabled.
4778+
const saveContextFlags = context.flags;
4779+
context.flags |= NodeBuilderFlags.UseFullyQualifiedType;
4780+
seenNames.forEach(types => {
4781+
if (!arrayIsHomogeneous(types, ([a], [b]) => typesAreSameReference(a, b))) {
4782+
for (const [type, resultIndex] of types) {
4783+
result[resultIndex] = typeToTypeNodeHelper(type, context);
4784+
}
4785+
}
4786+
});
4787+
context.flags = saveContextFlags;
4788+
}
4789+
47644790
return result;
47654791
}
47664792
}
47674793

4794+
function typesAreSameReference(a: Type, b: Type): boolean {
4795+
return a === b
4796+
|| !!a.symbol && a.symbol === b.symbol
4797+
|| !!a.aliasSymbol && a.aliasSymbol === b.aliasSymbol;
4798+
}
4799+
47684800
function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind, context: NodeBuilderContext): IndexSignatureDeclaration {
47694801
const name = getNameFromIndexInfo(indexInfo) || "x";
47704802
const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword);

src/compiler/core.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1351,6 +1351,24 @@ namespace ts {
13511351
}
13521352
}
13531353

1354+
export interface UnderscoreEscapedMultiMap<T> extends UnderscoreEscapedMap<T[]> {
1355+
/**
1356+
* Adds the value to an array of values associated with the key, and returns the array.
1357+
* Creates the array if it does not already exist.
1358+
*/
1359+
add(key: __String, value: T): T[];
1360+
/**
1361+
* Removes a value from an array of values associated with the key.
1362+
* Does not preserve the order of those values.
1363+
* Does nothing if `key` is not in `map`, or `value` is not in `map[key]`.
1364+
*/
1365+
remove(key: __String, value: T): void;
1366+
}
1367+
1368+
export function createUnderscoreEscapedMultiMap<T>(): UnderscoreEscapedMultiMap<T> {
1369+
return createMultiMap<T>() as UnderscoreEscapedMultiMap<T>;
1370+
}
1371+
13541372
/**
13551373
* Tests whether a value is an array.
13561374
*/

src/compiler/utilities.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6353,4 +6353,18 @@ namespace ts {
63536353
}) as HeritageClause | undefined;
63546354
return heritageClause?.token === SyntaxKind.ImplementsKeyword || heritageClause?.parent.kind === SyntaxKind.InterfaceDeclaration;
63556355
}
6356+
6357+
export function isIdentifierTypeReference(node: Node): node is TypeReferenceNode & { typeName: Identifier } {
6358+
return isTypeReferenceNode(node) && isIdentifier(node.typeName);
6359+
}
6360+
6361+
export function arrayIsHomogeneous<T>(array: readonly T[], comparer: EqualityComparer<T> = equateValues) {
6362+
if (array.length < 2) return true;
6363+
const first = array[0];
6364+
for (let i = 1, length = array.length; i < length; i++) {
6365+
const target = array[i];
6366+
if (!comparer(first, target)) return false;
6367+
}
6368+
return true;
6369+
}
63566370
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
tests/cases/compiler/namespaceDisambiguationInUnion.ts(10,7): error TS2322: Type '{ type: string; }' is not assignable to type 'Foo.Yep | Bar.Yep'.
2+
Type '{ type: string; }' is not assignable to type 'Yep'.
3+
Types of property 'type' are incompatible.
4+
Type 'string' is not assignable to type '"bar.yep"'.
5+
tests/cases/compiler/namespaceDisambiguationInUnion.ts(13,7): error TS2739: Type '{ type: string; }[]' is missing the following properties from type '[Foo.Yep, Bar.Yep]': 0, 1
6+
7+
8+
==== tests/cases/compiler/namespaceDisambiguationInUnion.ts (2 errors) ====
9+
namespace Foo {
10+
export type Yep = { type: "foo.yep" };
11+
}
12+
13+
namespace Bar {
14+
export type Yep = { type: "bar.yep" };
15+
}
16+
17+
const x = { type: "wat.nup" };
18+
const val1: Foo.Yep | Bar.Yep = x;
19+
~~~~
20+
!!! error TS2322: Type '{ type: string; }' is not assignable to type 'Foo.Yep | Bar.Yep'.
21+
!!! error TS2322: Type '{ type: string; }' is not assignable to type 'Yep'.
22+
!!! error TS2322: Types of property 'type' are incompatible.
23+
!!! error TS2322: Type 'string' is not assignable to type '"bar.yep"'.
24+
25+
const y = [{ type: "a" }, { type: "b" }];
26+
const val2: [Foo.Yep, Bar.Yep] = y;
27+
~~~~
28+
!!! error TS2739: Type '{ type: string; }[]' is missing the following properties from type '[Foo.Yep, Bar.Yep]': 0, 1
29+
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//// [namespaceDisambiguationInUnion.ts]
2+
namespace Foo {
3+
export type Yep = { type: "foo.yep" };
4+
}
5+
6+
namespace Bar {
7+
export type Yep = { type: "bar.yep" };
8+
}
9+
10+
const x = { type: "wat.nup" };
11+
const val1: Foo.Yep | Bar.Yep = x;
12+
13+
const y = [{ type: "a" }, { type: "b" }];
14+
const val2: [Foo.Yep, Bar.Yep] = y;
15+
16+
17+
//// [namespaceDisambiguationInUnion.js]
18+
var x = { type: "wat.nup" };
19+
var val1 = x;
20+
var y = [{ type: "a" }, { type: "b" }];
21+
var val2 = y;
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
=== tests/cases/compiler/namespaceDisambiguationInUnion.ts ===
2+
namespace Foo {
3+
>Foo : Symbol(Foo, Decl(namespaceDisambiguationInUnion.ts, 0, 0))
4+
5+
export type Yep = { type: "foo.yep" };
6+
>Yep : Symbol(Yep, Decl(namespaceDisambiguationInUnion.ts, 0, 15))
7+
>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 1, 21))
8+
}
9+
10+
namespace Bar {
11+
>Bar : Symbol(Bar, Decl(namespaceDisambiguationInUnion.ts, 2, 1))
12+
13+
export type Yep = { type: "bar.yep" };
14+
>Yep : Symbol(Yep, Decl(namespaceDisambiguationInUnion.ts, 4, 15))
15+
>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 5, 21))
16+
}
17+
18+
const x = { type: "wat.nup" };
19+
>x : Symbol(x, Decl(namespaceDisambiguationInUnion.ts, 8, 5))
20+
>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 8, 11))
21+
22+
const val1: Foo.Yep | Bar.Yep = x;
23+
>val1 : Symbol(val1, Decl(namespaceDisambiguationInUnion.ts, 9, 5))
24+
>Foo : Symbol(Foo, Decl(namespaceDisambiguationInUnion.ts, 0, 0))
25+
>Yep : Symbol(Foo.Yep, Decl(namespaceDisambiguationInUnion.ts, 0, 15))
26+
>Bar : Symbol(Bar, Decl(namespaceDisambiguationInUnion.ts, 2, 1))
27+
>Yep : Symbol(Bar.Yep, Decl(namespaceDisambiguationInUnion.ts, 4, 15))
28+
>x : Symbol(x, Decl(namespaceDisambiguationInUnion.ts, 8, 5))
29+
30+
const y = [{ type: "a" }, { type: "b" }];
31+
>y : Symbol(y, Decl(namespaceDisambiguationInUnion.ts, 11, 5))
32+
>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 11, 12))
33+
>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 11, 27))
34+
35+
const val2: [Foo.Yep, Bar.Yep] = y;
36+
>val2 : Symbol(val2, Decl(namespaceDisambiguationInUnion.ts, 12, 5))
37+
>Foo : Symbol(Foo, Decl(namespaceDisambiguationInUnion.ts, 0, 0))
38+
>Yep : Symbol(Foo.Yep, Decl(namespaceDisambiguationInUnion.ts, 0, 15))
39+
>Bar : Symbol(Bar, Decl(namespaceDisambiguationInUnion.ts, 2, 1))
40+
>Yep : Symbol(Bar.Yep, Decl(namespaceDisambiguationInUnion.ts, 4, 15))
41+
>y : Symbol(y, Decl(namespaceDisambiguationInUnion.ts, 11, 5))
42+
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
=== tests/cases/compiler/namespaceDisambiguationInUnion.ts ===
2+
namespace Foo {
3+
export type Yep = { type: "foo.yep" };
4+
>Yep : Yep
5+
>type : "foo.yep"
6+
}
7+
8+
namespace Bar {
9+
export type Yep = { type: "bar.yep" };
10+
>Yep : Yep
11+
>type : "bar.yep"
12+
}
13+
14+
const x = { type: "wat.nup" };
15+
>x : { type: string; }
16+
>{ type: "wat.nup" } : { type: string; }
17+
>type : string
18+
>"wat.nup" : "wat.nup"
19+
20+
const val1: Foo.Yep | Bar.Yep = x;
21+
>val1 : Foo.Yep | Bar.Yep
22+
>Foo : any
23+
>Bar : any
24+
>x : { type: string; }
25+
26+
const y = [{ type: "a" }, { type: "b" }];
27+
>y : { type: string; }[]
28+
>[{ type: "a" }, { type: "b" }] : { type: string; }[]
29+
>{ type: "a" } : { type: string; }
30+
>type : string
31+
>"a" : "a"
32+
>{ type: "b" } : { type: string; }
33+
>type : string
34+
>"b" : "b"
35+
36+
const val2: [Foo.Yep, Bar.Yep] = y;
37+
>val2 : [Foo.Yep, Bar.Yep]
38+
>Foo : any
39+
>Bar : any
40+
>y : { type: string; }[]
41+
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
namespace Foo {
2+
export type Yep = { type: "foo.yep" };
3+
}
4+
5+
namespace Bar {
6+
export type Yep = { type: "bar.yep" };
7+
}
8+
9+
const x = { type: "wat.nup" };
10+
const val1: Foo.Yep | Bar.Yep = x;
11+
12+
const y = [{ type: "a" }, { type: "b" }];
13+
const val2: [Foo.Yep, Bar.Yep] = y;

0 commit comments

Comments
 (0)