diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 25a35c307f4c0..4400b82ae0e5c 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2165,6 +2165,11 @@ namespace ts { else if (type.flags & TypeFlags.NumberLiteral) { writer.writeStringLiteral((type).text); } + else if (type.flags & TypeFlags.KeysQuery) { + writer.writeKeyword("keysof"); + writeSpace(writer); + writeType((type).baseType, flags); + } else { // Should never get here // { ... } @@ -5145,6 +5150,34 @@ namespace ts { return links.resolvedType; } + function resolveKeysQueryType(type: KeysQueryType): Type { + if (!type.resolvedBaseUnion) { + // Skip any essymbol members and remember to unescape the identifier before making a type from it + const memberTypes = map(filter(getPropertiesOfType(type.baseType), + symbol => !startsWith(symbol.name, "__@")), + symbol => getLiteralTypeForText(TypeFlags.StringLiteral, unescapeIdentifier(symbol.name)) + ); + type.resolvedBaseUnion = memberTypes ? getUnionType(memberTypes) : neverType; + } + return type.resolvedBaseUnion; + } + + function getKeysQueryType(type: Type): KeysQueryType { + const queryType = createType(TypeFlags.KeysQuery) as KeysQueryType; + queryType.baseType = type; + return queryType; + } + + function getTypeFromKeysQueryNode(node: KeysQueryNode): Type { + const links = getNodeLinks(node); + if (!links.resolvedType) { + // The expression is processed as an identifier expression, which we + // then store the resulting type of into a "KeysQuery" type + links.resolvedType = getKeysQueryType(getTypeFromTypeNode(node.type)); + } + return links.resolvedType; + } + function getTypeOfGlobalSymbol(symbol: Symbol, arity: number): ObjectType { function getTypeDeclaration(symbol: Symbol): Declaration { @@ -5569,6 +5602,8 @@ namespace ts { return getTypeFromTypeReference(node); case SyntaxKind.TypeQuery: return getTypeFromTypeQueryNode(node); + case SyntaxKind.KeysQuery: + return getTypeFromKeysQueryNode(node); case SyntaxKind.ArrayType: case SyntaxKind.JSDocArrayType: return getTypeFromArrayTypeNode(node); @@ -5855,6 +5890,9 @@ namespace ts { if (type.flags & TypeFlags.Intersection) { return getIntersectionType(instantiateList((type).types, mapper, instantiateType), type.aliasSymbol, mapper.targetTypes); } + if (type.flags & TypeFlags.KeysQuery) { + return getKeysQueryType(instantiateType((type).baseType, mapper)); + } } return type; } @@ -6185,6 +6223,8 @@ namespace ts { if (source.flags & (TypeFlags.Number | TypeFlags.NumberLiteral) && target.flags & TypeFlags.Enum) return true; if (source.flags & TypeFlags.NumberLiteral && target.flags & TypeFlags.EnumLiteral && (source).text === (target).text) return true; } + if (source.flags & TypeFlags.KeysQuery) return isTypeRelatedTo(resolveKeysQueryType(source as KeysQueryType), target, relation); + if (target.flags & TypeFlags.KeysQuery) return isTypeRelatedTo(source, resolveKeysQueryType(target as KeysQueryType), relation); return false; } @@ -13356,6 +13396,9 @@ namespace ts { } contextualType = apparentType; } + if (contextualType.flags & TypeFlags.KeysQuery) { + return type === stringType; + } if (type.flags & TypeFlags.String) { return maybeTypeOfKind(contextualType, TypeFlags.StringLiteral); } diff --git a/src/compiler/parser.ts b/src/compiler/parser.ts index aca7d745730cd..23e144b7c50cf 100644 --- a/src/compiler/parser.ts +++ b/src/compiler/parser.ts @@ -123,6 +123,8 @@ namespace ts { visitNode(cbNode, (node).type); case SyntaxKind.TypeQuery: return visitNode(cbNode, (node).exprName); + case SyntaxKind.KeysQuery: + return visitNode(cbNode, (node).type); case SyntaxKind.TypeLiteral: return visitNodes(cbNodes, (node).members); case SyntaxKind.ArrayType: @@ -2018,6 +2020,13 @@ namespace ts { return finishNode(node); } + function parseKeysQuery(): KeysQueryNode { + const node = createNode(SyntaxKind.KeysQuery); + parseExpected(SyntaxKind.KeysOfKeyword); + node.type = parseType(); + return finishNode(node); + } + function parseTypeParameter(): TypeParameterDeclaration { const node = createNode(SyntaxKind.TypeParameter); node.name = parseIdentifier(); @@ -2420,6 +2429,21 @@ namespace ts { return nextToken() === SyntaxKind.NumericLiteral; } + function nextTokenIsKeysQueryTypeTerminator() { + const next = nextToken(); + switch (next) { + case SyntaxKind.DotToken: + case SyntaxKind.CommaToken: + case SyntaxKind.SemicolonToken: + case SyntaxKind.OpenBraceToken: + case SyntaxKind.CloseBraceToken: + case SyntaxKind.CloseParenToken: + case SyntaxKind.EqualsGreaterThanToken: + return true; + } + return false; + } + function parseNonArrayType(): TypeNode { switch (token()) { case SyntaxKind.AnyKeyword: @@ -2453,6 +2477,8 @@ namespace ts { } case SyntaxKind.TypeOfKeyword: return parseTypeQuery(); + case SyntaxKind.KeysOfKeyword: + return lookAhead(nextTokenIsKeysQueryTypeTerminator) ? parseTypeReference() : parseKeysQuery(); case SyntaxKind.OpenBraceToken: return parseTypeLiteral(); case SyntaxKind.OpenBracketToken: diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 134dc489b2ae8..bd6573098f071 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -125,6 +125,7 @@ namespace ts { "async": SyntaxKind.AsyncKeyword, "await": SyntaxKind.AwaitKeyword, "of": SyntaxKind.OfKeyword, + "keysof": SyntaxKind.KeysOfKeyword, "{": SyntaxKind.OpenBraceToken, "}": SyntaxKind.CloseBraceToken, "(": SyntaxKind.OpenParenToken, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index d144c984a3fc2..d2421adaf5d99 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -180,6 +180,7 @@ namespace ts { FromKeyword, GlobalKeyword, OfKeyword, // LastKeyword and LastToken + KeysOfKeyword, // Parse tree nodes @@ -207,6 +208,7 @@ namespace ts { FunctionType, ConstructorType, TypeQuery, + KeysQuery, TypeLiteral, ArrayType, TupleType, @@ -766,6 +768,12 @@ namespace ts { exprName: EntityName; } + // @kind(SyntaxKind.KeysQuery) + export interface KeysQueryNode extends TypeNode { + " keys query brand ": never; + type: TypeNode; + } + // A TypeLiteral is the declaration node for an anonymous symbol. // @kind(SyntaxKind.TypeLiteral) export interface TypeLiteralNode extends TypeNode, Declaration { @@ -2259,19 +2267,20 @@ namespace ts { Union = 1 << 19, // Union (T | U) Intersection = 1 << 20, // Intersection (T & U) Anonymous = 1 << 21, // Anonymous - Instantiated = 1 << 22, // Instantiated anonymous type + KeysQuery = 1 << 22, // keysof type + Instantiated = 1 << 23, // Instantiated anonymous type /* @internal */ - ObjectLiteral = 1 << 23, // Originates in an object literal + ObjectLiteral = 1 << 24, // Originates in an object literal /* @internal */ - FreshObjectLiteral = 1 << 24, // Fresh object literal type + FreshObjectLiteral = 1 << 25, // Fresh object literal type /* @internal */ - ContainsWideningType = 1 << 25, // Type is or contains undefined or null widening type + ContainsWideningType = 1 << 26, // Type is or contains undefined or null widening type /* @internal */ - ContainsObjectLiteral = 1 << 26, // Type is or contains object literal type + ContainsObjectLiteral = 1 << 27, // Type is or contains object literal type /* @internal */ - ContainsAnyFunctionType = 1 << 27, // Type is or contains object literal type - ThisType = 1 << 28, // This type - ObjectLiteralPatternWithComputedProperties = 1 << 29, // Object literal type implied by binding pattern has computed properties + ContainsAnyFunctionType = 1 << 28, // Type is or contains object literal type + ThisType = 1 << 29, // This type + ObjectLiteralPatternWithComputedProperties = 1 << 30, // Object literal type implied by binding pattern has computed properties /* @internal */ Nullable = Undefined | Null, @@ -2283,7 +2292,7 @@ namespace ts { Intrinsic = Any | String | Number | Boolean | BooleanLiteral | ESSymbol | Void | Undefined | Null | Never, /* @internal */ Primitive = String | Number | Boolean | Enum | ESSymbol | Void | Undefined | Null | Literal, - StringLike = String | StringLiteral, + StringLike = String | StringLiteral | KeysQuery, NumberLike = Number | NumberLiteral | Enum | EnumLiteral, BooleanLike = Boolean | BooleanLiteral, EnumLike = Enum | EnumLiteral, @@ -2338,6 +2347,14 @@ namespace ts { // Object types (TypeFlags.ObjectType) export interface ObjectType extends Type { } + // KeysOf Query types (TypeFlags.KeysQuery) + export interface KeysQueryType extends Type { + baseType: Type; + /* @internal */ + // Do not access directly. Use resolveKeysQueryType. + resolvedBaseUnion?: Type; + } + // Class and interface types (TypeFlags.Class and TypeFlags.Interface) export interface InterfaceType extends ObjectType { typeParameters: TypeParameter[]; // Type parameters (undefined if non-generic) diff --git a/src/services/formatting/rules.ts b/src/services/formatting/rules.ts index 1839726b9ad9e..04df2951d7dfb 100644 --- a/src/services/formatting/rules.ts +++ b/src/services/formatting/rules.ts @@ -318,7 +318,7 @@ namespace ts.formatting { this.NoSpaceBeforeComma = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.CommaToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Delete)); - this.SpaceAfterCertainKeywords = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword, SyntaxKind.AwaitKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); + this.SpaceAfterCertainKeywords = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.VarKeyword, SyntaxKind.ThrowKeyword, SyntaxKind.NewKeyword, SyntaxKind.DeleteKeyword, SyntaxKind.ReturnKeyword, SyntaxKind.TypeOfKeyword, SyntaxKind.KeysOfKeyword, SyntaxKind.AwaitKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext), RuleAction.Space)); this.SpaceAfterLetConstInVariableDeclaration = new Rule(RuleDescriptor.create4(Shared.TokenRange.FromTokens([SyntaxKind.LetKeyword, SyntaxKind.ConstKeyword]), Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsStartOfVariableDeclarationList), RuleAction.Space)); this.NoSpaceBeforeOpenParenInFuncCall = new Rule(RuleDescriptor.create2(Shared.TokenRange.Any, SyntaxKind.OpenParenToken), RuleOperation.create2(new RuleOperationContext(Rules.IsNonJsxSameLineTokenContext, Rules.IsFunctionCallOrNewContext, Rules.IsPreviousTokenNotComma), RuleAction.Delete)); this.SpaceAfterFunctionInFuncDecl = new Rule(RuleDescriptor.create3(SyntaxKind.FunctionKeyword, Shared.TokenRange.Any), RuleOperation.create2(new RuleOperationContext(Rules.IsFunctionDeclContext), RuleAction.Space)); diff --git a/tests/baselines/reference/simpleKeysofTest.js b/tests/baselines/reference/simpleKeysofTest.js new file mode 100644 index 0000000000000..d3d77fa256e53 --- /dev/null +++ b/tests/baselines/reference/simpleKeysofTest.js @@ -0,0 +1,73 @@ +//// [simpleKeysofTest.ts] +// First, check that the new keyword doesn't interfere +// with any other potential uses of the identifier `keysof`. +namespace keysof { + export type name = {}; +} +function old(a: keysof.name) {} + +type keysof = {a: string}; +function old2(a: keysof, b: keysof): keysof { return {a: ""}; } +var old3 = (): keysof => ({a: ""}); + +function disambiguate1(a: keysof ({b: number})) {} +function disambiguate2(): keysof ({a}) {return "a";} + +// Then check that the `keysof` operator works as expected +interface FooBar { + foo: "yes"; + bar: "no"; + [index: string]: string; // Remove when the indexer is patched to passthru unions +} + +function pick(thing: FooBar, member: keysof FooBar) { + return thing[member]; +} + +const a = pick({foo: "yes", "bar": "no"}, "bar"); + +function pick2(thing: T, member: keysof T): keysof T { + return member; +} +const realA: "a" = "a"; +const x = pick2({a: "", b: 0}, realA); +const xx = pick2({a: "", b: 0}, "a"); +const item = {0: "yes", 1: "no"}; +const xxx = pick2(item, "0"); + +function annotate(obj: U, key: T): U & {annotation: T} { + const ret = obj as U & {annotation: T}; + if (key === "annotation") return ret; // Already annotated + ret.annotation = key; + return ret; +} + +annotate({a: "things", b: "stuff"}, "b").annotation === "b"; + + +//// [simpleKeysofTest.js] +function old(a) { } +function old2(a, b) { return { a: "" }; } +var old3 = function () { return ({ a: "" }); }; +function disambiguate1(a) { } +function disambiguate2() { return "a"; } +function pick(thing, member) { + return thing[member]; +} +var a = pick({ foo: "yes", "bar": "no" }, "bar"); +function pick2(thing, member) { + return member; +} +var realA = "a"; +var x = pick2({ a: "", b: 0 }, realA); +var xx = pick2({ a: "", b: 0 }, "a"); +var item = { 0: "yes", 1: "no" }; +var xxx = pick2(item, "0"); +function annotate(obj, key) { + var ret = obj; + if (key === "annotation") + return ret; // Already annotated + ret.annotation = key; + return ret; +} +annotate({ a: "things", b: "stuff" }, "b").annotation === "b"; diff --git a/tests/baselines/reference/simpleKeysofTest.symbols b/tests/baselines/reference/simpleKeysofTest.symbols new file mode 100644 index 0000000000000..72c6b92eb22c4 --- /dev/null +++ b/tests/baselines/reference/simpleKeysofTest.symbols @@ -0,0 +1,150 @@ +=== tests/cases/conformance/types/keysof/simpleKeysofTest.ts === +// First, check that the new keyword doesn't interfere +// with any other potential uses of the identifier `keysof`. +namespace keysof { +>keysof : Symbol(keysof, Decl(simpleKeysofTest.ts, 0, 0), Decl(simpleKeysofTest.ts, 5, 31)) + + export type name = {}; +>name : Symbol(name, Decl(simpleKeysofTest.ts, 2, 18)) +} +function old(a: keysof.name) {} +>old : Symbol(old, Decl(simpleKeysofTest.ts, 4, 1)) +>a : Symbol(a, Decl(simpleKeysofTest.ts, 5, 13)) +>keysof : Symbol(keysof, Decl(simpleKeysofTest.ts, 0, 0), Decl(simpleKeysofTest.ts, 5, 31)) +>name : Symbol(keysof.name, Decl(simpleKeysofTest.ts, 2, 18)) + +type keysof = {a: string}; +>keysof : Symbol(keysof, Decl(simpleKeysofTest.ts, 0, 0), Decl(simpleKeysofTest.ts, 5, 31)) +>a : Symbol(a, Decl(simpleKeysofTest.ts, 7, 15)) + +function old2(a: keysof, b: keysof): keysof { return {a: ""}; } +>old2 : Symbol(old2, Decl(simpleKeysofTest.ts, 7, 26)) +>a : Symbol(a, Decl(simpleKeysofTest.ts, 8, 14)) +>keysof : Symbol(keysof, Decl(simpleKeysofTest.ts, 0, 0), Decl(simpleKeysofTest.ts, 5, 31)) +>b : Symbol(b, Decl(simpleKeysofTest.ts, 8, 24)) +>keysof : Symbol(keysof, Decl(simpleKeysofTest.ts, 0, 0), Decl(simpleKeysofTest.ts, 5, 31)) +>keysof : Symbol(keysof, Decl(simpleKeysofTest.ts, 0, 0), Decl(simpleKeysofTest.ts, 5, 31)) +>a : Symbol(a, Decl(simpleKeysofTest.ts, 8, 54)) + +var old3 = (): keysof => ({a: ""}); +>old3 : Symbol(old3, Decl(simpleKeysofTest.ts, 9, 3)) +>keysof : Symbol(keysof, Decl(simpleKeysofTest.ts, 0, 0), Decl(simpleKeysofTest.ts, 5, 31)) +>a : Symbol(a, Decl(simpleKeysofTest.ts, 9, 27)) + +function disambiguate1(a: keysof ({b: number})) {} +>disambiguate1 : Symbol(disambiguate1, Decl(simpleKeysofTest.ts, 9, 35)) +>a : Symbol(a, Decl(simpleKeysofTest.ts, 11, 23)) +>b : Symbol(b, Decl(simpleKeysofTest.ts, 11, 35)) + +function disambiguate2(): keysof ({a}) {return "a";} +>disambiguate2 : Symbol(disambiguate2, Decl(simpleKeysofTest.ts, 11, 50)) +>a : Symbol(a, Decl(simpleKeysofTest.ts, 12, 35)) + +// Then check that the `keysof` operator works as expected +interface FooBar { +>FooBar : Symbol(FooBar, Decl(simpleKeysofTest.ts, 12, 52)) + + foo: "yes"; +>foo : Symbol(FooBar.foo, Decl(simpleKeysofTest.ts, 15, 18)) + + bar: "no"; +>bar : Symbol(FooBar.bar, Decl(simpleKeysofTest.ts, 16, 15)) + + [index: string]: string; // Remove when the indexer is patched to passthru unions +>index : Symbol(index, Decl(simpleKeysofTest.ts, 18, 5)) +} + +function pick(thing: FooBar, member: keysof FooBar) { +>pick : Symbol(pick, Decl(simpleKeysofTest.ts, 19, 1)) +>thing : Symbol(thing, Decl(simpleKeysofTest.ts, 21, 14)) +>FooBar : Symbol(FooBar, Decl(simpleKeysofTest.ts, 12, 52)) +>member : Symbol(member, Decl(simpleKeysofTest.ts, 21, 28)) +>FooBar : Symbol(FooBar, Decl(simpleKeysofTest.ts, 12, 52)) + + return thing[member]; +>thing : Symbol(thing, Decl(simpleKeysofTest.ts, 21, 14)) +>member : Symbol(member, Decl(simpleKeysofTest.ts, 21, 28)) +} + +const a = pick({foo: "yes", "bar": "no"}, "bar"); +>a : Symbol(a, Decl(simpleKeysofTest.ts, 25, 5)) +>pick : Symbol(pick, Decl(simpleKeysofTest.ts, 19, 1)) +>foo : Symbol(foo, Decl(simpleKeysofTest.ts, 25, 16)) + +function pick2(thing: T, member: keysof T): keysof T { +>pick2 : Symbol(pick2, Decl(simpleKeysofTest.ts, 25, 49)) +>T : Symbol(T, Decl(simpleKeysofTest.ts, 27, 15)) +>thing : Symbol(thing, Decl(simpleKeysofTest.ts, 27, 18)) +>T : Symbol(T, Decl(simpleKeysofTest.ts, 27, 15)) +>member : Symbol(member, Decl(simpleKeysofTest.ts, 27, 27)) +>T : Symbol(T, Decl(simpleKeysofTest.ts, 27, 15)) +>T : Symbol(T, Decl(simpleKeysofTest.ts, 27, 15)) + + return member; +>member : Symbol(member, Decl(simpleKeysofTest.ts, 27, 27)) +} +const realA: "a" = "a"; +>realA : Symbol(realA, Decl(simpleKeysofTest.ts, 30, 5)) + +const x = pick2({a: "", b: 0}, realA); +>x : Symbol(x, Decl(simpleKeysofTest.ts, 31, 5)) +>pick2 : Symbol(pick2, Decl(simpleKeysofTest.ts, 25, 49)) +>a : Symbol(a, Decl(simpleKeysofTest.ts, 31, 17)) +>b : Symbol(b, Decl(simpleKeysofTest.ts, 31, 23)) +>realA : Symbol(realA, Decl(simpleKeysofTest.ts, 30, 5)) + +const xx = pick2({a: "", b: 0}, "a"); +>xx : Symbol(xx, Decl(simpleKeysofTest.ts, 32, 5)) +>pick2 : Symbol(pick2, Decl(simpleKeysofTest.ts, 25, 49)) +>a : Symbol(a, Decl(simpleKeysofTest.ts, 32, 18)) +>b : Symbol(b, Decl(simpleKeysofTest.ts, 32, 24)) + +const item = {0: "yes", 1: "no"}; +>item : Symbol(item, Decl(simpleKeysofTest.ts, 33, 5)) + +const xxx = pick2(item, "0"); +>xxx : Symbol(xxx, Decl(simpleKeysofTest.ts, 34, 5)) +>pick2 : Symbol(pick2, Decl(simpleKeysofTest.ts, 25, 49)) +>item : Symbol(item, Decl(simpleKeysofTest.ts, 33, 5)) + +function annotate(obj: U, key: T): U & {annotation: T} { +>annotate : Symbol(annotate, Decl(simpleKeysofTest.ts, 34, 29)) +>U : Symbol(U, Decl(simpleKeysofTest.ts, 36, 18)) +>T : Symbol(T, Decl(simpleKeysofTest.ts, 36, 20)) +>U : Symbol(U, Decl(simpleKeysofTest.ts, 36, 18)) +>obj : Symbol(obj, Decl(simpleKeysofTest.ts, 36, 41)) +>U : Symbol(U, Decl(simpleKeysofTest.ts, 36, 18)) +>key : Symbol(key, Decl(simpleKeysofTest.ts, 36, 48)) +>T : Symbol(T, Decl(simpleKeysofTest.ts, 36, 20)) +>U : Symbol(U, Decl(simpleKeysofTest.ts, 36, 18)) +>annotation : Symbol(annotation, Decl(simpleKeysofTest.ts, 36, 63)) +>T : Symbol(T, Decl(simpleKeysofTest.ts, 36, 20)) + + const ret = obj as U & {annotation: T}; +>ret : Symbol(ret, Decl(simpleKeysofTest.ts, 37, 9)) +>obj : Symbol(obj, Decl(simpleKeysofTest.ts, 36, 41)) +>U : Symbol(U, Decl(simpleKeysofTest.ts, 36, 18)) +>annotation : Symbol(annotation, Decl(simpleKeysofTest.ts, 37, 28)) +>T : Symbol(T, Decl(simpleKeysofTest.ts, 36, 20)) + + if (key === "annotation") return ret; // Already annotated +>key : Symbol(key, Decl(simpleKeysofTest.ts, 36, 48)) +>ret : Symbol(ret, Decl(simpleKeysofTest.ts, 37, 9)) + + ret.annotation = key; +>ret.annotation : Symbol(annotation, Decl(simpleKeysofTest.ts, 37, 28)) +>ret : Symbol(ret, Decl(simpleKeysofTest.ts, 37, 9)) +>annotation : Symbol(annotation, Decl(simpleKeysofTest.ts, 37, 28)) +>key : Symbol(key, Decl(simpleKeysofTest.ts, 36, 48)) + + return ret; +>ret : Symbol(ret, Decl(simpleKeysofTest.ts, 37, 9)) +} + +annotate({a: "things", b: "stuff"}, "b").annotation === "b"; +>annotate({a: "things", b: "stuff"}, "b").annotation : Symbol(annotation, Decl(simpleKeysofTest.ts, 36, 63)) +>annotate : Symbol(annotate, Decl(simpleKeysofTest.ts, 34, 29)) +>a : Symbol(a, Decl(simpleKeysofTest.ts, 43, 10)) +>b : Symbol(b, Decl(simpleKeysofTest.ts, 43, 22)) +>annotation : Symbol(annotation, Decl(simpleKeysofTest.ts, 36, 63)) + diff --git a/tests/baselines/reference/simpleKeysofTest.types b/tests/baselines/reference/simpleKeysofTest.types new file mode 100644 index 0000000000000..10146c1089a20 --- /dev/null +++ b/tests/baselines/reference/simpleKeysofTest.types @@ -0,0 +1,189 @@ +=== tests/cases/conformance/types/keysof/simpleKeysofTest.ts === +// First, check that the new keyword doesn't interfere +// with any other potential uses of the identifier `keysof`. +namespace keysof { +>keysof : any + + export type name = {}; +>name : {} +} +function old(a: keysof.name) {} +>old : (a: {}) => void +>a : {} +>keysof : any +>name : {} + +type keysof = {a: string}; +>keysof : { a: string; } +>a : string + +function old2(a: keysof, b: keysof): keysof { return {a: ""}; } +>old2 : (a: { a: string; }, b: { a: string; }) => { a: string; } +>a : { a: string; } +>keysof : { a: string; } +>b : { a: string; } +>keysof : { a: string; } +>keysof : { a: string; } +>{a: ""} : { a: string; } +>a : string +>"" : string + +var old3 = (): keysof => ({a: ""}); +>old3 : () => { a: string; } +>(): keysof => ({a: ""}) : () => { a: string; } +>keysof : { a: string; } +>({a: ""}) : { a: string; } +>{a: ""} : { a: string; } +>a : string +>"" : string + +function disambiguate1(a: keysof ({b: number})) {} +>disambiguate1 : (a: keysof { b: number; }) => void +>a : keysof { b: number; } +>b : number + +function disambiguate2(): keysof ({a}) {return "a";} +>disambiguate2 : () => keysof { a: any; } +>a : any +>"a" : "a" + +// Then check that the `keysof` operator works as expected +interface FooBar { +>FooBar : FooBar + + foo: "yes"; +>foo : "yes" + + bar: "no"; +>bar : "no" + + [index: string]: string; // Remove when the indexer is patched to passthru unions +>index : string +} + +function pick(thing: FooBar, member: keysof FooBar) { +>pick : (thing: FooBar, member: keysof FooBar) => string +>thing : FooBar +>FooBar : FooBar +>member : keysof FooBar +>FooBar : FooBar + + return thing[member]; +>thing[member] : string +>thing : FooBar +>member : keysof FooBar +} + +const a = pick({foo: "yes", "bar": "no"}, "bar"); +>a : string +>pick({foo: "yes", "bar": "no"}, "bar") : string +>pick : (thing: FooBar, member: keysof FooBar) => string +>{foo: "yes", "bar": "no"} : { foo: "yes"; "bar": "no"; } +>foo : "yes" +>"yes" : "yes" +>"no" : "no" +>"bar" : "bar" + +function pick2(thing: T, member: keysof T): keysof T { +>pick2 : (thing: T, member: keysof T) => keysof T +>T : T +>thing : T +>T : T +>member : keysof T +>T : T +>T : T + + return member; +>member : keysof T +} +const realA: "a" = "a"; +>realA : "a" +>"a" : "a" + +const x = pick2({a: "", b: 0}, realA); +>x : keysof { a: string; b: number; } +>pick2({a: "", b: 0}, realA) : keysof { a: string; b: number; } +>pick2 : (thing: T, member: keysof T) => keysof T +>{a: "", b: 0} : { a: string; b: number; } +>a : string +>"" : string +>b : number +>0 : number +>realA : "a" + +const xx = pick2({a: "", b: 0}, "a"); +>xx : keysof { a: string; b: number; } +>pick2({a: "", b: 0}, "a") : keysof { a: string; b: number; } +>pick2 : (thing: T, member: keysof T) => keysof T +>{a: "", b: 0} : { a: string; b: number; } +>a : string +>"" : string +>b : number +>0 : number +>"a" : "a" + +const item = {0: "yes", 1: "no"}; +>item : { 0: string; 1: string; } +>{0: "yes", 1: "no"} : { 0: string; 1: string; } +>"yes" : string +>"no" : string + +const xxx = pick2(item, "0"); +>xxx : keysof { 0: string; 1: string; } +>pick2(item, "0") : keysof { 0: string; 1: string; } +>pick2 : (thing: T, member: keysof T) => keysof T +>item : { 0: string; 1: string; } +>"0" : "0" + +function annotate(obj: U, key: T): U & {annotation: T} { +>annotate : (obj: U, key: T) => U & { annotation: T; } +>U : U +>T : T +>U : U +>obj : U +>U : U +>key : T +>T : T +>U : U +>annotation : T +>T : T + + const ret = obj as U & {annotation: T}; +>ret : U & { annotation: T; } +>obj as U & {annotation: T} : U & { annotation: T; } +>obj : U +>U : U +>annotation : T +>T : T + + if (key === "annotation") return ret; // Already annotated +>key === "annotation" : boolean +>key : T +>"annotation" : "annotation" +>ret : U & { annotation: T; } + + ret.annotation = key; +>ret.annotation = key : T +>ret.annotation : T +>ret : U & { annotation: T; } +>annotation : T +>key : T + + return ret; +>ret : U & { annotation: T; } +} + +annotate({a: "things", b: "stuff"}, "b").annotation === "b"; +>annotate({a: "things", b: "stuff"}, "b").annotation === "b" : boolean +>annotate({a: "things", b: "stuff"}, "b").annotation : "b" +>annotate({a: "things", b: "stuff"}, "b") : { a: string; b: string; } & { annotation: "b"; } +>annotate : (obj: U, key: T) => U & { annotation: T; } +>{a: "things", b: "stuff"} : { a: string; b: string; } +>a : string +>"things" : string +>b : string +>"stuff" : string +>"b" : "b" +>annotation : "b" +>"b" : "b" + diff --git a/tests/cases/conformance/types/keysof/simpleKeysofTest.ts b/tests/cases/conformance/types/keysof/simpleKeysofTest.ts new file mode 100644 index 0000000000000..d28524916645d --- /dev/null +++ b/tests/cases/conformance/types/keysof/simpleKeysofTest.ts @@ -0,0 +1,44 @@ +// First, check that the new keyword doesn't interfere +// with any other potential uses of the identifier `keysof`. +namespace keysof { + export type name = {}; +} +function old(a: keysof.name) {} + +type keysof = {a: string}; +function old2(a: keysof, b: keysof): keysof { return {a: ""}; } +var old3 = (): keysof => ({a: ""}); + +function disambiguate1(a: keysof ({b: number})) {} +function disambiguate2(): keysof ({a}) {return "a";} + +// Then check that the `keysof` operator works as expected +interface FooBar { + foo: "yes"; + bar: "no"; + [index: string]: string; // Remove when the indexer is patched to passthru unions +} + +function pick(thing: FooBar, member: keysof FooBar) { + return thing[member]; +} + +const a = pick({foo: "yes", "bar": "no"}, "bar"); + +function pick2(thing: T, member: keysof T): keysof T { + return member; +} +const realA: "a" = "a"; +const x = pick2({a: "", b: 0}, realA); +const xx = pick2({a: "", b: 0}, "a"); +const item = {0: "yes", 1: "no"}; +const xxx = pick2(item, "0"); + +function annotate(obj: U, key: T): U & {annotation: T} { + const ret = obj as U & {annotation: T}; + if (key === "annotation") return ret; // Already annotated + ret.annotation = key; + return ret; +} + +annotate({a: "things", b: "stuff"}, "b").annotation === "b";