Skip to content

Commit 9613ca8

Browse files
committed
keysof type operator
1 parent 2a62917 commit 9613ca8

File tree

5 files changed

+142
-10
lines changed

5 files changed

+142
-10
lines changed

src/compiler/checker.ts

+50
Original file line numberDiff line numberDiff line change
@@ -2165,6 +2165,11 @@ namespace ts {
21652165
else if (type.flags & TypeFlags.NumberLiteral) {
21662166
writer.writeStringLiteral((<LiteralType>type).text);
21672167
}
2168+
else if (type.flags & TypeFlags.KeysQuery) {
2169+
writer.writeKeyword("keysof");
2170+
writeSpace(writer);
2171+
writeType((<KeysQueryType>type).baseType, flags);
2172+
}
21682173
else {
21692174
// Should never get here
21702175
// { ... }
@@ -5145,6 +5150,41 @@ namespace ts {
51455150
return links.resolvedType;
51465151
}
51475152

5153+
function resolveKeysQueryType(type: KeysQueryType): Type {
5154+
// Get index types to include in the union if need be
5155+
let indexTypes: Type[];
5156+
const numberIndex = getIndexInfoOfType(type.baseType, IndexKind.Number);
5157+
const stringIndex = getIndexInfoOfType(type.baseType, IndexKind.String);
5158+
if (numberIndex) {
5159+
indexTypes = [numberType];
5160+
}
5161+
if (stringIndex) {
5162+
indexTypes ? indexTypes.push(stringType) : indexTypes = [stringType];
5163+
}
5164+
// Skip any essymbol members and remember to unescape the identifier before making a type from it
5165+
const memberTypes = concatenate(indexTypes, map(filter(getPropertiesOfType(type.baseType),
5166+
symbol => !startsWith(symbol.name, "__@")),
5167+
symbol => getLiteralTypeForText(TypeFlags.StringLiteral, unescapeIdentifier(symbol.name))
5168+
));
5169+
return memberTypes ? getUnionType(memberTypes) : neverType;
5170+
}
5171+
5172+
function getKeysQueryType(type: Type): KeysQueryType {
5173+
const queryType = createType(TypeFlags.KeysQuery) as KeysQueryType;
5174+
queryType.baseType = type;
5175+
return queryType;
5176+
}
5177+
5178+
function getTypeFromKeysQueryNode(node: KeysQueryNode): Type {
5179+
const links = getNodeLinks(node);
5180+
if (!links.resolvedType) {
5181+
// The expression is processed as an identifier expression, which we
5182+
// then store the resulting type of into a "KeysQuery" type
5183+
links.resolvedType = getKeysQueryType(getTypeFromTypeNode(node.type));
5184+
}
5185+
return links.resolvedType;
5186+
}
5187+
51485188
function getTypeOfGlobalSymbol(symbol: Symbol, arity: number): ObjectType {
51495189

51505190
function getTypeDeclaration(symbol: Symbol): Declaration {
@@ -5569,6 +5609,8 @@ namespace ts {
55695609
return getTypeFromTypeReference(<ExpressionWithTypeArguments>node);
55705610
case SyntaxKind.TypeQuery:
55715611
return getTypeFromTypeQueryNode(<TypeQueryNode>node);
5612+
case SyntaxKind.KeysQuery:
5613+
return getTypeFromKeysQueryNode(<KeysQueryNode>node);
55725614
case SyntaxKind.ArrayType:
55735615
case SyntaxKind.JSDocArrayType:
55745616
return getTypeFromArrayTypeNode(<ArrayTypeNode>node);
@@ -5855,6 +5897,9 @@ namespace ts {
58555897
if (type.flags & TypeFlags.Intersection) {
58565898
return getIntersectionType(instantiateList((<IntersectionType>type).types, mapper, instantiateType), type.aliasSymbol, mapper.targetTypes);
58575899
}
5900+
if (type.flags & TypeFlags.KeysQuery) {
5901+
return getKeysQueryType(instantiateType((<KeysQueryType>type).baseType, mapper));
5902+
}
58585903
}
58595904
return type;
58605905
}
@@ -6185,6 +6230,8 @@ namespace ts {
61856230
if (source.flags & (TypeFlags.Number | TypeFlags.NumberLiteral) && target.flags & TypeFlags.Enum) return true;
61866231
if (source.flags & TypeFlags.NumberLiteral && target.flags & TypeFlags.EnumLiteral && (<LiteralType>source).text === (<LiteralType>target).text) return true;
61876232
}
6233+
if (source.flags & TypeFlags.KeysQuery) return isTypeRelatedTo(resolveKeysQueryType(source as KeysQueryType), target, relation);
6234+
if (target.flags & TypeFlags.KeysQuery) return isTypeRelatedTo(source, resolveKeysQueryType(target as KeysQueryType), relation);
61886235
return false;
61896236
}
61906237

@@ -13356,6 +13403,9 @@ namespace ts {
1335613403
}
1335713404
contextualType = apparentType;
1335813405
}
13406+
if (contextualType.flags & TypeFlags.KeysQuery) {
13407+
return true;
13408+
}
1335913409
if (type.flags & TypeFlags.String) {
1336013410
return maybeTypeOfKind(contextualType, TypeFlags.StringLiteral);
1336113411
}

src/compiler/parser.ts

+26
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,8 @@ namespace ts {
123123
visitNode(cbNode, (<TypePredicateNode>node).type);
124124
case SyntaxKind.TypeQuery:
125125
return visitNode(cbNode, (<TypeQueryNode>node).exprName);
126+
case SyntaxKind.KeysQuery:
127+
return visitNode(cbNode, (<KeysQueryNode>node).type);
126128
case SyntaxKind.TypeLiteral:
127129
return visitNodes(cbNodes, (<TypeLiteralNode>node).members);
128130
case SyntaxKind.ArrayType:
@@ -2018,6 +2020,13 @@ namespace ts {
20182020
return finishNode(node);
20192021
}
20202022

2023+
function parseKeysQuery(): KeysQueryNode {
2024+
const node = <KeysQueryNode>createNode(SyntaxKind.KeysQuery);
2025+
parseExpected(SyntaxKind.KeysOfKeyword);
2026+
node.type = parseType();
2027+
return finishNode(node);
2028+
}
2029+
20212030
function parseTypeParameter(): TypeParameterDeclaration {
20222031
const node = <TypeParameterDeclaration>createNode(SyntaxKind.TypeParameter);
20232032
node.name = parseIdentifier();
@@ -2420,6 +2429,21 @@ namespace ts {
24202429
return nextToken() === SyntaxKind.NumericLiteral;
24212430
}
24222431

2432+
function nextTokenIsKeysQueryTypeTerminator() {
2433+
const next = nextToken();
2434+
switch (next) {
2435+
case SyntaxKind.DotToken:
2436+
case SyntaxKind.CommaToken:
2437+
case SyntaxKind.SemicolonToken:
2438+
case SyntaxKind.OpenBraceToken:
2439+
case SyntaxKind.CloseBraceToken:
2440+
case SyntaxKind.CloseParenToken:
2441+
case SyntaxKind.EqualsGreaterThanToken:
2442+
return true;
2443+
}
2444+
return false;
2445+
}
2446+
24232447
function parseNonArrayType(): TypeNode {
24242448
switch (token()) {
24252449
case SyntaxKind.AnyKeyword:
@@ -2453,6 +2477,8 @@ namespace ts {
24532477
}
24542478
case SyntaxKind.TypeOfKeyword:
24552479
return parseTypeQuery();
2480+
case SyntaxKind.KeysOfKeyword:
2481+
return lookAhead(nextTokenIsKeysQueryTypeTerminator) ? parseTypeReference() : parseKeysQuery();
24562482
case SyntaxKind.OpenBraceToken:
24572483
return parseTypeLiteral();
24582484
case SyntaxKind.OpenBracketToken:

src/compiler/scanner.ts

+1
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,7 @@ namespace ts {
125125
"async": SyntaxKind.AsyncKeyword,
126126
"await": SyntaxKind.AwaitKeyword,
127127
"of": SyntaxKind.OfKeyword,
128+
"keysof": SyntaxKind.KeysOfKeyword,
128129
"{": SyntaxKind.OpenBraceToken,
129130
"}": SyntaxKind.CloseBraceToken,
130131
"(": SyntaxKind.OpenParenToken,

src/compiler/types.ts

+24-10
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,8 @@ namespace ts {
179179
UndefinedKeyword,
180180
FromKeyword,
181181
GlobalKeyword,
182-
OfKeyword, // LastKeyword and LastToken
182+
OfKeyword,
183+
KeysOfKeyword, // LastKeyword and LastToken
183184

184185
// Parse tree nodes
185186

@@ -207,6 +208,7 @@ namespace ts {
207208
FunctionType,
208209
ConstructorType,
209210
TypeQuery,
211+
KeysQuery,
210212
TypeLiteral,
211213
ArrayType,
212214
TupleType,
@@ -361,7 +363,7 @@ namespace ts {
361363
FirstReservedWord = BreakKeyword,
362364
LastReservedWord = WithKeyword,
363365
FirstKeyword = BreakKeyword,
364-
LastKeyword = OfKeyword,
366+
LastKeyword = KeysOfKeyword,
365367
FirstFutureReservedWord = ImplementsKeyword,
366368
LastFutureReservedWord = YieldKeyword,
367369
FirstTypeNode = TypePredicate,
@@ -766,6 +768,12 @@ namespace ts {
766768
exprName: EntityName;
767769
}
768770

771+
// @kind(SyntaxKind.KeysQuery)
772+
export interface KeysQueryNode extends TypeNode {
773+
" keys query brand ": never;
774+
type: TypeNode;
775+
}
776+
769777
// A TypeLiteral is the declaration node for an anonymous symbol.
770778
// @kind(SyntaxKind.TypeLiteral)
771779
export interface TypeLiteralNode extends TypeNode, Declaration {
@@ -2259,19 +2267,20 @@ namespace ts {
22592267
Union = 1 << 19, // Union (T | U)
22602268
Intersection = 1 << 20, // Intersection (T & U)
22612269
Anonymous = 1 << 21, // Anonymous
2262-
Instantiated = 1 << 22, // Instantiated anonymous type
2270+
KeysQuery = 1 << 22, // keysof <type expr> type
2271+
Instantiated = 1 << 23, // Instantiated anonymous type
22632272
/* @internal */
2264-
ObjectLiteral = 1 << 23, // Originates in an object literal
2273+
ObjectLiteral = 1 << 24, // Originates in an object literal
22652274
/* @internal */
2266-
FreshObjectLiteral = 1 << 24, // Fresh object literal type
2275+
FreshObjectLiteral = 1 << 25, // Fresh object literal type
22672276
/* @internal */
2268-
ContainsWideningType = 1 << 25, // Type is or contains undefined or null widening type
2277+
ContainsWideningType = 1 << 26, // Type is or contains undefined or null widening type
22692278
/* @internal */
2270-
ContainsObjectLiteral = 1 << 26, // Type is or contains object literal type
2279+
ContainsObjectLiteral = 1 << 27, // Type is or contains object literal type
22712280
/* @internal */
2272-
ContainsAnyFunctionType = 1 << 27, // Type is or contains object literal type
2273-
ThisType = 1 << 28, // This type
2274-
ObjectLiteralPatternWithComputedProperties = 1 << 29, // Object literal type implied by binding pattern has computed properties
2281+
ContainsAnyFunctionType = 1 << 28, // Type is or contains object literal type
2282+
ThisType = 1 << 29, // This type
2283+
ObjectLiteralPatternWithComputedProperties = 1 << 30, // Object literal type implied by binding pattern has computed properties
22752284

22762285
/* @internal */
22772286
Nullable = Undefined | Null,
@@ -2338,6 +2347,11 @@ namespace ts {
23382347
// Object types (TypeFlags.ObjectType)
23392348
export interface ObjectType extends Type { }
23402349

2350+
// KeysOf Query types (TypeFlags.KeysQuery)
2351+
export interface KeysQueryType extends Type {
2352+
baseType: Type;
2353+
}
2354+
23412355
// Class and interface types (TypeFlags.Class and TypeFlags.Interface)
23422356
export interface InterfaceType extends ObjectType {
23432357
typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// First, check that the new keyword doesn't interfere
2+
// with any other potential uses of the identifier `keysof`.
3+
namespace keysof {
4+
export type name = {};
5+
}
6+
function old(a: keysof.name) {}
7+
8+
type keysof = {a: string};
9+
function old2(a: keysof, b: keysof): keysof { return {a: ""}; }
10+
var old3 = (): keysof => ({a: ""});
11+
12+
function disambiguate1(a: keysof ({b: number})) {}
13+
function disambiguate2(): keysof ({a}) {return "a";}
14+
15+
// Then check that the `keysof` operator works as expected
16+
interface FooBar {
17+
foo: "yes";
18+
bar: "no";
19+
}
20+
21+
function pick(thing: FooBar, member: keysof FooBar): typeof member {
22+
return thing[member];
23+
}
24+
25+
const a = pick({foo: "yes", "bar": "no"}, "bar");
26+
27+
function pick2<T>(thing: T, member: keysof T): keysof T {
28+
return member;
29+
}
30+
const realA: "a" = "a";
31+
const x = pick2({a: "", b: 0}, realA);
32+
const xx = pick2({a: "", b: 0}, "a");
33+
const item = {0: "yes", 1: "no"};
34+
const xxx = pick2(item, "0");
35+
const xxxx = pick2(item, 0);
36+
item["0"].charCodeAt(0);
37+
item[0].charCodeAt(0);
38+
39+
function pick3<U, T extends keysof U>(obj: U, key: T) {
40+
key
41+
}

0 commit comments

Comments
 (0)