Skip to content

Commit

Permalink
Disambiguate types with same name from different namespaces in mapToT…
Browse files Browse the repository at this point in the history
…ypeNodes (#37543)

* Disambiguate types with same name from different namespaces in mapToTypeNodes

* Update baseline with additional example

* Fix typo
  • Loading branch information
andrewbranch authored Apr 7, 2020
1 parent c47aca0 commit 3e86f15
Show file tree
Hide file tree
Showing 8 changed files with 211 additions and 1 deletion.
34 changes: 33 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4742,7 +4742,10 @@ namespace ts {
];
}
}
const result = [];
const mayHaveNameCollisions = !(context.flags & NodeBuilderFlags.UseFullyQualifiedType);
/** Map from type reference identifier text to [type, index in `result` where the type node is] */
const seenNames = mayHaveNameCollisions ? createUnderscoreEscapedMultiMap<[Type, number]>() : undefined;
const result: TypeNode[] = [];
let i = 0;
for (const type of types) {
i++;
Expand All @@ -4758,13 +4761,42 @@ namespace ts {
const typeNode = typeToTypeNodeHelper(type, context);
if (typeNode) {
result.push(typeNode);
if (seenNames && isIdentifierTypeReference(typeNode)) {
seenNames.add(typeNode.typeName.escapedText, [type, result.length - 1]);
}
}
}

if (seenNames) {
// To avoid printing types like `[Foo, Foo]` or `Bar & Bar` where
// occurrences of the same name actually come from different
// namespaces, go through the single-identifier type reference nodes
// we just generated, and see if any names were generated more than
// once while referring to different types. If so, regenerate the
// type node for each entry by that name with the
// `UseFullyQualifiedType` flag enabled.
const saveContextFlags = context.flags;
context.flags |= NodeBuilderFlags.UseFullyQualifiedType;
seenNames.forEach(types => {
if (!arrayIsHomogeneous(types, ([a], [b]) => typesAreSameReference(a, b))) {
for (const [type, resultIndex] of types) {
result[resultIndex] = typeToTypeNodeHelper(type, context);
}
}
});
context.flags = saveContextFlags;
}

return result;
}
}

function typesAreSameReference(a: Type, b: Type): boolean {
return a === b
|| !!a.symbol && a.symbol === b.symbol
|| !!a.aliasSymbol && a.aliasSymbol === b.aliasSymbol;
}

function indexInfoToIndexSignatureDeclarationHelper(indexInfo: IndexInfo, kind: IndexKind, context: NodeBuilderContext): IndexSignatureDeclaration {
const name = getNameFromIndexInfo(indexInfo) || "x";
const indexerTypeNode = createKeywordTypeNode(kind === IndexKind.String ? SyntaxKind.StringKeyword : SyntaxKind.NumberKeyword);
Expand Down
18 changes: 18 additions & 0 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1351,6 +1351,24 @@ namespace ts {
}
}

export interface UnderscoreEscapedMultiMap<T> extends UnderscoreEscapedMap<T[]> {
/**
* Adds the value to an array of values associated with the key, and returns the array.
* Creates the array if it does not already exist.
*/
add(key: __String, value: T): T[];
/**
* Removes a value from an array of values associated with the key.
* Does not preserve the order of those values.
* Does nothing if `key` is not in `map`, or `value` is not in `map[key]`.
*/
remove(key: __String, value: T): void;
}

export function createUnderscoreEscapedMultiMap<T>(): UnderscoreEscapedMultiMap<T> {
return createMultiMap<T>() as UnderscoreEscapedMultiMap<T>;
}

/**
* Tests whether a value is an array.
*/
Expand Down
14 changes: 14 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6353,4 +6353,18 @@ namespace ts {
}) as HeritageClause | undefined;
return heritageClause?.token === SyntaxKind.ImplementsKeyword || heritageClause?.parent.kind === SyntaxKind.InterfaceDeclaration;
}

export function isIdentifierTypeReference(node: Node): node is TypeReferenceNode & { typeName: Identifier } {
return isTypeReferenceNode(node) && isIdentifier(node.typeName);
}

export function arrayIsHomogeneous<T>(array: readonly T[], comparer: EqualityComparer<T> = equateValues) {
if (array.length < 2) return true;
const first = array[0];
for (let i = 1, length = array.length; i < length; i++) {
const target = array[i];
if (!comparer(first, target)) return false;
}
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
tests/cases/compiler/namespaceDisambiguationInUnion.ts(10,7): error TS2322: Type '{ type: string; }' is not assignable to type 'Foo.Yep | Bar.Yep'.
Type '{ type: string; }' is not assignable to type 'Yep'.
Types of property 'type' are incompatible.
Type 'string' is not assignable to type '"bar.yep"'.
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


==== tests/cases/compiler/namespaceDisambiguationInUnion.ts (2 errors) ====
namespace Foo {
export type Yep = { type: "foo.yep" };
}

namespace Bar {
export type Yep = { type: "bar.yep" };
}

const x = { type: "wat.nup" };
const val1: Foo.Yep | Bar.Yep = x;
~~~~
!!! error TS2322: Type '{ type: string; }' is not assignable to type 'Foo.Yep | Bar.Yep'.
!!! error TS2322: Type '{ type: string; }' is not assignable to type 'Yep'.
!!! error TS2322: Types of property 'type' are incompatible.
!!! error TS2322: Type 'string' is not assignable to type '"bar.yep"'.

const y = [{ type: "a" }, { type: "b" }];
const val2: [Foo.Yep, Bar.Yep] = y;
~~~~
!!! error TS2739: Type '{ type: string; }[]' is missing the following properties from type '[Foo.Yep, Bar.Yep]': 0, 1

21 changes: 21 additions & 0 deletions tests/baselines/reference/namespaceDisambiguationInUnion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
//// [namespaceDisambiguationInUnion.ts]
namespace Foo {
export type Yep = { type: "foo.yep" };
}

namespace Bar {
export type Yep = { type: "bar.yep" };
}

const x = { type: "wat.nup" };
const val1: Foo.Yep | Bar.Yep = x;

const y = [{ type: "a" }, { type: "b" }];
const val2: [Foo.Yep, Bar.Yep] = y;


//// [namespaceDisambiguationInUnion.js]
var x = { type: "wat.nup" };
var val1 = x;
var y = [{ type: "a" }, { type: "b" }];
var val2 = y;
42 changes: 42 additions & 0 deletions tests/baselines/reference/namespaceDisambiguationInUnion.symbols
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
=== tests/cases/compiler/namespaceDisambiguationInUnion.ts ===
namespace Foo {
>Foo : Symbol(Foo, Decl(namespaceDisambiguationInUnion.ts, 0, 0))

export type Yep = { type: "foo.yep" };
>Yep : Symbol(Yep, Decl(namespaceDisambiguationInUnion.ts, 0, 15))
>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 1, 21))
}

namespace Bar {
>Bar : Symbol(Bar, Decl(namespaceDisambiguationInUnion.ts, 2, 1))

export type Yep = { type: "bar.yep" };
>Yep : Symbol(Yep, Decl(namespaceDisambiguationInUnion.ts, 4, 15))
>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 5, 21))
}

const x = { type: "wat.nup" };
>x : Symbol(x, Decl(namespaceDisambiguationInUnion.ts, 8, 5))
>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 8, 11))

const val1: Foo.Yep | Bar.Yep = x;
>val1 : Symbol(val1, Decl(namespaceDisambiguationInUnion.ts, 9, 5))
>Foo : Symbol(Foo, Decl(namespaceDisambiguationInUnion.ts, 0, 0))
>Yep : Symbol(Foo.Yep, Decl(namespaceDisambiguationInUnion.ts, 0, 15))
>Bar : Symbol(Bar, Decl(namespaceDisambiguationInUnion.ts, 2, 1))
>Yep : Symbol(Bar.Yep, Decl(namespaceDisambiguationInUnion.ts, 4, 15))
>x : Symbol(x, Decl(namespaceDisambiguationInUnion.ts, 8, 5))

const y = [{ type: "a" }, { type: "b" }];
>y : Symbol(y, Decl(namespaceDisambiguationInUnion.ts, 11, 5))
>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 11, 12))
>type : Symbol(type, Decl(namespaceDisambiguationInUnion.ts, 11, 27))

const val2: [Foo.Yep, Bar.Yep] = y;
>val2 : Symbol(val2, Decl(namespaceDisambiguationInUnion.ts, 12, 5))
>Foo : Symbol(Foo, Decl(namespaceDisambiguationInUnion.ts, 0, 0))
>Yep : Symbol(Foo.Yep, Decl(namespaceDisambiguationInUnion.ts, 0, 15))
>Bar : Symbol(Bar, Decl(namespaceDisambiguationInUnion.ts, 2, 1))
>Yep : Symbol(Bar.Yep, Decl(namespaceDisambiguationInUnion.ts, 4, 15))
>y : Symbol(y, Decl(namespaceDisambiguationInUnion.ts, 11, 5))

41 changes: 41 additions & 0 deletions tests/baselines/reference/namespaceDisambiguationInUnion.types
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
=== tests/cases/compiler/namespaceDisambiguationInUnion.ts ===
namespace Foo {
export type Yep = { type: "foo.yep" };
>Yep : Yep
>type : "foo.yep"
}

namespace Bar {
export type Yep = { type: "bar.yep" };
>Yep : Yep
>type : "bar.yep"
}

const x = { type: "wat.nup" };
>x : { type: string; }
>{ type: "wat.nup" } : { type: string; }
>type : string
>"wat.nup" : "wat.nup"

const val1: Foo.Yep | Bar.Yep = x;
>val1 : Foo.Yep | Bar.Yep
>Foo : any
>Bar : any
>x : { type: string; }

const y = [{ type: "a" }, { type: "b" }];
>y : { type: string; }[]
>[{ type: "a" }, { type: "b" }] : { type: string; }[]
>{ type: "a" } : { type: string; }
>type : string
>"a" : "a"
>{ type: "b" } : { type: string; }
>type : string
>"b" : "b"

const val2: [Foo.Yep, Bar.Yep] = y;
>val2 : [Foo.Yep, Bar.Yep]
>Foo : any
>Bar : any
>y : { type: string; }[]

13 changes: 13 additions & 0 deletions tests/cases/compiler/namespaceDisambiguationInUnion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Foo {
export type Yep = { type: "foo.yep" };
}

namespace Bar {
export type Yep = { type: "bar.yep" };
}

const x = { type: "wat.nup" };
const val1: Foo.Yep | Bar.Yep = x;

const y = [{ type: "a" }, { type: "b" }];
const val2: [Foo.Yep, Bar.Yep] = y;

0 comments on commit 3e86f15

Please sign in to comment.