From 3b1d6c969d90b8d41b5bdfce13bc1955512b53de Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 2 Dec 2016 14:29:03 -0800 Subject: [PATCH 1/4] Treat indexed access types T[K] as type variables --- src/compiler/checker.ts | 161 +++++++++++++++++++++------------------- src/compiler/types.ts | 25 ++++--- 2 files changed, 99 insertions(+), 87 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 02cde0d4412..81f982e4dc0 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4513,10 +4513,10 @@ namespace ts { setStructuredTypeMembers(type, emptySymbols, emptyArray, emptyArray, undefined, undefined); // In { [P in K]: T }, we refer to P as the type parameter type, K as the constraint type, // and T as the template type. If K is of the form 'keyof S', the mapped type and S are - // isomorphic and we copy property modifiers from corresponding properties in S. + // homomorphic and we copy property modifiers from corresponding properties in S. const typeParameter = getTypeParameterFromMappedType(type); const constraintType = getConstraintTypeFromMappedType(type); - const isomorphicType = getIsomorphicTypeFromMappedType(type); + const homomorphicType = getHomomorphicTypeFromMappedType(type); const templateType = getTemplateTypeFromMappedType(type); const templateReadonly = !!type.declaration.readonlyToken; const templateOptional = !!type.declaration.questionToken; @@ -4536,11 +4536,11 @@ namespace ts { // Otherwise, for type string create a string index signature. if (t.flags & TypeFlags.StringLiteral) { const propName = (t).text; - const isomorphicProp = isomorphicType && getPropertyOfType(isomorphicType, propName); - const isOptional = templateOptional || !!(isomorphicProp && isomorphicProp.flags & SymbolFlags.Optional); + const homomorphicProp = homomorphicType && getPropertyOfType(homomorphicType, propName); + const isOptional = templateOptional || !!(homomorphicProp && homomorphicProp.flags & SymbolFlags.Optional); const prop = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | (isOptional ? SymbolFlags.Optional : 0), propName); prop.type = propType; - prop.isReadonly = templateReadonly || isomorphicProp && isReadonlySymbol(isomorphicProp); + prop.isReadonly = templateReadonly || homomorphicProp && isReadonlySymbol(homomorphicProp); members[propName] = prop; } else if (t.flags & TypeFlags.String) { @@ -4567,7 +4567,7 @@ namespace ts { unknownType); } - function getIsomorphicTypeFromMappedType(type: MappedType) { + function getHomomorphicTypeFromMappedType(type: MappedType) { const constraint = getConstraintDeclaration(getTypeParameterFromMappedType(type)); return constraint.kind === SyntaxKind.TypeOperator ? instantiateType(getTypeFromTypeNode((constraint).type), type.mapper || identityMapper) : undefined; } @@ -5912,7 +5912,7 @@ namespace ts { return links.resolvedType; } - function getIndexTypeForTypeParameter(type: TypeParameter) { + function getIndexTypeForTypeVariable(type: TypeVariable) { if (!type.resolvedIndexType) { type.resolvedIndexType = createType(TypeFlags.Index); type.resolvedIndexType.type = type; @@ -5931,7 +5931,7 @@ namespace ts { } function getIndexType(type: Type): Type { - return type.flags & TypeFlags.TypeParameter ? getIndexTypeForTypeParameter(type) : + return type.flags & TypeFlags.TypeVariable ? getIndexTypeForTypeVariable(type) : getObjectFlags(type) & ObjectFlags.Mapped ? getConstraintTypeFromMappedType(type) : type.flags & TypeFlags.Any || getIndexInfoOfType(type, IndexKind.String) ? stringType : getLiteralTypeFromPropertyNames(type); @@ -6032,13 +6032,14 @@ namespace ts { } function getIndexedAccessType(objectType: Type, indexType: Type, accessNode?: ElementAccessExpression | IndexedAccessTypeNode) { - if (indexType.flags & TypeFlags.TypeParameter || - objectType.flags & TypeFlags.TypeParameter && indexType.flags & TypeFlags.Index || + if (indexType.flags & TypeFlags.TypeVariable || + objectType.flags & TypeFlags.TypeVariable && indexType.flags & TypeFlags.Index || isGenericMappedType(objectType)) { - // If either the object type or the index type are type parameters, or if the object type is a mapped - // type with a generic constraint, we are performing a higher-order index access where we cannot - // meaningfully access the properties of the object type. In those cases, we first check that the - // index type is assignable to 'keyof T' for the object type. + // If the object type is a type variable (a type parameter or another indexed access type), if the + // index type is a type variable or an index type, or if the object type is a mapped type with a + // generic constraint, we are performing a higher-order index access where we cannot meaningfully + // access the properties of the object type. In those cases, we first check that the index type is + // assignable to 'keyof T' for the object type. if (accessNode) { if (!isTypeAssignableTo(indexType, getIndexType(objectType))) { error(accessNode, Diagnostics.Type_0_cannot_be_used_to_index_type_1, typeToString(indexType), typeToString(objectType)); @@ -6531,19 +6532,19 @@ namespace ts { } function instantiateMappedType(type: MappedType, mapper: TypeMapper): Type { - // Check if we have an isomorphic mapped type, i.e. a type of the form { [P in keyof T]: X } for some - // type parameter T. If so, the mapped type is distributive over a union type and when T is instantiated + // Check if we have a homomorphic mapped type, i.e. a type of the form { [P in keyof T]: X } for some + // type variable T. If so, the mapped type is distributive over a union type and when T is instantiated // to a union type A | B, we produce { [P in keyof A]: X } | { [P in keyof B]: X }. Furthermore, for - // isomorphic mapped types we leave primitive types alone. For example, when T is instantiated to a + // homomorphic mapped types we leave primitive types alone. For example, when T is instantiated to a // union type A | undefined, we produce { [P in keyof A]: X } | undefined. const constraintType = getConstraintTypeFromMappedType(type); if (constraintType.flags & TypeFlags.Index) { - const typeParameter = (constraintType).type; - const mappedTypeParameter = mapper(typeParameter); - if (typeParameter !== mappedTypeParameter) { - return mapType(mappedTypeParameter, t => { + const typeVariable = (constraintType).type; + const mappedTypeVariable = instantiateType(typeVariable, mapper); + if (typeVariable !== mappedTypeVariable) { + return mapType(mappedTypeVariable, t => { if (isMappableType(t)) { - const replacementMapper = createUnaryTypeMapper(typeParameter, t); + const replacementMapper = createUnaryTypeMapper(typeVariable, t); const combinedMapper = mapper.mappedTypes && mapper.mappedTypes.length === 1 ? replacementMapper : combineTypeMappers(replacementMapper, mapper); combinedMapper.mappedTypes = mapper.mappedTypes; return instantiateMappedObjectType(type, combinedMapper); @@ -7274,10 +7275,12 @@ namespace ts { } // Given a type parameter T with a constraint C, a type S is assignable to // keyof T if S is assignable to keyof C. - const constraint = getConstraintOfTypeParameter((target).type); - if (constraint) { - if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) { - return result; + if ((target).type.flags & TypeFlags.TypeParameter) { + const constraint = getConstraintOfTypeParameter((target).type); + if (constraint) { + if (result = isRelatedTo(source, getIndexType(constraint), reportErrors)) { + return result; + } } } } @@ -8458,69 +8461,68 @@ namespace ts { // Return true if the given type could possibly reference a type parameter for which // we perform type inference (i.e. a type parameter of a generic function). We cache // results for union and intersection types for performance reasons. - function couldContainTypeParameters(type: Type): boolean { + function couldContainTypeVariables(type: Type): boolean { const objectFlags = getObjectFlags(type); - return !!(type.flags & (TypeFlags.TypeParameter | TypeFlags.IndexedAccess) || - objectFlags & ObjectFlags.Reference && forEach((type).typeArguments, couldContainTypeParameters) || + return !!(type.flags & TypeFlags.TypeVariable || + objectFlags & ObjectFlags.Reference && forEach((type).typeArguments, couldContainTypeVariables) || objectFlags & ObjectFlags.Anonymous && type.symbol && type.symbol.flags & (SymbolFlags.Method | SymbolFlags.TypeLiteral | SymbolFlags.Class) || objectFlags & ObjectFlags.Mapped || - type.flags & TypeFlags.UnionOrIntersection && couldUnionOrIntersectionContainTypeParameters(type)); + type.flags & TypeFlags.UnionOrIntersection && couldUnionOrIntersectionContainTypeVariables(type)); } - function couldUnionOrIntersectionContainTypeParameters(type: UnionOrIntersectionType): boolean { - if (type.couldContainTypeParameters === undefined) { - type.couldContainTypeParameters = forEach(type.types, couldContainTypeParameters); + function couldUnionOrIntersectionContainTypeVariables(type: UnionOrIntersectionType): boolean { + if (type.couldContainTypeVariables === undefined) { + type.couldContainTypeVariables = forEach(type.types, couldContainTypeVariables); } - return type.couldContainTypeParameters; + return type.couldContainTypeVariables; } function isTypeParameterAtTopLevel(type: Type, typeParameter: TypeParameter): boolean { return type === typeParameter || type.flags & TypeFlags.UnionOrIntersection && forEach((type).types, t => isTypeParameterAtTopLevel(t, typeParameter)); } - // Infer a suitable input type for an isomorphic mapped type { [P in keyof T]: X }. We construct + // Infer a suitable input type for a homomorphic mapped type { [P in keyof T]: X }. We construct // an object type with the same set of properties as the source type, where the type of each - // property is computed by inferring from the source property type to X for a synthetic type - // parameter T[P] (i.e. we treat the type T[P] as the type parameter we're inferring for). - function inferTypeForIsomorphicMappedType(source: Type, target: MappedType): Type { - if (!isMappableType(source)) { - return source; + // property is computed by inferring from the source property type to X for the type + // variable T[P] (i.e. we treat the type T[P] as the type variable we're inferring for). + function inferTypeForHomomorphicMappedType(source: Type, target: MappedType): Type { + const properties = getPropertiesOfType(source); + let indexInfo = getIndexInfoOfType(source, IndexKind.String); + if (properties.length === 0 && !indexInfo) { + return undefined; } - const typeParameter = getIndexedAccessType((getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target)); - const typeParameterArray = [typeParameter]; + const typeVariable = getIndexedAccessType((getConstraintTypeFromMappedType(target)).type, getTypeParameterFromMappedType(target)); + const typeVariableArray = [typeVariable]; const typeInferences = createTypeInferencesObject(); const typeInferencesArray = [typeInferences]; const templateType = getTemplateTypeFromMappedType(target); const readonlyMask = target.declaration.readonlyToken ? false : true; const optionalMask = target.declaration.questionToken ? 0 : SymbolFlags.Optional; - const properties = getPropertiesOfType(source); const members = createSymbolTable(properties); - let hasInferredTypes = false; for (const prop of properties) { const inferredPropType = inferTargetType(getTypeOfSymbol(prop)); - if (inferredPropType) { - const inferredProp = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | prop.flags & optionalMask, prop.name); - inferredProp.declarations = prop.declarations; - inferredProp.type = inferredPropType; - inferredProp.isReadonly = readonlyMask && isReadonlySymbol(prop); - members[prop.name] = inferredProp; - hasInferredTypes = true; + if (!inferredPropType) { + return undefined; } + const inferredProp = createSymbol(SymbolFlags.Property | SymbolFlags.Transient | prop.flags & optionalMask, prop.name); + inferredProp.declarations = prop.declarations; + inferredProp.type = inferredPropType; + inferredProp.isReadonly = readonlyMask && isReadonlySymbol(prop); + members[prop.name] = inferredProp; } - let indexInfo = getIndexInfoOfType(source, IndexKind.String); if (indexInfo) { const inferredIndexType = inferTargetType(indexInfo.type); - if (inferredIndexType) { - indexInfo = createIndexInfo(inferredIndexType, readonlyMask && indexInfo.isReadonly); - hasInferredTypes = true; + if (!inferredIndexType) { + return undefined; } + indexInfo = createIndexInfo(inferredIndexType, readonlyMask && indexInfo.isReadonly); } - return hasInferredTypes ? createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined) : source; + return createAnonymousType(undefined, members, emptyArray, emptyArray, indexInfo, undefined); function inferTargetType(sourceType: Type): Type { typeInferences.primary = undefined; typeInferences.secondary = undefined; - inferTypes(typeParameterArray, typeInferencesArray, sourceType, templateType); + inferTypes(typeVariableArray, typeInferencesArray, sourceType, templateType); const inferences = typeInferences.primary || typeInferences.secondary; return inferences && getUnionType(inferences, /*subtypeReduction*/ true); } @@ -8530,7 +8532,7 @@ namespace ts { inferTypes(context.signature.typeParameters, context.inferences, originalSource, originalTarget); } - function inferTypes(typeParameters: Type[], typeInferences: TypeInferences[], originalSource: Type, originalTarget: Type) { + function inferTypes(typeVariables: TypeVariable[], typeInferences: TypeInferences[], originalSource: Type, originalTarget: Type) { let sourceStack: Type[]; let targetStack: Type[]; let depth = 0; @@ -8548,7 +8550,7 @@ namespace ts { } function inferFromTypes(source: Type, target: Type) { - if (!couldContainTypeParameters(target)) { + if (!couldContainTypeVariables(target)) { return; } if (source.aliasSymbol && source.aliasTypeArguments && source.aliasSymbol === target.aliasSymbol) { @@ -8598,7 +8600,7 @@ namespace ts { target = removeTypesFromUnionOrIntersection(target, matchingTypes); } } - if (target.flags & (TypeFlags.TypeParameter | TypeFlags.IndexedAccess)) { + if (target.flags & TypeFlags.TypeVariable) { // If target is a type parameter, make an inference, unless the source type contains // the anyFunctionType (the wildcard type that's used to avoid contextually typing functions). // Because the anyFunctionType is internal, it should not be exposed to the user by adding @@ -8608,8 +8610,8 @@ namespace ts { if (source.flags & TypeFlags.ContainsAnyFunctionType) { return; } - for (let i = 0; i < typeParameters.length; i++) { - if (target === typeParameters[i]) { + for (let i = 0; i < typeVariables.length; i++) { + if (target === typeVariables[i]) { const inferences = typeInferences[i]; if (!inferences.isFixed) { // Any inferences that are made to a type parameter in a union type are inferior @@ -8643,24 +8645,24 @@ namespace ts { } else if (target.flags & TypeFlags.UnionOrIntersection) { const targetTypes = (target).types; - let typeParameterCount = 0; - let typeParameter: TypeParameter; - // First infer to each type in union or intersection that isn't a type parameter + let typeVariableCount = 0; + let typeVariable: TypeVariable; + // First infer to each type in union or intersection that isn't a type variable for (const t of targetTypes) { - if (t.flags & TypeFlags.TypeParameter && contains(typeParameters, t)) { - typeParameter = t; - typeParameterCount++; + if (t.flags & TypeFlags.TypeVariable && contains(typeVariables, t)) { + typeVariable = t; + typeVariableCount++; } else { inferFromTypes(source, t); } } - // Next, if target containings a single naked type parameter, make a secondary inference to that type - // parameter. This gives meaningful results for union types in co-variant positions and intersection + // Next, if target containings a single naked type variable, make a secondary inference to that type + // variable. This gives meaningful results for union types in co-variant positions and intersection // types in contra-variant positions (such as callback parameters). - if (typeParameterCount === 1) { + if (typeVariableCount === 1) { inferiority++; - inferFromTypes(source, typeParameter); + inferFromTypes(source, typeVariable); inferiority--; } } @@ -8702,12 +8704,15 @@ namespace ts { if (getObjectFlags(target) & ObjectFlags.Mapped) { const constraintType = getConstraintTypeFromMappedType(target); if (constraintType.flags & TypeFlags.Index) { - // We're inferring from some source type S to an isomorphic mapped type { [P in keyof T]: X }, - // where T is a type parameter. Use inferTypeForIsomorphicMappedType to infer a suitable source + // We're inferring from some source type S to a homomorphic mapped type { [P in keyof T]: X }, + // where T is a type variable. Use inferTypeForHomomorphicMappedType to infer a suitable source // type and then infer from that type to T. - const index = indexOf(typeParameters, (constraintType).type); + const index = indexOf(typeVariables, (constraintType).type); if (index >= 0 && !typeInferences[index].isFixed) { - inferFromTypes(inferTypeForIsomorphicMappedType(source, target), typeParameters[index]); + const inferredType = inferTypeForHomomorphicMappedType(source, target); + if (inferredType) { + inferFromTypes(inferredType, typeVariables[index]); + } } return; } @@ -14388,7 +14393,7 @@ namespace ts { if (!(isTypeComparableTo(leftType, stringType) || isTypeOfKind(leftType, TypeFlags.NumberLike | TypeFlags.ESSymbol))) { error(left, Diagnostics.The_left_hand_side_of_an_in_expression_must_be_of_type_any_string_number_or_symbol); } - if (!isTypeAnyOrAllConstituentTypesHaveKind(rightType, TypeFlags.Object | TypeFlags.TypeParameter | TypeFlags.IndexedAccess)) { + if (!isTypeAnyOrAllConstituentTypesHaveKind(rightType, TypeFlags.Object | TypeFlags.TypeVariable)) { error(right, Diagnostics.The_right_hand_side_of_an_in_expression_must_be_of_type_any_an_object_type_or_a_type_parameter); } return booleanType; @@ -17331,7 +17336,7 @@ namespace ts { // unknownType is returned i.e. if node.expression is identifier whose name cannot be resolved // in this case error about missing name is already reported - do not report extra one - if (!isTypeAnyOrAllConstituentTypesHaveKind(rightType, TypeFlags.Object | TypeFlags.TypeParameter | TypeFlags.IndexedAccess)) { + if (!isTypeAnyOrAllConstituentTypesHaveKind(rightType, TypeFlags.Object | TypeFlags.TypeVariable)) { error(node.expression, Diagnostics.The_right_hand_side_of_a_for_in_statement_must_be_of_type_any_an_object_type_or_a_type_parameter); } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index bc400df257d..aa96b35d758 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2797,6 +2797,7 @@ namespace ts { UnionOrIntersection = Union | Intersection, StructuredType = Object | Union | Intersection, StructuredOrTypeParameter = StructuredType | TypeParameter | Index, + TypeVariable = TypeParameter | IndexedAccess, // 'Narrowable' types are types where narrowing actually narrows. // This *should* be every type other than null, undefined, void, and never @@ -2907,7 +2908,7 @@ namespace ts { /* @internal */ resolvedProperties: SymbolTable; // Cache of resolved properties /* @internal */ - couldContainTypeParameters: boolean; + couldContainTypeVariables: boolean; } export interface UnionType extends UnionOrIntersectionType { } @@ -2963,8 +2964,13 @@ namespace ts { iteratorElementType?: Type; } + export interface TypeVariable extends Type { + /* @internal */ + resolvedIndexType: IndexType; + } + // Type parameters (TypeFlags.TypeParameter) - export interface TypeParameter extends Type { + export interface TypeParameter extends TypeVariable { constraint: Type; // Constraint /* @internal */ target?: TypeParameter; // Instantiation target @@ -2973,20 +2979,21 @@ namespace ts { /* @internal */ resolvedApparentType: Type; /* @internal */ - resolvedIndexType: IndexType; - /* @internal */ isThisType?: boolean; } - export interface IndexType extends Type { - type: TypeParameter; - } - - export interface IndexedAccessType extends Type { + // Indexed access types (TypeFlags.IndexedAccess) + // Possible forms are T[xxx], xxx[T], or xxx[keyof T], where T is a type variable + export interface IndexedAccessType extends TypeVariable { objectType: Type; indexType: Type; } + // keyof T types (TypeFlags.Index) + export interface IndexType extends Type { + type: TypeVariable; + } + export const enum SignatureKind { Call, Construct, From b3b23597a8362c1501b810ecd67048a39ee7794d Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 2 Dec 2016 14:29:23 -0800 Subject: [PATCH 2/4] Accept new baselines --- tests/baselines/reference/keyofAndIndexedAccess.types | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/keyofAndIndexedAccess.types b/tests/baselines/reference/keyofAndIndexedAccess.types index 587100496e9..fa54c614f07 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.types +++ b/tests/baselines/reference/keyofAndIndexedAccess.types @@ -826,7 +826,7 @@ function f54(obj: T, key: keyof T) { >T : T for (let s in obj[key]) { ->s : string +>s : keyof T[keyof T] >obj[key] : T[keyof T] >obj : T >key : keyof T @@ -851,7 +851,7 @@ function f55(obj: T, key: K) { >K : K for (let s in obj[key]) { ->s : string +>s : keyof T[K] >obj[key] : T[K] >obj : T >key : K From 1e2425ebfce32a5207578214bf15c5591778a0a7 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 2 Dec 2016 14:50:14 -0800 Subject: [PATCH 3/4] Add tests --- .../types/keyof/keyofAndIndexedAccess.ts | 42 +++++++++++++++++++ .../mapped/isomorphicMappedTypeInference.ts | 27 +++++++++++- 2 files changed, 68 insertions(+), 1 deletion(-) diff --git a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts index 9aa05798607..3cad745402f 100644 --- a/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts +++ b/tests/cases/conformance/types/keyof/keyofAndIndexedAccess.ts @@ -250,3 +250,45 @@ class OtherPerson { return getProperty(this, "parts") } } + +// Modified repro from #12544 + +function path(obj: T, key1: K1): T[K1]; +function path(obj: T, key1: K1, key2: K2): T[K1][K2]; +function path(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; +function path(obj: any, ...keys: (string | number)[]): any; +function path(obj: any, ...keys: (string | number)[]): any { + let result = obj; + for (let k of keys) { + result = result[k]; + } + return result; +} + +type Thing = { + a: { x: number, y: string }, + b: boolean +}; + + +function f1(thing: Thing) { + let x1 = path(thing, 'a'); // { x: number, y: string } + let x2 = path(thing, 'a', 'y'); // string + let x3 = path(thing, 'b'); // boolean + let x4 = path(thing, ...['a', 'x']); // any +} + +// Repro from comment in #12114 + +const assignTo2 = (object: T, key1: K1, key2: K2) => + (value: T[K1][K2]) => object[key1][key2] = value; + +// Modified repro from #12573 + +declare function one(handler: (t: T) => void): T +var empty = one(() => {}) // inferred as {}, expected + +type Handlers = { [K in keyof T]: (t: T[K]) => void } +declare function on(handlerHash: Handlers): T +var hashOfEmpty1 = on({ test: () => {} }); // {} +var hashOfEmpty2 = on({ test: (x: boolean) => {} }); // { test: boolean } \ No newline at end of file diff --git a/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts b/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts index 4aab2d95ba3..330d99dd7f1 100644 --- a/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts +++ b/tests/cases/conformance/types/mapped/isomorphicMappedTypeInference.ts @@ -120,4 +120,29 @@ function f10(foo: Foo) { let x = validate(foo); // { a: number, readonly b: string } let y = clone(foo); // { a?: number, b: string } let z = validateAndClone(foo); // { a: number, b: string } -} \ No newline at end of file +} + +// Repro from #12606 + +type Func = (...args: any[]) => T; +type Spec = { + [P in keyof T]: Func | Spec ; +}; + +/** + * Given a spec object recursively mapping properties to functions, creates a function + * producing an object of the same structure, by mapping each property to the result + * of calling its associated function with the supplied arguments. + */ +declare function applySpec(obj: Spec): (...args: any[]) => T; + +// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } } +var g1 = applySpec({ + sum: (a: any) => 3, + nested: { + mul: (b: any) => "n" + } +}); + +// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } } +var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } }); \ No newline at end of file From fe0b66a00c5e11cd593ab99c99e8ab4262973e80 Mon Sep 17 00:00:00 2001 From: Anders Hejlsberg Date: Fri, 2 Dec 2016 14:50:21 -0800 Subject: [PATCH 4/4] Accept new baselines --- .../isomorphicMappedTypeInference.js | 59 ++++- .../isomorphicMappedTypeInference.symbols | 66 ++++++ .../isomorphicMappedTypeInference.types | 79 +++++++ .../reference/keyofAndIndexedAccess.js | 91 +++++++- .../reference/keyofAndIndexedAccess.symbols | 193 +++++++++++++++ .../reference/keyofAndIndexedAccess.types | 220 ++++++++++++++++++ 6 files changed, 706 insertions(+), 2 deletions(-) diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.js b/tests/baselines/reference/isomorphicMappedTypeInference.js index 87f3e1c4017..0ed2285ed04 100644 --- a/tests/baselines/reference/isomorphicMappedTypeInference.js +++ b/tests/baselines/reference/isomorphicMappedTypeInference.js @@ -118,7 +118,32 @@ function f10(foo: Foo) { let x = validate(foo); // { a: number, readonly b: string } let y = clone(foo); // { a?: number, b: string } let z = validateAndClone(foo); // { a: number, b: string } -} +} + +// Repro from #12606 + +type Func = (...args: any[]) => T; +type Spec = { + [P in keyof T]: Func | Spec ; +}; + +/** + * Given a spec object recursively mapping properties to functions, creates a function + * producing an object of the same structure, by mapping each property to the result + * of calling its associated function with the supplied arguments. + */ +declare function applySpec(obj: Spec): (...args: any[]) => T; + +// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } } +var g1 = applySpec({ + sum: (a: any) => 3, + nested: { + mul: (b: any) => "n" + } +}); + +// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } } +var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } }); //// [isomorphicMappedTypeInference.js] function box(x) { @@ -210,6 +235,15 @@ function f10(foo) { var y = clone(foo); // { a?: number, b: string } var z = validateAndClone(foo); // { a: number, b: string } } +// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } } +var g1 = applySpec({ + sum: function (a) { return 3; }, + nested: { + mul: function (b) { return "n"; } + } +}); +// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } } +var g2 = applySpec({ foo: { bar: { baz: function (x) { return true; } } } }); //// [isomorphicMappedTypeInference.d.ts] @@ -254,3 +288,26 @@ declare type Foo = { readonly b: string; }; declare function f10(foo: Foo): void; +declare type Func = (...args: any[]) => T; +declare type Spec = { + [P in keyof T]: Func | Spec; +}; +/** + * Given a spec object recursively mapping properties to functions, creates a function + * producing an object of the same structure, by mapping each property to the result + * of calling its associated function with the supplied arguments. + */ +declare function applySpec(obj: Spec): (...args: any[]) => T; +declare var g1: (...args: any[]) => { + sum: number; + nested: { + mul: string; + }; +}; +declare var g2: (...args: any[]) => { + foo: { + bar: { + baz: boolean; + }; + }; +}; diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.symbols b/tests/baselines/reference/isomorphicMappedTypeInference.symbols index 3e8a92e7691..92bcbb697ec 100644 --- a/tests/baselines/reference/isomorphicMappedTypeInference.symbols +++ b/tests/baselines/reference/isomorphicMappedTypeInference.symbols @@ -393,3 +393,69 @@ function f10(foo: Foo) { >validateAndClone : Symbol(validateAndClone, Decl(isomorphicMappedTypeInference.ts, 107, 69)) >foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 115, 13)) } + +// Repro from #12606 + +type Func = (...args: any[]) => T; +>Func : Symbol(Func, Decl(isomorphicMappedTypeInference.ts, 119, 1)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 123, 10)) +>args : Symbol(args, Decl(isomorphicMappedTypeInference.ts, 123, 16)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 123, 10)) + +type Spec = { +>Spec : Symbol(Spec, Decl(isomorphicMappedTypeInference.ts, 123, 37)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10)) + + [P in keyof T]: Func | Spec ; +>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 125, 5)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10)) +>Func : Symbol(Func, Decl(isomorphicMappedTypeInference.ts, 119, 1)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10)) +>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 125, 5)) +>Spec : Symbol(Spec, Decl(isomorphicMappedTypeInference.ts, 123, 37)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 124, 10)) +>P : Symbol(P, Decl(isomorphicMappedTypeInference.ts, 125, 5)) + +}; + +/** + * Given a spec object recursively mapping properties to functions, creates a function + * producing an object of the same structure, by mapping each property to the result + * of calling its associated function with the supplied arguments. + */ +declare function applySpec(obj: Spec): (...args: any[]) => T; +>applySpec : Symbol(applySpec, Decl(isomorphicMappedTypeInference.ts, 126, 2)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 133, 27)) +>obj : Symbol(obj, Decl(isomorphicMappedTypeInference.ts, 133, 30)) +>Spec : Symbol(Spec, Decl(isomorphicMappedTypeInference.ts, 123, 37)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 133, 27)) +>args : Symbol(args, Decl(isomorphicMappedTypeInference.ts, 133, 46)) +>T : Symbol(T, Decl(isomorphicMappedTypeInference.ts, 133, 27)) + +// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } } +var g1 = applySpec({ +>g1 : Symbol(g1, Decl(isomorphicMappedTypeInference.ts, 136, 3)) +>applySpec : Symbol(applySpec, Decl(isomorphicMappedTypeInference.ts, 126, 2)) + + sum: (a: any) => 3, +>sum : Symbol(sum, Decl(isomorphicMappedTypeInference.ts, 136, 20)) +>a : Symbol(a, Decl(isomorphicMappedTypeInference.ts, 137, 10)) + + nested: { +>nested : Symbol(nested, Decl(isomorphicMappedTypeInference.ts, 137, 23)) + + mul: (b: any) => "n" +>mul : Symbol(mul, Decl(isomorphicMappedTypeInference.ts, 138, 13)) +>b : Symbol(b, Decl(isomorphicMappedTypeInference.ts, 139, 14)) + } +}); + +// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } } +var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } }); +>g2 : Symbol(g2, Decl(isomorphicMappedTypeInference.ts, 144, 3)) +>applySpec : Symbol(applySpec, Decl(isomorphicMappedTypeInference.ts, 126, 2)) +>foo : Symbol(foo, Decl(isomorphicMappedTypeInference.ts, 144, 20)) +>bar : Symbol(bar, Decl(isomorphicMappedTypeInference.ts, 144, 27)) +>baz : Symbol(baz, Decl(isomorphicMappedTypeInference.ts, 144, 34)) +>x : Symbol(x, Decl(isomorphicMappedTypeInference.ts, 144, 41)) + diff --git a/tests/baselines/reference/isomorphicMappedTypeInference.types b/tests/baselines/reference/isomorphicMappedTypeInference.types index eee2d6cbe7d..b4d1383071b 100644 --- a/tests/baselines/reference/isomorphicMappedTypeInference.types +++ b/tests/baselines/reference/isomorphicMappedTypeInference.types @@ -467,3 +467,82 @@ function f10(foo: Foo) { >validateAndClone : (obj: { readonly [P in keyof T]?: T[P] | undefined; }) => T >foo : Foo } + +// Repro from #12606 + +type Func = (...args: any[]) => T; +>Func : Func +>T : T +>args : any[] +>T : T + +type Spec = { +>Spec : Spec +>T : T + + [P in keyof T]: Func | Spec ; +>P : P +>T : T +>Func : Func +>T : T +>P : P +>Spec : Spec +>T : T +>P : P + +}; + +/** + * Given a spec object recursively mapping properties to functions, creates a function + * producing an object of the same structure, by mapping each property to the result + * of calling its associated function with the supplied arguments. + */ +declare function applySpec(obj: Spec): (...args: any[]) => T; +>applySpec : (obj: Spec) => (...args: any[]) => T +>T : T +>obj : Spec +>Spec : Spec +>T : T +>args : any[] +>T : T + +// Infers g1: (...args: any[]) => { sum: number, nested: { mul: string } } +var g1 = applySpec({ +>g1 : (...args: any[]) => { sum: number; nested: { mul: string; }; } +>applySpec({ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }}) : (...args: any[]) => { sum: number; nested: { mul: string; }; } +>applySpec : (obj: Spec) => (...args: any[]) => T +>{ sum: (a: any) => 3, nested: { mul: (b: any) => "n" }} : { sum: (a: any) => number; nested: { mul: (b: any) => string; }; } + + sum: (a: any) => 3, +>sum : (a: any) => number +>(a: any) => 3 : (a: any) => number +>a : any +>3 : 3 + + nested: { +>nested : { mul: (b: any) => string; } +>{ mul: (b: any) => "n" } : { mul: (b: any) => string; } + + mul: (b: any) => "n" +>mul : (b: any) => string +>(b: any) => "n" : (b: any) => string +>b : any +>"n" : "n" + } +}); + +// Infers g2: (...args: any[]) => { foo: { bar: { baz: boolean } } } +var g2 = applySpec({ foo: { bar: { baz: (x: any) => true } } }); +>g2 : (...args: any[]) => { foo: { bar: { baz: boolean; }; }; } +>applySpec({ foo: { bar: { baz: (x: any) => true } } }) : (...args: any[]) => { foo: { bar: { baz: boolean; }; }; } +>applySpec : (obj: Spec) => (...args: any[]) => T +>{ foo: { bar: { baz: (x: any) => true } } } : { foo: { bar: { baz: (x: any) => boolean; }; }; } +>foo : { bar: { baz: (x: any) => boolean; }; } +>{ bar: { baz: (x: any) => true } } : { bar: { baz: (x: any) => boolean; }; } +>bar : { baz: (x: any) => boolean; } +>{ baz: (x: any) => true } : { baz: (x: any) => boolean; } +>baz : (x: any) => boolean +>(x: any) => true : (x: any) => boolean +>x : any +>true : true + diff --git a/tests/baselines/reference/keyofAndIndexedAccess.js b/tests/baselines/reference/keyofAndIndexedAccess.js index 8d8f125f39e..0e630be9a43 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.js +++ b/tests/baselines/reference/keyofAndIndexedAccess.js @@ -250,7 +250,48 @@ class OtherPerson { return getProperty(this, "parts") } } - + +// Modified repro from #12544 + +function path(obj: T, key1: K1): T[K1]; +function path(obj: T, key1: K1, key2: K2): T[K1][K2]; +function path(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; +function path(obj: any, ...keys: (string | number)[]): any; +function path(obj: any, ...keys: (string | number)[]): any { + let result = obj; + for (let k of keys) { + result = result[k]; + } + return result; +} + +type Thing = { + a: { x: number, y: string }, + b: boolean +}; + + +function f1(thing: Thing) { + let x1 = path(thing, 'a'); // { x: number, y: string } + let x2 = path(thing, 'a', 'y'); // string + let x3 = path(thing, 'b'); // boolean + let x4 = path(thing, ...['a', 'x']); // any +} + +// Repro from comment in #12114 + +const assignTo2 = (object: T, key1: K1, key2: K2) => + (value: T[K1][K2]) => object[key1][key2] = value; + +// Modified repro from #12573 + +declare function one(handler: (t: T) => void): T +var empty = one(() => {}) // inferred as {}, expected + +type Handlers = { [K in keyof T]: (t: T[K]) => void } +declare function on(handlerHash: Handlers): T +var hashOfEmpty1 = on({ test: () => {} }); // {} +var hashOfEmpty2 = on({ test: (x: boolean) => {} }); // { test: boolean } //// [keyofAndIndexedAccess.js] var __extends = (this && this.__extends) || function (d, b) { @@ -430,6 +471,31 @@ var OtherPerson = (function () { }; return OtherPerson; }()); +function path(obj) { + var keys = []; + for (var _i = 1; _i < arguments.length; _i++) { + keys[_i - 1] = arguments[_i]; + } + var result = obj; + for (var _a = 0, keys_1 = keys; _a < keys_1.length; _a++) { + var k = keys_1[_a]; + result = result[k]; + } + return result; +} +function f1(thing) { + var x1 = path(thing, 'a'); // { x: number, y: string } + var x2 = path(thing, 'a', 'y'); // string + var x3 = path(thing, 'b'); // boolean + var x4 = path.apply(void 0, [thing].concat(['a', 'x'])); // any +} +// Repro from comment in #12114 +var assignTo2 = function (object, key1, key2) { + return function (value) { return object[key1][key2] = value; }; +}; +var empty = one(function () { }); // inferred as {}, expected +var hashOfEmpty1 = on({ test: function () { } }); // {} +var hashOfEmpty2 = on({ test: function (x) { } }); // { test: boolean } //// [keyofAndIndexedAccess.d.ts] @@ -551,3 +617,26 @@ declare class OtherPerson { constructor(parts: number); getParts(): number; } +declare function path(obj: T, key1: K1): T[K1]; +declare function path(obj: T, key1: K1, key2: K2): T[K1][K2]; +declare function path(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; +declare function path(obj: any, ...keys: (string | number)[]): any; +declare type Thing = { + a: { + x: number; + y: string; + }; + b: boolean; +}; +declare function f1(thing: Thing): void; +declare const assignTo2: (object: T, key1: K1, key2: K2) => (value: T[K1][K2]) => T[K1][K2]; +declare function one(handler: (t: T) => void): T; +declare var empty: {}; +declare type Handlers = { + [K in keyof T]: (t: T[K]) => void; +}; +declare function on(handlerHash: Handlers): T; +declare var hashOfEmpty1: {}; +declare var hashOfEmpty2: { + test: boolean; +}; diff --git a/tests/baselines/reference/keyofAndIndexedAccess.symbols b/tests/baselines/reference/keyofAndIndexedAccess.symbols index 2371bf419c8..e67db316dad 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.symbols +++ b/tests/baselines/reference/keyofAndIndexedAccess.symbols @@ -848,3 +848,196 @@ class OtherPerson { } } +// Modified repro from #12544 + +function path(obj: T, key1: K1): T[K1]; +>path : Symbol(path, Decl(keyofAndIndexedAccess.ts, 250, 1), Decl(keyofAndIndexedAccess.ts, 254, 62), Decl(keyofAndIndexedAccess.ts, 255, 100), Decl(keyofAndIndexedAccess.ts, 256, 142), Decl(keyofAndIndexedAccess.ts, 257, 59)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 254, 14)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 254, 16)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 254, 14)) +>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 254, 37)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 254, 14)) +>key1 : Symbol(key1, Decl(keyofAndIndexedAccess.ts, 254, 44)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 254, 16)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 254, 14)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 254, 16)) + +function path(obj: T, key1: K1, key2: K2): T[K1][K2]; +>path : Symbol(path, Decl(keyofAndIndexedAccess.ts, 250, 1), Decl(keyofAndIndexedAccess.ts, 254, 62), Decl(keyofAndIndexedAccess.ts, 255, 100), Decl(keyofAndIndexedAccess.ts, 256, 142), Decl(keyofAndIndexedAccess.ts, 257, 59)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 255, 14)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 255, 16)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 255, 14)) +>K2 : Symbol(K2, Decl(keyofAndIndexedAccess.ts, 255, 36)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 255, 14)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 255, 16)) +>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 255, 61)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 255, 14)) +>key1 : Symbol(key1, Decl(keyofAndIndexedAccess.ts, 255, 68)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 255, 16)) +>key2 : Symbol(key2, Decl(keyofAndIndexedAccess.ts, 255, 78)) +>K2 : Symbol(K2, Decl(keyofAndIndexedAccess.ts, 255, 36)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 255, 14)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 255, 16)) +>K2 : Symbol(K2, Decl(keyofAndIndexedAccess.ts, 255, 36)) + +function path(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; +>path : Symbol(path, Decl(keyofAndIndexedAccess.ts, 250, 1), Decl(keyofAndIndexedAccess.ts, 254, 62), Decl(keyofAndIndexedAccess.ts, 255, 100), Decl(keyofAndIndexedAccess.ts, 256, 142), Decl(keyofAndIndexedAccess.ts, 257, 59)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 256, 14)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 256, 16)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 256, 14)) +>K2 : Symbol(K2, Decl(keyofAndIndexedAccess.ts, 256, 36)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 256, 14)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 256, 16)) +>K3 : Symbol(K3, Decl(keyofAndIndexedAccess.ts, 256, 60)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 256, 14)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 256, 16)) +>K2 : Symbol(K2, Decl(keyofAndIndexedAccess.ts, 256, 36)) +>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 256, 89)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 256, 14)) +>key1 : Symbol(key1, Decl(keyofAndIndexedAccess.ts, 256, 96)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 256, 16)) +>key2 : Symbol(key2, Decl(keyofAndIndexedAccess.ts, 256, 106)) +>K2 : Symbol(K2, Decl(keyofAndIndexedAccess.ts, 256, 36)) +>key3 : Symbol(key3, Decl(keyofAndIndexedAccess.ts, 256, 116)) +>K3 : Symbol(K3, Decl(keyofAndIndexedAccess.ts, 256, 60)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 256, 14)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 256, 16)) +>K2 : Symbol(K2, Decl(keyofAndIndexedAccess.ts, 256, 36)) +>K3 : Symbol(K3, Decl(keyofAndIndexedAccess.ts, 256, 60)) + +function path(obj: any, ...keys: (string | number)[]): any; +>path : Symbol(path, Decl(keyofAndIndexedAccess.ts, 250, 1), Decl(keyofAndIndexedAccess.ts, 254, 62), Decl(keyofAndIndexedAccess.ts, 255, 100), Decl(keyofAndIndexedAccess.ts, 256, 142), Decl(keyofAndIndexedAccess.ts, 257, 59)) +>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 257, 14)) +>keys : Symbol(keys, Decl(keyofAndIndexedAccess.ts, 257, 23)) + +function path(obj: any, ...keys: (string | number)[]): any { +>path : Symbol(path, Decl(keyofAndIndexedAccess.ts, 250, 1), Decl(keyofAndIndexedAccess.ts, 254, 62), Decl(keyofAndIndexedAccess.ts, 255, 100), Decl(keyofAndIndexedAccess.ts, 256, 142), Decl(keyofAndIndexedAccess.ts, 257, 59)) +>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 258, 14)) +>keys : Symbol(keys, Decl(keyofAndIndexedAccess.ts, 258, 23)) + + let result = obj; +>result : Symbol(result, Decl(keyofAndIndexedAccess.ts, 259, 7)) +>obj : Symbol(obj, Decl(keyofAndIndexedAccess.ts, 258, 14)) + + for (let k of keys) { +>k : Symbol(k, Decl(keyofAndIndexedAccess.ts, 260, 12)) +>keys : Symbol(keys, Decl(keyofAndIndexedAccess.ts, 258, 23)) + + result = result[k]; +>result : Symbol(result, Decl(keyofAndIndexedAccess.ts, 259, 7)) +>result : Symbol(result, Decl(keyofAndIndexedAccess.ts, 259, 7)) +>k : Symbol(k, Decl(keyofAndIndexedAccess.ts, 260, 12)) + } + return result; +>result : Symbol(result, Decl(keyofAndIndexedAccess.ts, 259, 7)) +} + +type Thing = { +>Thing : Symbol(Thing, Decl(keyofAndIndexedAccess.ts, 264, 1)) + + a: { x: number, y: string }, +>a : Symbol(a, Decl(keyofAndIndexedAccess.ts, 266, 14)) +>x : Symbol(x, Decl(keyofAndIndexedAccess.ts, 267, 8)) +>y : Symbol(y, Decl(keyofAndIndexedAccess.ts, 267, 19)) + + b: boolean +>b : Symbol(b, Decl(keyofAndIndexedAccess.ts, 267, 32)) + +}; + + +function f1(thing: Thing) { +>f1 : Symbol(f1, Decl(keyofAndIndexedAccess.ts, 269, 2)) +>thing : Symbol(thing, Decl(keyofAndIndexedAccess.ts, 272, 12)) +>Thing : Symbol(Thing, Decl(keyofAndIndexedAccess.ts, 264, 1)) + + let x1 = path(thing, 'a'); // { x: number, y: string } +>x1 : Symbol(x1, Decl(keyofAndIndexedAccess.ts, 273, 7)) +>path : Symbol(path, Decl(keyofAndIndexedAccess.ts, 250, 1), Decl(keyofAndIndexedAccess.ts, 254, 62), Decl(keyofAndIndexedAccess.ts, 255, 100), Decl(keyofAndIndexedAccess.ts, 256, 142), Decl(keyofAndIndexedAccess.ts, 257, 59)) +>thing : Symbol(thing, Decl(keyofAndIndexedAccess.ts, 272, 12)) + + let x2 = path(thing, 'a', 'y'); // string +>x2 : Symbol(x2, Decl(keyofAndIndexedAccess.ts, 274, 7)) +>path : Symbol(path, Decl(keyofAndIndexedAccess.ts, 250, 1), Decl(keyofAndIndexedAccess.ts, 254, 62), Decl(keyofAndIndexedAccess.ts, 255, 100), Decl(keyofAndIndexedAccess.ts, 256, 142), Decl(keyofAndIndexedAccess.ts, 257, 59)) +>thing : Symbol(thing, Decl(keyofAndIndexedAccess.ts, 272, 12)) + + let x3 = path(thing, 'b'); // boolean +>x3 : Symbol(x3, Decl(keyofAndIndexedAccess.ts, 275, 7)) +>path : Symbol(path, Decl(keyofAndIndexedAccess.ts, 250, 1), Decl(keyofAndIndexedAccess.ts, 254, 62), Decl(keyofAndIndexedAccess.ts, 255, 100), Decl(keyofAndIndexedAccess.ts, 256, 142), Decl(keyofAndIndexedAccess.ts, 257, 59)) +>thing : Symbol(thing, Decl(keyofAndIndexedAccess.ts, 272, 12)) + + let x4 = path(thing, ...['a', 'x']); // any +>x4 : Symbol(x4, Decl(keyofAndIndexedAccess.ts, 276, 7)) +>path : Symbol(path, Decl(keyofAndIndexedAccess.ts, 250, 1), Decl(keyofAndIndexedAccess.ts, 254, 62), Decl(keyofAndIndexedAccess.ts, 255, 100), Decl(keyofAndIndexedAccess.ts, 256, 142), Decl(keyofAndIndexedAccess.ts, 257, 59)) +>thing : Symbol(thing, Decl(keyofAndIndexedAccess.ts, 272, 12)) +} + +// Repro from comment in #12114 + +const assignTo2 = (object: T, key1: K1, key2: K2) => +>assignTo2 : Symbol(assignTo2, Decl(keyofAndIndexedAccess.ts, 281, 5)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 281, 19)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 281, 21)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 281, 19)) +>K2 : Symbol(K2, Decl(keyofAndIndexedAccess.ts, 281, 41)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 281, 19)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 281, 21)) +>object : Symbol(object, Decl(keyofAndIndexedAccess.ts, 281, 66)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 281, 19)) +>key1 : Symbol(key1, Decl(keyofAndIndexedAccess.ts, 281, 76)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 281, 21)) +>key2 : Symbol(key2, Decl(keyofAndIndexedAccess.ts, 281, 86)) +>K2 : Symbol(K2, Decl(keyofAndIndexedAccess.ts, 281, 41)) + + (value: T[K1][K2]) => object[key1][key2] = value; +>value : Symbol(value, Decl(keyofAndIndexedAccess.ts, 282, 5)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 281, 19)) +>K1 : Symbol(K1, Decl(keyofAndIndexedAccess.ts, 281, 21)) +>K2 : Symbol(K2, Decl(keyofAndIndexedAccess.ts, 281, 41)) +>object : Symbol(object, Decl(keyofAndIndexedAccess.ts, 281, 66)) +>key1 : Symbol(key1, Decl(keyofAndIndexedAccess.ts, 281, 76)) +>key2 : Symbol(key2, Decl(keyofAndIndexedAccess.ts, 281, 86)) +>value : Symbol(value, Decl(keyofAndIndexedAccess.ts, 282, 5)) + +// Modified repro from #12573 + +declare function one(handler: (t: T) => void): T +>one : Symbol(one, Decl(keyofAndIndexedAccess.ts, 282, 53)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 286, 21)) +>handler : Symbol(handler, Decl(keyofAndIndexedAccess.ts, 286, 24)) +>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 286, 34)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 286, 21)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 286, 21)) + +var empty = one(() => {}) // inferred as {}, expected +>empty : Symbol(empty, Decl(keyofAndIndexedAccess.ts, 287, 3)) +>one : Symbol(one, Decl(keyofAndIndexedAccess.ts, 282, 53)) + +type Handlers = { [K in keyof T]: (t: T[K]) => void } +>Handlers : Symbol(Handlers, Decl(keyofAndIndexedAccess.ts, 287, 25)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 289, 14)) +>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 289, 22)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 289, 14)) +>t : Symbol(t, Decl(keyofAndIndexedAccess.ts, 289, 38)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 289, 14)) +>K : Symbol(K, Decl(keyofAndIndexedAccess.ts, 289, 22)) + +declare function on(handlerHash: Handlers): T +>on : Symbol(on, Decl(keyofAndIndexedAccess.ts, 289, 56)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 290, 20)) +>handlerHash : Symbol(handlerHash, Decl(keyofAndIndexedAccess.ts, 290, 23)) +>Handlers : Symbol(Handlers, Decl(keyofAndIndexedAccess.ts, 287, 25)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 290, 20)) +>T : Symbol(T, Decl(keyofAndIndexedAccess.ts, 290, 20)) + +var hashOfEmpty1 = on({ test: () => {} }); // {} +>hashOfEmpty1 : Symbol(hashOfEmpty1, Decl(keyofAndIndexedAccess.ts, 291, 3)) +>on : Symbol(on, Decl(keyofAndIndexedAccess.ts, 289, 56)) +>test : Symbol(test, Decl(keyofAndIndexedAccess.ts, 291, 23)) + +var hashOfEmpty2 = on({ test: (x: boolean) => {} }); // { test: boolean } +>hashOfEmpty2 : Symbol(hashOfEmpty2, Decl(keyofAndIndexedAccess.ts, 292, 3)) +>on : Symbol(on, Decl(keyofAndIndexedAccess.ts, 289, 56)) +>test : Symbol(test, Decl(keyofAndIndexedAccess.ts, 292, 23)) +>x : Symbol(x, Decl(keyofAndIndexedAccess.ts, 292, 31)) + diff --git a/tests/baselines/reference/keyofAndIndexedAccess.types b/tests/baselines/reference/keyofAndIndexedAccess.types index fa54c614f07..9e1e7d4b4d1 100644 --- a/tests/baselines/reference/keyofAndIndexedAccess.types +++ b/tests/baselines/reference/keyofAndIndexedAccess.types @@ -982,3 +982,223 @@ class OtherPerson { } } +// Modified repro from #12544 + +function path(obj: T, key1: K1): T[K1]; +>path : { (obj: T, key1: K1): T[K1]; (obj: T, key1: K1, key2: K2): T[K1][K2]; (obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; (obj: any, ...keys: (string | number)[]): any; } +>T : T +>K1 : K1 +>T : T +>obj : T +>T : T +>key1 : K1 +>K1 : K1 +>T : T +>K1 : K1 + +function path(obj: T, key1: K1, key2: K2): T[K1][K2]; +>path : { (obj: T, key1: K1): T[K1]; (obj: T, key1: K1, key2: K2): T[K1][K2]; (obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; (obj: any, ...keys: (string | number)[]): any; } +>T : T +>K1 : K1 +>T : T +>K2 : K2 +>T : T +>K1 : K1 +>obj : T +>T : T +>key1 : K1 +>K1 : K1 +>key2 : K2 +>K2 : K2 +>T : T +>K1 : K1 +>K2 : K2 + +function path(obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; +>path : { (obj: T, key1: K1): T[K1]; (obj: T, key1: K1, key2: K2): T[K1][K2]; (obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; (obj: any, ...keys: (string | number)[]): any; } +>T : T +>K1 : K1 +>T : T +>K2 : K2 +>T : T +>K1 : K1 +>K3 : K3 +>T : T +>K1 : K1 +>K2 : K2 +>obj : T +>T : T +>key1 : K1 +>K1 : K1 +>key2 : K2 +>K2 : K2 +>key3 : K3 +>K3 : K3 +>T : T +>K1 : K1 +>K2 : K2 +>K3 : K3 + +function path(obj: any, ...keys: (string | number)[]): any; +>path : { (obj: T, key1: K1): T[K1]; (obj: T, key1: K1, key2: K2): T[K1][K2]; (obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; (obj: any, ...keys: (string | number)[]): any; } +>obj : any +>keys : (string | number)[] + +function path(obj: any, ...keys: (string | number)[]): any { +>path : { (obj: T, key1: K1): T[K1]; (obj: T, key1: K1, key2: K2): T[K1][K2]; (obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; (obj: any, ...keys: (string | number)[]): any; } +>obj : any +>keys : (string | number)[] + + let result = obj; +>result : any +>obj : any + + for (let k of keys) { +>k : string | number +>keys : (string | number)[] + + result = result[k]; +>result = result[k] : any +>result : any +>result[k] : any +>result : any +>k : string | number + } + return result; +>result : any +} + +type Thing = { +>Thing : Thing + + a: { x: number, y: string }, +>a : { x: number; y: string; } +>x : number +>y : string + + b: boolean +>b : boolean + +}; + + +function f1(thing: Thing) { +>f1 : (thing: Thing) => void +>thing : Thing +>Thing : Thing + + let x1 = path(thing, 'a'); // { x: number, y: string } +>x1 : { x: number; y: string; } +>path(thing, 'a') : { x: number; y: string; } +>path : { (obj: T, key1: K1): T[K1]; (obj: T, key1: K1, key2: K2): T[K1][K2]; (obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; (obj: any, ...keys: (string | number)[]): any; } +>thing : Thing +>'a' : "a" + + let x2 = path(thing, 'a', 'y'); // string +>x2 : string +>path(thing, 'a', 'y') : string +>path : { (obj: T, key1: K1): T[K1]; (obj: T, key1: K1, key2: K2): T[K1][K2]; (obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; (obj: any, ...keys: (string | number)[]): any; } +>thing : Thing +>'a' : "a" +>'y' : "y" + + let x3 = path(thing, 'b'); // boolean +>x3 : boolean +>path(thing, 'b') : boolean +>path : { (obj: T, key1: K1): T[K1]; (obj: T, key1: K1, key2: K2): T[K1][K2]; (obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; (obj: any, ...keys: (string | number)[]): any; } +>thing : Thing +>'b' : "b" + + let x4 = path(thing, ...['a', 'x']); // any +>x4 : any +>path(thing, ...['a', 'x']) : any +>path : { (obj: T, key1: K1): T[K1]; (obj: T, key1: K1, key2: K2): T[K1][K2]; (obj: T, key1: K1, key2: K2, key3: K3): T[K1][K2][K3]; (obj: any, ...keys: (string | number)[]): any; } +>thing : Thing +>...['a', 'x'] : string +>['a', 'x'] : string[] +>'a' : "a" +>'x' : "x" +} + +// Repro from comment in #12114 + +const assignTo2 = (object: T, key1: K1, key2: K2) => +>assignTo2 : (object: T, key1: K1, key2: K2) => (value: T[K1][K2]) => T[K1][K2] +>(object: T, key1: K1, key2: K2) => (value: T[K1][K2]) => object[key1][key2] = value : (object: T, key1: K1, key2: K2) => (value: T[K1][K2]) => T[K1][K2] +>T : T +>K1 : K1 +>T : T +>K2 : K2 +>T : T +>K1 : K1 +>object : T +>T : T +>key1 : K1 +>K1 : K1 +>key2 : K2 +>K2 : K2 + + (value: T[K1][K2]) => object[key1][key2] = value; +>(value: T[K1][K2]) => object[key1][key2] = value : (value: T[K1][K2]) => T[K1][K2] +>value : T[K1][K2] +>T : T +>K1 : K1 +>K2 : K2 +>object[key1][key2] = value : T[K1][K2] +>object[key1][key2] : T[K1][K2] +>object[key1] : T[K1] +>object : T +>key1 : K1 +>key2 : K2 +>value : T[K1][K2] + +// Modified repro from #12573 + +declare function one(handler: (t: T) => void): T +>one : (handler: (t: T) => void) => T +>T : T +>handler : (t: T) => void +>t : T +>T : T +>T : T + +var empty = one(() => {}) // inferred as {}, expected +>empty : {} +>one(() => {}) : {} +>one : (handler: (t: T) => void) => T +>() => {} : () => void + +type Handlers = { [K in keyof T]: (t: T[K]) => void } +>Handlers : Handlers +>T : T +>K : K +>T : T +>t : T[K] +>T : T +>K : K + +declare function on(handlerHash: Handlers): T +>on : (handlerHash: Handlers) => T +>T : T +>handlerHash : Handlers +>Handlers : Handlers +>T : T +>T : T + +var hashOfEmpty1 = on({ test: () => {} }); // {} +>hashOfEmpty1 : {} +>on({ test: () => {} }) : {} +>on : (handlerHash: Handlers) => T +>{ test: () => {} } : { test: () => void; } +>test : () => void +>() => {} : () => void + +var hashOfEmpty2 = on({ test: (x: boolean) => {} }); // { test: boolean } +>hashOfEmpty2 : { test: boolean; } +>on({ test: (x: boolean) => {} }) : { test: boolean; } +>on : (handlerHash: Handlers) => T +>{ test: (x: boolean) => {} } : { test: (x: boolean) => void; } +>test : (x: boolean) => void +>(x: boolean) => {} : (x: boolean) => void +>x : boolean +