From 68a6c0ea7abf0812d758823fe7fa0efe9b3e1255 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 25 Apr 2022 06:01:09 -0700 Subject: [PATCH 1/3] Improve support for numeric string types --- src/compiler/checker.ts | 45 +++++++++++++++++++++++++++++------------ 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2d3060682ffb5..a7297903cfbe5 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -833,6 +833,7 @@ namespace ts { const keyofConstraintType = keyofStringsOnly ? stringType : stringNumberSymbolType; const numberOrBigIntType = getUnionType([numberType, bigintType]); const templateConstraintType = getUnionType([stringType, numberType, booleanType, bigintType, nullType, undefinedType]) as UnionType; + const numericStringType = getTemplateLiteralType(["", ""], [numberType]); // The `${number}` type const restrictiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? getRestrictiveTypeParameter(t as TypeParameter) : t); const permissiveMapper: TypeMapper = makeFunctionTypeMapper(t => t.flags & TypeFlags.TypeParameter ? wildcardType : t); @@ -12273,7 +12274,7 @@ namespace ts { const typeVariable = getHomomorphicTypeVariable(type); if (typeVariable && !type.declaration.nameType) { const constraint = getConstraintOfTypeParameter(typeVariable); - if (constraint && (isArrayType(constraint) || isTupleType(constraint))) { + if (constraint && isArrayOrTupleType(constraint)) { return instantiateType(type, prependTypeMapping(typeVariable, constraint, type.mapper)); } } @@ -12654,10 +12655,10 @@ namespace ts { function isApplicableIndexType(source: Type, target: Type): boolean { // A 'string' index signature applies to types assignable to 'string' or 'number', and a 'number' index - // signature applies to types assignable to 'number' and numeric string literal types. + // signature applies to types assignable to 'number', `${number}` and numeric string literal types. return isTypeAssignableTo(source, target) || target === stringType && isTypeAssignableTo(source, numberType) || - target === numberType && !!(source.flags & TypeFlags.StringLiteral) && isNumericLiteralName((source as StringLiteralType).value); + target === numberType && (source === numericStringType || !!(source.flags & TypeFlags.StringLiteral) && isNumericLiteralName((source as StringLiteralType).value)); } function getIndexInfosOfStructuredType(type: Type): readonly IndexInfo[] { @@ -13778,6 +13779,20 @@ namespace ts { constraints = append(constraints, constraint); } } + // Given a homomorphic mapped type { [K in keyof T]: XXX }, where T is constrained to an array or tuple type, in the + // template type XXX, K has an added constraint of number | `${number}`. + else if (type.flags & TypeFlags.TypeParameter && parent.kind === SyntaxKind.MappedType && node === (parent as MappedTypeNode).type) { + const mappedType = getTypeFromTypeNode(parent as TypeNode) as MappedType; + if (getTypeParameterFromMappedType(mappedType) === getActualTypeVariable(type)) { + const typeParameter = getHomomorphicTypeVariable(mappedType); + if (typeParameter) { + const constraint = getConstraintOfTypeParameter(typeParameter); + if (constraint && everyType(constraint, isArrayOrTupleType)) { + constraints = append(constraints, getUnionType([numberType, numericStringType])); + } + } + } + } node = parent; } return constraints ? getSubstitutionType(type, getIntersectionType(append(constraints, type))) : type; @@ -14817,7 +14832,7 @@ namespace ts { i--; const t = types[i]; const remove = - t.flags & TypeFlags.String && includes & TypeFlags.StringLiteral || + t.flags & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || t.flags & TypeFlags.Number && includes & TypeFlags.NumberLiteral || t.flags & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || t.flags & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol; @@ -14978,7 +14993,7 @@ namespace ts { if (!strictNullChecks && includes & TypeFlags.Nullable) { return includes & TypeFlags.Undefined ? undefinedType : nullType; } - if (includes & TypeFlags.String && includes & TypeFlags.StringLiteral || + if (includes & TypeFlags.String && includes & (TypeFlags.StringLiteral | TypeFlags.TemplateLiteral | TypeFlags.StringMapping) || includes & TypeFlags.Number && includes & TypeFlags.NumberLiteral || includes & TypeFlags.BigInt && includes & TypeFlags.BigIntLiteral || includes & TypeFlags.ESSymbol && includes & TypeFlags.UniqueESSymbol) { @@ -16996,7 +17011,7 @@ namespace ts { if (!type.declaration.nameType) { let constraint; if (isArrayType(t) || t.flags & TypeFlags.Any && findResolutionCycleStartIndex(typeVariable, TypeSystemPropertyName.ImmediateBaseConstraint) < 0 && - (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, or(isArrayType, isTupleType))) { + (constraint = getConstraintOfTypeParameter(typeVariable)) && everyType(constraint, isArrayOrTupleType)) { return instantiateMappedArrayType(t, type, prependTypeMapping(typeVariable, t, mapper)); } if (isGenericTupleType(t)) { @@ -18565,7 +18580,7 @@ namespace ts { } return false; } - return isTupleType(target) || isArrayType(target); + return isArrayOrTupleType(target); } if (isReadonlyArrayType(source) && isMutableArrayOrTuple(target)) { if (reportErrors) { @@ -18700,7 +18715,7 @@ namespace ts { // recursive intersections that are structurally similar but not exactly identical. See #37854. if (result && !inPropertyCheck && ( target.flags & TypeFlags.Intersection && (isPerformingExcessPropertyChecks || isPerformingCommonPropertyChecks) || - isNonGenericObjectType(target) && !isArrayType(target) && !isTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { + isNonGenericObjectType(target) && !isArrayOrTupleType(target) && source.flags & TypeFlags.Intersection && getApparentType(source).flags & TypeFlags.StructuredType && !some((source as IntersectionType).types, t => !!(getObjectFlags(t) & ObjectFlags.NonInferrableType)))) { inPropertyCheck = true; result &= recursiveTypeRelatedTo(source, target, reportErrors, IntersectionState.PropertyCheck, recursionFlags); inPropertyCheck = false; @@ -19708,7 +19723,7 @@ namespace ts { return varianceResult; } } - else if (isReadonlyArrayType(target) ? isArrayType(source) || isTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { + else if (isReadonlyArrayType(target) ? isArrayOrTupleType(source) : isArrayType(target) && isTupleType(source) && !source.target.readonly) { if (relation !== identityRelation) { return isRelatedTo(getIndexTypeOfType(source, numberType) || anyType, getIndexTypeOfType(target, numberType) || anyType, RecursionFlags.Both, reportErrors); } @@ -20093,7 +20108,7 @@ namespace ts { } let result = Ternary.True; if (isTupleType(target)) { - if (isArrayType(source) || isTupleType(source)) { + if (isArrayOrTupleType(source)) { if (!target.target.readonly && (isReadonlyArrayType(source) || isTupleType(source) && source.target.readonly)) { return Ternary.False; } @@ -21098,6 +21113,10 @@ namespace ts { return !!(getObjectFlags(type) & ObjectFlags.Reference) && (type as TypeReference).target === globalReadonlyArrayType; } + function isArrayOrTupleType(type: Type): type is TypeReference { + return isArrayType(type) || isTupleType(type); + } + function isMutableArrayOrTuple(type: Type): boolean { return isArrayType(type) && !isReadonlyArrayType(type) || isTupleType(type) && !type.target.readonly; } @@ -21598,7 +21617,7 @@ namespace ts { else if (type.flags & TypeFlags.Intersection) { result = getIntersectionType(sameMap((type as IntersectionType).types, getWidenedType)); } - else if (isArrayType(type) || isTupleType(type)) { + else if (isArrayOrTupleType(type)) { result = createTypeReference(type.target, sameMap(getTypeArguments(type), getWidenedType)); } if (result && context === undefined) { @@ -21635,7 +21654,7 @@ namespace ts { } } } - if (isArrayType(type) || isTupleType(type)) { + if (isArrayOrTupleType(type)) { for (const t of getTypeArguments(type)) { if (reportWideningErrorsInType(t)) { errorReported = true; @@ -22714,7 +22733,7 @@ namespace ts { } // Infer from the members of source and target only if the two types are possibly related if (!typesDefinitelyUnrelated(source, target)) { - if (isArrayType(source) || isTupleType(source)) { + if (isArrayOrTupleType(source)) { if (isTupleType(target)) { const sourceArity = getTypeReferenceArity(source); const targetArity = getTypeReferenceArity(target); From 71a2b47442a30b0b2b469509c1537bcf2b50452f Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 25 Apr 2022 06:01:51 -0700 Subject: [PATCH 2/3] Update test --- .../templateLiteralTypes1.errors.txt | 3 +- .../reference/templateLiteralTypes1.js | 1 + .../reference/templateLiteralTypes1.symbols | 95 ++++++++++--------- .../reference/templateLiteralTypes1.types | 1 + .../types/literal/templateLiteralTypes1.ts | 1 + 5 files changed, 54 insertions(+), 47 deletions(-) diff --git a/tests/baselines/reference/templateLiteralTypes1.errors.txt b/tests/baselines/reference/templateLiteralTypes1.errors.txt index e586a1678eb65..384125f3380bc 100644 --- a/tests/baselines/reference/templateLiteralTypes1.errors.txt +++ b/tests/baselines/reference/templateLiteralTypes1.errors.txt @@ -6,7 +6,7 @@ tests/cases/conformance/types/literal/templateLiteralTypes1.ts(165,15): error TS tests/cases/conformance/types/literal/templateLiteralTypes1.ts(197,16): error TS2590: Expression produces a union type that is too complex to represent. tests/cases/conformance/types/literal/templateLiteralTypes1.ts(201,16): error TS2590: Expression produces a union type that is too complex to represent. tests/cases/conformance/types/literal/templateLiteralTypes1.ts(205,16): error TS2590: Expression produces a union type that is too complex to represent. -tests/cases/conformance/types/literal/templateLiteralTypes1.ts(251,7): error TS2590: Expression produces a union type that is too complex to represent. +tests/cases/conformance/types/literal/templateLiteralTypes1.ts(252,7): error TS2590: Expression produces a union type that is too complex to represent. ==== tests/cases/conformance/types/literal/templateLiteralTypes1.ts (7 errors) ==== @@ -242,6 +242,7 @@ tests/cases/conformance/types/literal/templateLiteralTypes1.ts(251,7): error TS2 // Repro from #40970 type PathKeys = + unknown extends T ? never : T extends readonly any[] ? Extract | SubKeys> : T extends object ? Extract | SubKeys> : never; diff --git a/tests/baselines/reference/templateLiteralTypes1.js b/tests/baselines/reference/templateLiteralTypes1.js index d212909fd7d2b..afa410f6dc9de 100644 --- a/tests/baselines/reference/templateLiteralTypes1.js +++ b/tests/baselines/reference/templateLiteralTypes1.js @@ -217,6 +217,7 @@ type BB = AA<-2, -2>; // Repro from #40970 type PathKeys = + unknown extends T ? never : T extends readonly any[] ? Extract | SubKeys> : T extends object ? Extract | SubKeys> : never; diff --git a/tests/baselines/reference/templateLiteralTypes1.symbols b/tests/baselines/reference/templateLiteralTypes1.symbols index 8f539fe347e15..fda962de761e1 100644 --- a/tests/baselines/reference/templateLiteralTypes1.symbols +++ b/tests/baselines/reference/templateLiteralTypes1.symbols @@ -878,11 +878,14 @@ type PathKeys = >PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21)) >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) + unknown extends T ? never : +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) + T extends readonly any[] ? Extract | SubKeys> : >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) >Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) ->SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10)) +>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 221, 10)) >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) >Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) @@ -891,7 +894,7 @@ type PathKeys = >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) >Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) ->SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10)) +>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 221, 10)) >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) >Extract : Symbol(Extract, Decl(lib.es5.d.ts, --, --)) >T : Symbol(T, Decl(templateLiteralTypes1.ts, 217, 14)) @@ -899,63 +902,63 @@ type PathKeys = never; type SubKeys = K extends keyof T ? `${K}.${PathKeys}` : never; ->SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 220, 10)) ->T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13)) ->K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15)) ->K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15)) ->T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13)) ->K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15)) +>SubKeys : Symbol(SubKeys, Decl(templateLiteralTypes1.ts, 221, 10)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 223, 13)) +>K : Symbol(K, Decl(templateLiteralTypes1.ts, 223, 15)) +>K : Symbol(K, Decl(templateLiteralTypes1.ts, 223, 15)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 223, 13)) +>K : Symbol(K, Decl(templateLiteralTypes1.ts, 223, 15)) >PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21)) ->T : Symbol(T, Decl(templateLiteralTypes1.ts, 222, 13)) ->K : Symbol(K, Decl(templateLiteralTypes1.ts, 222, 15)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 223, 13)) +>K : Symbol(K, Decl(templateLiteralTypes1.ts, 223, 15)) declare function getProp2>(obj: T, path: P): PropType; ->getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 222, 89)) ->T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26)) ->P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28)) +>getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 223, 89)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 225, 26)) +>P : Symbol(P, Decl(templateLiteralTypes1.ts, 225, 28)) >PathKeys : Symbol(PathKeys, Decl(templateLiteralTypes1.ts, 213, 21)) ->T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26)) ->obj : Symbol(obj, Decl(templateLiteralTypes1.ts, 224, 52)) ->T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26)) ->path : Symbol(path, Decl(templateLiteralTypes1.ts, 224, 59)) ->P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 225, 26)) +>obj : Symbol(obj, Decl(templateLiteralTypes1.ts, 225, 52)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 225, 26)) +>path : Symbol(path, Decl(templateLiteralTypes1.ts, 225, 59)) +>P : Symbol(P, Decl(templateLiteralTypes1.ts, 225, 28)) >PropType : Symbol(PropType, Decl(templateLiteralTypes1.ts, 138, 69)) ->T : Symbol(T, Decl(templateLiteralTypes1.ts, 224, 26)) ->P : Symbol(P, Decl(templateLiteralTypes1.ts, 224, 28)) +>T : Symbol(T, Decl(templateLiteralTypes1.ts, 225, 26)) +>P : Symbol(P, Decl(templateLiteralTypes1.ts, 225, 28)) const obj2 = { ->obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 226, 5)) +>obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 227, 5)) name: 'John', ->name : Symbol(name, Decl(templateLiteralTypes1.ts, 226, 14)) +>name : Symbol(name, Decl(templateLiteralTypes1.ts, 227, 14)) age: 42, ->age : Symbol(age, Decl(templateLiteralTypes1.ts, 227, 17)) +>age : Symbol(age, Decl(templateLiteralTypes1.ts, 228, 17)) cars: [ ->cars : Symbol(cars, Decl(templateLiteralTypes1.ts, 228, 12)) +>cars : Symbol(cars, Decl(templateLiteralTypes1.ts, 229, 12)) { make: 'Ford', age: 10 }, ->make : Symbol(make, Decl(templateLiteralTypes1.ts, 230, 9)) ->age : Symbol(age, Decl(templateLiteralTypes1.ts, 230, 23)) +>make : Symbol(make, Decl(templateLiteralTypes1.ts, 231, 9)) +>age : Symbol(age, Decl(templateLiteralTypes1.ts, 231, 23)) { make: 'Trabant', age: 35 } ->make : Symbol(make, Decl(templateLiteralTypes1.ts, 231, 9)) ->age : Symbol(age, Decl(templateLiteralTypes1.ts, 231, 26)) +>make : Symbol(make, Decl(templateLiteralTypes1.ts, 232, 9)) +>age : Symbol(age, Decl(templateLiteralTypes1.ts, 232, 26)) ] } as const; >const : Symbol(const) let make = getProp2(obj2, 'cars.1.make'); // 'Trabant' ->make : Symbol(make, Decl(templateLiteralTypes1.ts, 235, 3)) ->getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 222, 89)) ->obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 226, 5)) +>make : Symbol(make, Decl(templateLiteralTypes1.ts, 236, 3)) +>getProp2 : Symbol(getProp2, Decl(templateLiteralTypes1.ts, 223, 89)) +>obj2 : Symbol(obj2, Decl(templateLiteralTypes1.ts, 227, 5)) // Repro from #46480 export type Spacing = ->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41)) | `0` | `${number}px` @@ -963,28 +966,28 @@ export type Spacing = | `s${1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20}`; const spacing: Spacing = "s12" ->spacing : Symbol(spacing, Decl(templateLiteralTypes1.ts, 245, 5)) ->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) +>spacing : Symbol(spacing, Decl(templateLiteralTypes1.ts, 246, 5)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41)) export type SpacingShorthand = ->SpacingShorthand : Symbol(SpacingShorthand, Decl(templateLiteralTypes1.ts, 245, 30)) +>SpacingShorthand : Symbol(SpacingShorthand, Decl(templateLiteralTypes1.ts, 246, 30)) | `${Spacing} ${Spacing}` ->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) ->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41)) | `${Spacing} ${Spacing} ${Spacing}` ->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) ->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) ->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41)) | `${Spacing} ${Spacing} ${Spacing} ${Spacing}`; ->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) ->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) ->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) ->Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 235, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41)) +>Spacing : Symbol(Spacing, Decl(templateLiteralTypes1.ts, 236, 41)) const test1: SpacingShorthand = "0 0 0"; ->test1 : Symbol(test1, Decl(templateLiteralTypes1.ts, 252, 5)) ->SpacingShorthand : Symbol(SpacingShorthand, Decl(templateLiteralTypes1.ts, 245, 30)) +>test1 : Symbol(test1, Decl(templateLiteralTypes1.ts, 253, 5)) +>SpacingShorthand : Symbol(SpacingShorthand, Decl(templateLiteralTypes1.ts, 246, 30)) diff --git a/tests/baselines/reference/templateLiteralTypes1.types b/tests/baselines/reference/templateLiteralTypes1.types index fed8939ea7605..42036b95f496d 100644 --- a/tests/baselines/reference/templateLiteralTypes1.types +++ b/tests/baselines/reference/templateLiteralTypes1.types @@ -541,6 +541,7 @@ type BB = AA<-2, -2>; type PathKeys = >PathKeys : PathKeys + unknown extends T ? never : T extends readonly any[] ? Extract | SubKeys> : T extends object ? Extract | SubKeys> : never; diff --git a/tests/cases/conformance/types/literal/templateLiteralTypes1.ts b/tests/cases/conformance/types/literal/templateLiteralTypes1.ts index daa3fbaeb78e7..0b11e9f5d613a 100644 --- a/tests/cases/conformance/types/literal/templateLiteralTypes1.ts +++ b/tests/cases/conformance/types/literal/templateLiteralTypes1.ts @@ -219,6 +219,7 @@ type BB = AA<-2, -2>; // Repro from #40970 type PathKeys = + unknown extends T ? never : T extends readonly any[] ? Extract | SubKeys> : T extends object ? Extract | SubKeys> : never; From 2a7fc1e81c62ff9f61af2b56262bce50542562eb Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Mon, 25 Apr 2022 06:15:51 -0700 Subject: [PATCH 3/3] Add tests --- .../reference/numericStringLiteralTypes.js | 80 ++++++++++++ .../numericStringLiteralTypes.symbols | 122 ++++++++++++++++++ .../reference/numericStringLiteralTypes.types | 102 +++++++++++++++ .../literal/numericStringLiteralTypes.ts | 40 ++++++ 4 files changed, 344 insertions(+) create mode 100644 tests/baselines/reference/numericStringLiteralTypes.js create mode 100644 tests/baselines/reference/numericStringLiteralTypes.symbols create mode 100644 tests/baselines/reference/numericStringLiteralTypes.types create mode 100644 tests/cases/conformance/types/literal/numericStringLiteralTypes.ts diff --git a/tests/baselines/reference/numericStringLiteralTypes.js b/tests/baselines/reference/numericStringLiteralTypes.js new file mode 100644 index 0000000000000..975ca1d31799c --- /dev/null +++ b/tests/baselines/reference/numericStringLiteralTypes.js @@ -0,0 +1,80 @@ +//// [numericStringLiteralTypes.ts] +type T0 = string & `${string}`; // string +type T1 = string & `${number}`; // `${number} +type T2 = string & `${bigint}`; // `${bigint} +type T3 = string & `${T}`; // `${T} +type T4 = string & `${Capitalize<`${T}`>}`; // `${Capitalize}` + +function f1(a: boolean[], x: `${number}`) { + let s = a[x]; // boolean +} + +function f2(a: boolean[], x: number | `${number}`) { + let s = a[x]; // boolean +} + +type T10 = boolean[][`${number}`]; // boolean +type T11 = boolean[][number | `${number}`]; // boolean + +type T20 = T; +type T21 = { [K in keyof T]: T20 }; + +type Container = { + value: T +} + +type UnwrapContainers[]> = { [K in keyof T]: T[K]['value'] }; + +declare function createContainer(value: T): Container; + +declare function f[]>(containers: [...T], callback: (...values: UnwrapContainers) => void): void; + +const container1 = createContainer('hi') +const container2 = createContainer(2) + +f([container1, container2], (value1, value2) => { + value1; // string + value2; // number +}); + + +//// [numericStringLiteralTypes.js] +"use strict"; +function f1(a, x) { + var s = a[x]; // boolean +} +function f2(a, x) { + var s = a[x]; // boolean +} +var container1 = createContainer('hi'); +var container2 = createContainer(2); +f([container1, container2], function (value1, value2) { + value1; // string + value2; // number +}); + + +//// [numericStringLiteralTypes.d.ts] +declare type T0 = string & `${string}`; +declare type T1 = string & `${number}`; +declare type T2 = string & `${bigint}`; +declare type T3 = string & `${T}`; +declare type T4 = string & `${Capitalize<`${T}`>}`; +declare function f1(a: boolean[], x: `${number}`): void; +declare function f2(a: boolean[], x: number | `${number}`): void; +declare type T10 = boolean[][`${number}`]; +declare type T11 = boolean[][number | `${number}`]; +declare type T20 = T; +declare type T21 = { + [K in keyof T]: T20; +}; +declare type Container = { + value: T; +}; +declare type UnwrapContainers[]> = { + [K in keyof T]: T[K]['value']; +}; +declare function createContainer(value: T): Container; +declare function f[]>(containers: [...T], callback: (...values: UnwrapContainers) => void): void; +declare const container1: Container; +declare const container2: Container; diff --git a/tests/baselines/reference/numericStringLiteralTypes.symbols b/tests/baselines/reference/numericStringLiteralTypes.symbols new file mode 100644 index 0000000000000..33d2a1633a6d5 --- /dev/null +++ b/tests/baselines/reference/numericStringLiteralTypes.symbols @@ -0,0 +1,122 @@ +=== tests/cases/conformance/types/literal/numericStringLiteralTypes.ts === +type T0 = string & `${string}`; // string +>T0 : Symbol(T0, Decl(numericStringLiteralTypes.ts, 0, 0)) + +type T1 = string & `${number}`; // `${number} +>T1 : Symbol(T1, Decl(numericStringLiteralTypes.ts, 0, 31)) + +type T2 = string & `${bigint}`; // `${bigint} +>T2 : Symbol(T2, Decl(numericStringLiteralTypes.ts, 1, 31)) + +type T3 = string & `${T}`; // `${T} +>T3 : Symbol(T3, Decl(numericStringLiteralTypes.ts, 2, 31)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 3, 8)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 3, 8)) + +type T4 = string & `${Capitalize<`${T}`>}`; // `${Capitalize}` +>T4 : Symbol(T4, Decl(numericStringLiteralTypes.ts, 3, 44)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 4, 8)) +>Capitalize : Symbol(Capitalize, Decl(lib.es5.d.ts, --, --)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 4, 8)) + +function f1(a: boolean[], x: `${number}`) { +>f1 : Symbol(f1, Decl(numericStringLiteralTypes.ts, 4, 61)) +>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 6, 12)) +>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 6, 25)) + + let s = a[x]; // boolean +>s : Symbol(s, Decl(numericStringLiteralTypes.ts, 7, 7)) +>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 6, 12)) +>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 6, 25)) +} + +function f2(a: boolean[], x: number | `${number}`) { +>f2 : Symbol(f2, Decl(numericStringLiteralTypes.ts, 8, 1)) +>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 10, 12)) +>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 10, 25)) + + let s = a[x]; // boolean +>s : Symbol(s, Decl(numericStringLiteralTypes.ts, 11, 7)) +>a : Symbol(a, Decl(numericStringLiteralTypes.ts, 10, 12)) +>x : Symbol(x, Decl(numericStringLiteralTypes.ts, 10, 25)) +} + +type T10 = boolean[][`${number}`]; // boolean +>T10 : Symbol(T10, Decl(numericStringLiteralTypes.ts, 12, 1)) + +type T11 = boolean[][number | `${number}`]; // boolean +>T11 : Symbol(T11, Decl(numericStringLiteralTypes.ts, 14, 34)) + +type T20 = T; +>T20 : Symbol(T20, Decl(numericStringLiteralTypes.ts, 15, 43)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 17, 9)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 17, 9)) + +type T21 = { [K in keyof T]: T20 }; +>T21 : Symbol(T21, Decl(numericStringLiteralTypes.ts, 17, 45)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 18, 9)) +>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 18, 35)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 18, 9)) +>T20 : Symbol(T20, Decl(numericStringLiteralTypes.ts, 15, 43)) +>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 18, 35)) + +type Container = { +>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 20, 15)) + + value: T +>value : Symbol(value, Decl(numericStringLiteralTypes.ts, 20, 21)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 20, 15)) +} + +type UnwrapContainers[]> = { [K in keyof T]: T[K]['value'] }; +>UnwrapContainers : Symbol(UnwrapContainers, Decl(numericStringLiteralTypes.ts, 22, 1)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 24, 22)) +>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59)) +>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 24, 59)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 24, 22)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 24, 22)) +>K : Symbol(K, Decl(numericStringLiteralTypes.ts, 24, 59)) + +declare function createContainer(value: T): Container; +>createContainer : Symbol(createContainer, Decl(numericStringLiteralTypes.ts, 24, 90)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 26, 33)) +>value : Symbol(value, Decl(numericStringLiteralTypes.ts, 26, 52)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 26, 33)) +>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 26, 33)) + +declare function f[]>(containers: [...T], callback: (...values: UnwrapContainers) => void): void; +>f : Symbol(f, Decl(numericStringLiteralTypes.ts, 26, 76)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 28, 19)) +>Container : Symbol(Container, Decl(numericStringLiteralTypes.ts, 18, 59)) +>containers : Symbol(containers, Decl(numericStringLiteralTypes.ts, 28, 51)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 28, 19)) +>callback : Symbol(callback, Decl(numericStringLiteralTypes.ts, 28, 70)) +>values : Symbol(values, Decl(numericStringLiteralTypes.ts, 28, 82)) +>UnwrapContainers : Symbol(UnwrapContainers, Decl(numericStringLiteralTypes.ts, 22, 1)) +>T : Symbol(T, Decl(numericStringLiteralTypes.ts, 28, 19)) + +const container1 = createContainer('hi') +>container1 : Symbol(container1, Decl(numericStringLiteralTypes.ts, 30, 5)) +>createContainer : Symbol(createContainer, Decl(numericStringLiteralTypes.ts, 24, 90)) + +const container2 = createContainer(2) +>container2 : Symbol(container2, Decl(numericStringLiteralTypes.ts, 31, 5)) +>createContainer : Symbol(createContainer, Decl(numericStringLiteralTypes.ts, 24, 90)) + +f([container1, container2], (value1, value2) => { +>f : Symbol(f, Decl(numericStringLiteralTypes.ts, 26, 76)) +>container1 : Symbol(container1, Decl(numericStringLiteralTypes.ts, 30, 5)) +>container2 : Symbol(container2, Decl(numericStringLiteralTypes.ts, 31, 5)) +>value1 : Symbol(value1, Decl(numericStringLiteralTypes.ts, 33, 29)) +>value2 : Symbol(value2, Decl(numericStringLiteralTypes.ts, 33, 36)) + + value1; // string +>value1 : Symbol(value1, Decl(numericStringLiteralTypes.ts, 33, 29)) + + value2; // number +>value2 : Symbol(value2, Decl(numericStringLiteralTypes.ts, 33, 36)) + +}); + diff --git a/tests/baselines/reference/numericStringLiteralTypes.types b/tests/baselines/reference/numericStringLiteralTypes.types new file mode 100644 index 0000000000000..245a3a674ba8e --- /dev/null +++ b/tests/baselines/reference/numericStringLiteralTypes.types @@ -0,0 +1,102 @@ +=== tests/cases/conformance/types/literal/numericStringLiteralTypes.ts === +type T0 = string & `${string}`; // string +>T0 : string + +type T1 = string & `${number}`; // `${number} +>T1 : `${number}` + +type T2 = string & `${bigint}`; // `${bigint} +>T2 : `${bigint}` + +type T3 = string & `${T}`; // `${T} +>T3 : `${T}` + +type T4 = string & `${Capitalize<`${T}`>}`; // `${Capitalize}` +>T4 : `${Capitalize<`${T}`>}` + +function f1(a: boolean[], x: `${number}`) { +>f1 : (a: boolean[], x: `${number}`) => void +>a : boolean[] +>x : `${number}` + + let s = a[x]; // boolean +>s : boolean +>a[x] : boolean +>a : boolean[] +>x : `${number}` +} + +function f2(a: boolean[], x: number | `${number}`) { +>f2 : (a: boolean[], x: number | `${number}`) => void +>a : boolean[] +>x : number | `${number}` + + let s = a[x]; // boolean +>s : boolean +>a[x] : boolean +>a : boolean[] +>x : number | `${number}` +} + +type T10 = boolean[][`${number}`]; // boolean +>T10 : boolean + +type T11 = boolean[][number | `${number}`]; // boolean +>T11 : boolean + +type T20 = T; +>T20 : T + +type T21 = { [K in keyof T]: T20 }; +>T21 : T21 + +type Container = { +>Container : Container + + value: T +>value : T +} + +type UnwrapContainers[]> = { [K in keyof T]: T[K]['value'] }; +>UnwrapContainers : UnwrapContainers + +declare function createContainer(value: T): Container; +>createContainer : (value: T) => Container +>value : T + +declare function f[]>(containers: [...T], callback: (...values: UnwrapContainers) => void): void; +>f : []>(containers: [...T], callback: (...values: UnwrapContainers) => void) => void +>containers : [...T] +>callback : (...values: UnwrapContainers) => void +>values : UnwrapContainers + +const container1 = createContainer('hi') +>container1 : Container +>createContainer('hi') : Container +>createContainer : (value: T) => Container +>'hi' : "hi" + +const container2 = createContainer(2) +>container2 : Container +>createContainer(2) : Container +>createContainer : (value: T) => Container +>2 : 2 + +f([container1, container2], (value1, value2) => { +>f([container1, container2], (value1, value2) => { value1; // string value2; // number}) : void +>f : []>(containers: [...T], callback: (...values: UnwrapContainers) => void) => void +>[container1, container2] : [Container, Container] +>container1 : Container +>container2 : Container +>(value1, value2) => { value1; // string value2; // number} : (value1: string, value2: number) => void +>value1 : string +>value2 : number + + value1; // string +>value1 : string + + value2; // number +>value2 : number + +}); + diff --git a/tests/cases/conformance/types/literal/numericStringLiteralTypes.ts b/tests/cases/conformance/types/literal/numericStringLiteralTypes.ts new file mode 100644 index 0000000000000..cb12bde1d45b7 --- /dev/null +++ b/tests/cases/conformance/types/literal/numericStringLiteralTypes.ts @@ -0,0 +1,40 @@ +// @strict: true +// @declaration: true + +type T0 = string & `${string}`; // string +type T1 = string & `${number}`; // `${number} +type T2 = string & `${bigint}`; // `${bigint} +type T3 = string & `${T}`; // `${T} +type T4 = string & `${Capitalize<`${T}`>}`; // `${Capitalize}` + +function f1(a: boolean[], x: `${number}`) { + let s = a[x]; // boolean +} + +function f2(a: boolean[], x: number | `${number}`) { + let s = a[x]; // boolean +} + +type T10 = boolean[][`${number}`]; // boolean +type T11 = boolean[][number | `${number}`]; // boolean + +type T20 = T; +type T21 = { [K in keyof T]: T20 }; + +type Container = { + value: T +} + +type UnwrapContainers[]> = { [K in keyof T]: T[K]['value'] }; + +declare function createContainer(value: T): Container; + +declare function f[]>(containers: [...T], callback: (...values: UnwrapContainers) => void): void; + +const container1 = createContainer('hi') +const container2 = createContainer(2) + +f([container1, container2], (value1, value2) => { + value1; // string + value2; // number +});