Skip to content

Commit 5d1ce0c

Browse files
committed
Print unions resulting from index operations with keyof if possible
1 parent e08e663 commit 5d1ce0c

15 files changed

+166
-46
lines changed

src/compiler/checker.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2609,7 +2609,11 @@ namespace ts {
26092609
if (!inTypeAlias && type.aliasSymbol && isTypeSymbolAccessible(type.aliasSymbol, context.enclosingDeclaration)) {
26102610
const name = symbolToTypeReferenceName(type.aliasSymbol);
26112611
const typeArgumentNodes = mapToTypeNodes(type.aliasTypeArguments, context);
2612-
return createTypeReferenceNode(name, typeArgumentNodes);
2612+
const ref = createTypeReferenceNode(name, typeArgumentNodes);
2613+
if (type.aliasKind === AliasKind.KeyOf) {
2614+
return createTypeOperatorNode(ref);
2615+
}
2616+
return ref;
26132617
}
26142618
if (type.flags & (TypeFlags.Union | TypeFlags.Intersection)) {
26152619
const types = type.flags & TypeFlags.Union ? formatUnionTypes((<UnionType>type).types) : (<IntersectionType>type).types;
@@ -3369,6 +3373,10 @@ namespace ts {
33693373
}
33703374
else if (!(flags & TypeFormatFlags.InTypeAlias) && type.aliasSymbol &&
33713375
((flags & TypeFormatFlags.UseAliasDefinedOutsideCurrentScope) || isTypeSymbolAccessible(type.aliasSymbol, enclosingDeclaration))) {
3376+
if (type.aliasKind === AliasKind.KeyOf) {
3377+
writer.writeKeyword("keyof");
3378+
writeSpace(writer);
3379+
}
33723380
const typeArguments = type.aliasTypeArguments;
33733381
writeSymbolTypeReference(type.aliasSymbol, typeArguments, 0, length(typeArguments), nextFlags);
33743382
}
@@ -7879,7 +7887,7 @@ namespace ts {
78797887
// expression constructs such as array literals and the || and ?: operators). Named types can
78807888
// circularly reference themselves and therefore cannot be subtype reduced during their declaration.
78817889
// For example, "type Item = string | (() => Item" is a named type that circularly references itself.
7882-
function getUnionType(types: Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
7890+
function getUnionType(types: Type[], unionReduction: UnionReduction = UnionReduction.Literal, aliasSymbol?: Symbol, aliasTypeArguments?: Type[], aliasKind?: AliasKind): Type {
78837891
if (types.length === 0) {
78847892
return neverType;
78857893
}
@@ -7906,7 +7914,7 @@ namespace ts {
79067914
typeSet.containsUndefined ? typeSet.containsNonWideningType ? undefinedType : undefinedWideningType :
79077915
neverType;
79087916
}
7909-
return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments);
7917+
return getUnionTypeFromSortedList(typeSet, aliasSymbol, aliasTypeArguments, aliasKind);
79107918
}
79117919

79127920
function getUnionTypePredicate(signatures: ReadonlyArray<Signature>): TypePredicate {
@@ -7946,7 +7954,7 @@ namespace ts {
79467954
}
79477955

79487956
// This function assumes the constituent type list is sorted and deduplicated.
7949-
function getUnionTypeFromSortedList(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: Type[]): Type {
7957+
function getUnionTypeFromSortedList(types: Type[], aliasSymbol?: Symbol, aliasTypeArguments?: Type[], aliasKind?: AliasKind): Type {
79507958
if (types.length === 0) {
79517959
return neverType;
79527960
}
@@ -7968,6 +7976,7 @@ namespace ts {
79687976
*/
79697977
type.aliasSymbol = aliasSymbol;
79707978
type.aliasTypeArguments = aliasTypeArguments;
7979+
type.aliasKind = aliasKind;
79717980
}
79727981
return type;
79737982
}
@@ -8089,7 +8098,19 @@ namespace ts {
80898098
}
80908099

80918100
function getLiteralTypeFromPropertyNames(type: Type) {
8092-
return getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName));
8101+
let aliasSymbol: Symbol;
8102+
let aliasTypeArguments: Type[];
8103+
if (type.aliasSymbol && !type.aliasKind) {
8104+
aliasSymbol = type.aliasSymbol;
8105+
aliasTypeArguments = type.aliasTypeArguments;
8106+
}
8107+
else if (type.aliasSymbol && type.aliasKind === AliasKind.KeyOf) {
8108+
aliasSymbol = getGlobalSymbol("String" as __String, SymbolFlags.Type, /*diagnostic*/ undefined);
8109+
}
8110+
else if (type.symbol && type.symbol.flags & SymbolFlags.Interface) {
8111+
aliasSymbol = type.symbol;
8112+
}
8113+
return getUnionType(map(getPropertiesOfType(type), getLiteralTypeFromPropertyName), UnionReduction.Literal, aliasSymbol, aliasTypeArguments, AliasKind.KeyOf);
80938114
}
80948115

80958116
function getIndexType(type: Type): Type {

src/compiler/types.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3449,6 +3449,12 @@ namespace ts {
34493449
pattern?: DestructuringPattern; // Destructuring pattern represented by type (if any)
34503450
aliasSymbol?: Symbol; // Alias associated with type
34513451
aliasTypeArguments?: Type[]; // Alias type arguments (if any)
3452+
aliasKind?: AliasKind; // Is eg, `keyof AliasSymbol<AliasTypeArguments>` and not `AliasSymbol<AliasTypeArguments>`
3453+
}
3454+
3455+
export const enum AliasKind {
3456+
None,
3457+
KeyOf
34523458
}
34533459

34543460
/* @internal */

tests/baselines/reference/api/tsserverlibrary.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,11 @@ declare namespace ts {
20372037
pattern?: DestructuringPattern;
20382038
aliasSymbol?: Symbol;
20392039
aliasTypeArguments?: Type[];
2040+
aliasKind?: AliasKind;
2041+
}
2042+
enum AliasKind {
2043+
None = 0,
2044+
KeyOf = 1,
20402045
}
20412046
interface LiteralType extends Type {
20422047
value: string | number;

tests/baselines/reference/api/typescript.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2037,6 +2037,11 @@ declare namespace ts {
20372037
pattern?: DestructuringPattern;
20382038
aliasSymbol?: Symbol;
20392039
aliasTypeArguments?: Type[];
2040+
aliasKind?: AliasKind;
2041+
}
2042+
enum AliasKind {
2043+
None = 0,
2044+
KeyOf = 1,
20402045
}
20412046
interface LiteralType extends Type {
20422047
value: string | number;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//// [declarationUsesKeyofAliasWherePossible.ts]
2+
var Foo = class Foo {
3+
method<T extends keyof ElementTagNameMap>() {};
4+
}
5+
6+
class Bar {
7+
method<T extends keyof ElementTagNameMap>() {};
8+
}
9+
10+
//// [declarationUsesKeyofAliasWherePossible.js]
11+
var Foo = /** @class */ (function () {
12+
function Foo() {
13+
}
14+
Foo.prototype.method = function () { };
15+
;
16+
return Foo;
17+
}());
18+
var Bar = /** @class */ (function () {
19+
function Bar() {
20+
}
21+
Bar.prototype.method = function () { };
22+
;
23+
return Bar;
24+
}());
25+
26+
27+
//// [declarationUsesKeyofAliasWherePossible.d.ts]
28+
declare var Foo: {
29+
new (): {
30+
method<T extends keyof ElementTagNameMap>(): void;
31+
};
32+
};
33+
declare class Bar {
34+
method<T extends keyof ElementTagNameMap>(): void;
35+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
=== tests/cases/compiler/declarationUsesKeyofAliasWherePossible.ts ===
2+
var Foo = class Foo {
3+
>Foo : Symbol(Foo, Decl(declarationUsesKeyofAliasWherePossible.ts, 0, 3))
4+
>Foo : Symbol(Foo, Decl(declarationUsesKeyofAliasWherePossible.ts, 0, 9))
5+
6+
method<T extends keyof ElementTagNameMap>() {};
7+
>method : Symbol(Foo.method, Decl(declarationUsesKeyofAliasWherePossible.ts, 0, 21))
8+
>T : Symbol(T, Decl(declarationUsesKeyofAliasWherePossible.ts, 1, 11))
9+
>ElementTagNameMap : Symbol(ElementTagNameMap, Decl(lib.dom.d.ts, --, --))
10+
}
11+
12+
class Bar {
13+
>Bar : Symbol(Bar, Decl(declarationUsesKeyofAliasWherePossible.ts, 2, 1))
14+
15+
method<T extends keyof ElementTagNameMap>() {};
16+
>method : Symbol(Bar.method, Decl(declarationUsesKeyofAliasWherePossible.ts, 4, 11))
17+
>T : Symbol(T, Decl(declarationUsesKeyofAliasWherePossible.ts, 5, 11))
18+
>ElementTagNameMap : Symbol(ElementTagNameMap, Decl(lib.dom.d.ts, --, --))
19+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
=== tests/cases/compiler/declarationUsesKeyofAliasWherePossible.ts ===
2+
var Foo = class Foo {
3+
>Foo : typeof Foo
4+
>class Foo { method<T extends keyof ElementTagNameMap>() {};} : typeof Foo
5+
>Foo : typeof Foo
6+
7+
method<T extends keyof ElementTagNameMap>() {};
8+
>method : <T extends keyof ElementTagNameMap>() => void
9+
>T : T
10+
>ElementTagNameMap : ElementTagNameMap
11+
}
12+
13+
class Bar {
14+
>Bar : Bar
15+
16+
method<T extends keyof ElementTagNameMap>() {};
17+
>method : <T extends keyof ElementTagNameMap>() => void
18+
>T : T
19+
>ElementTagNameMap : ElementTagNameMap
20+
}

tests/baselines/reference/doubleUnderscoreMappedTypes.types

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,12 +27,12 @@ const ok: Properties = {
2727

2828
// As expected, "__property2" is indeed a key of the type
2929
type Keys = keyof Properties;
30-
>Keys : "property1" | "__property2"
30+
>Keys : keyof Properties
3131
>Properties : Properties
3232

3333
const k: Keys = "__property2"; // ok
34-
>k : "property1" | "__property2"
35-
>Keys : "property1" | "__property2"
34+
>k : keyof Properties
35+
>Keys : keyof Properties
3636
>"__property2" : "__property2"
3737

3838
// This should be valid

tests/baselines/reference/keyofAndIndexedAccess.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ type K10 = keyof Shape; // "name" | "width" | "height" | "visible"
8888
>Shape : Shape
8989

9090
type K11 = keyof Shape[]; // "length" | "toString" | ...
91-
>K11 : "length" | "toString" | "toLocaleString" | "push" | "pop" | "concat" | "join" | "reverse" | "shift" | "slice" | "sort" | "splice" | "unshift" | "indexOf" | "lastIndexOf" | "every" | "some" | "forEach" | "map" | "filter" | "reduce" | "reduceRight"
91+
>K11 : keyof Array
9292
>Shape : Shape
9393

9494
type K12 = keyof Dictionary<Shape>; // string
@@ -100,7 +100,7 @@ type K13 = keyof {}; // never
100100
>K13 : never
101101

102102
type K14 = keyof Object; // "constructor" | "toString" | ...
103-
>K14 : "toString" | "toLocaleString" | "valueOf" | "constructor" | "hasOwnProperty" | "isPrototypeOf" | "propertyIsEnumerable"
103+
>K14 : keyof Object
104104
>Object : Object
105105

106106
type K15 = keyof E; // "toString" | "toFixed" | "toExponential" | ...

tests/baselines/reference/keyofAndIndexedAccessErrors.types

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,27 +26,27 @@ type T00 = keyof K0; // Error
2626
>K0 : No type information available!
2727

2828
type T01 = keyof Object;
29-
>T01 : "toString" | "constructor" | "toLocaleString" | "valueOf" | "hasOwnProperty" | "isPrototypeOf" | "propertyIsEnumerable"
29+
>T01 : keyof Object
3030
>Object : Object
3131

3232
type T02 = keyof keyof Object;
33-
>T02 : "length" | "toString" | "valueOf" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr"
33+
>T02 : keyof String
3434
>Object : Object
3535

3636
type T03 = keyof keyof keyof Object;
37-
>T03 : "length" | "toString" | "valueOf" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr"
37+
>T03 : keyof String
3838
>Object : Object
3939

4040
type T04 = keyof keyof keyof keyof Object;
41-
>T04 : "length" | "toString" | "valueOf" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr"
41+
>T04 : keyof String
4242
>Object : Object
4343

4444
type T05 = keyof keyof keyof keyof keyof Object;
45-
>T05 : "length" | "toString" | "valueOf" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr"
45+
>T05 : keyof String
4646
>Object : Object
4747

4848
type T06 = keyof keyof keyof keyof keyof keyof Object;
49-
>T06 : "length" | "toString" | "valueOf" | "charAt" | "charCodeAt" | "concat" | "indexOf" | "lastIndexOf" | "localeCompare" | "match" | "replace" | "search" | "slice" | "split" | "substring" | "toLowerCase" | "toLocaleLowerCase" | "toUpperCase" | "toLocaleUpperCase" | "trim" | "substr"
49+
>T06 : keyof String
5050
>Object : Object
5151

5252
type T10 = Shape["name"];

0 commit comments

Comments
 (0)