From 34515759c014eecda74aab16a465c2121c3f51b7 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 17 Nov 2017 20:36:43 -0800 Subject: [PATCH 1/6] Infer type parameters from indexes on those parameters --- src/compiler/checker.ts | 90 ++++++++++++++----- src/compiler/types.ts | 4 + .../reference/api/tsserverlibrary.d.ts | 1 + tests/baselines/reference/api/typescript.d.ts | 1 + .../reference/indexAccessCombinedInference.js | 29 ++++++ .../indexAccessCombinedInference.symbols | 46 ++++++++++ .../indexAccessCombinedInference.types | 54 +++++++++++ .../baselines/reference/inferingFromAny.types | 2 +- .../compiler/indexAccessCombinedInference.ts | 17 ++++ 9 files changed, 221 insertions(+), 23 deletions(-) create mode 100644 tests/baselines/reference/indexAccessCombinedInference.js create mode 100644 tests/baselines/reference/indexAccessCombinedInference.symbols create mode 100644 tests/baselines/reference/indexAccessCombinedInference.types create mode 100644 tests/cases/compiler/indexAccessCombinedInference.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 87d95b64d31cb..60d817623eaed 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2645,8 +2645,8 @@ namespace ts { function createMappedTypeNodeFromType(type: MappedType) { Debug.assert(!!(type.flags & TypeFlags.Object)); - const readonlyToken = type.declaration && type.declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; - const questionToken = type.declaration && type.declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; + const readonlyToken = isReadonlyMappedType(type) ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; + const questionToken = isOptionalMappedType(type) ? createToken(SyntaxKind.QuestionToken) : undefined; const typeParameterNode = typeParameterToDeclaration(getTypeParameterFromMappedType(type), context); const templateTypeNode = typeToTypeNodeHelper(getTemplateTypeFromMappedType(type), context); @@ -3717,7 +3717,7 @@ namespace ts { writePunctuation(writer, SyntaxKind.OpenBraceToken); writer.writeLine(); writer.increaseIndent(); - if (type.declaration.readonlyToken) { + if (isReadonlyMappedType(type)) { writeKeyword(writer, SyntaxKind.ReadonlyKeyword); writeSpace(writer); } @@ -3728,7 +3728,7 @@ namespace ts { writeSpace(writer); writeType(getConstraintTypeFromMappedType(type), TypeFormatFlags.None); writePunctuation(writer, SyntaxKind.CloseBracketToken); - if (type.declaration.questionToken) { + if (isOptionalMappedType(type)) { writePunctuation(writer, SyntaxKind.QuestionToken); } writePunctuation(writer, SyntaxKind.ColonToken); @@ -6108,11 +6108,9 @@ namespace ts { const constraintType = getConstraintTypeFromMappedType(type); const templateType = getTemplateTypeFromMappedType(type); const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' - const templateReadonly = !!type.declaration.readonlyToken; - const templateOptional = !!type.declaration.questionToken; - const constraintDeclaration = type.declaration.typeParameter.constraint; - if (constraintDeclaration.kind === SyntaxKind.TypeOperator && - (constraintDeclaration).operator === SyntaxKind.KeyOfKeyword) { + const templateReadonly = isReadonlyMappedType(type); + const templateOptional = isOptionalMappedType(type); + if (isPossiblyHomomorphicMappedType(type)) { // We have a { [P in keyof T]: X } for (const propertySymbol of getPropertiesOfType(modifiersType)) { addMemberForKeyType(getLiteralTypeFromPropertyName(propertySymbol), propertySymbol); @@ -6180,15 +6178,14 @@ namespace ts { function getTemplateTypeFromMappedType(type: MappedType) { return type.templateType || (type.templateType = type.declaration.type ? - instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), !!type.declaration.questionToken), type.mapper || identityMapper) : + instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), isOptionalMappedType(type)), type.mapper || identityMapper) : unknownType); } function getModifiersTypeFromMappedType(type: MappedType) { if (!type.modifiersType) { - const constraintDeclaration = type.declaration.typeParameter.constraint; - if (constraintDeclaration.kind === SyntaxKind.TypeOperator && - (constraintDeclaration).operator === SyntaxKind.KeyOfKeyword) { + if (isPossiblyHomomorphicMappedType(type)) { + const constraintDeclaration = type.declaration.typeParameter.constraint; // If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check // AST nodes here because, when T is a non-generic type, the logic below eagerly resolves // 'keyof T' to a literal union type and we can't recover T from that type. @@ -6208,8 +6205,8 @@ namespace ts { } function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers { - return (type.declaration.readonlyToken ? MappedTypeModifiers.Readonly : 0) | - (type.declaration.questionToken ? MappedTypeModifiers.Optional : 0); + return (isReadonlyMappedType(type) ? MappedTypeModifiers.Readonly : 0) | + (isOptionalMappedType(type) ? MappedTypeModifiers.Optional : 0); } function getCombinedMappedTypeModifiers(type: MappedType): MappedTypeModifiers { @@ -6219,7 +6216,7 @@ namespace ts { } function isPartialMappedType(type: Type) { - return getObjectFlags(type) & ObjectFlags.Mapped && !!(type).declaration.questionToken; + return getObjectFlags(type) & ObjectFlags.Mapped && isOptionalMappedType(type as MappedType); } function isGenericMappedType(type: Type) { @@ -9885,7 +9882,7 @@ namespace ts { if (target.flags & TypeFlags.TypeParameter) { // A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P]. if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(source) === getIndexType(target)) { - if (!(source).declaration.questionToken) { + if (!isOptionalMappedType(source)) { const templateType = getTemplateTypeFromMappedType(source); const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source)); if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) { @@ -11197,7 +11194,8 @@ namespace ts { inferredType: undefined, priority: undefined, topLevel: true, - isFixed: false + isFixed: false, + indexes: undefined, }; } @@ -11208,7 +11206,8 @@ namespace ts { inferredType: inference.inferredType, priority: inference.priority, topLevel: inference.topLevel, - isFixed: inference.isFixed + isFixed: inference.isFixed, + indexes: inference.indexes && inference.indexes.slice(), }; } @@ -11271,8 +11270,8 @@ namespace ts { const inference = createInferenceInfo(typeParameter); const inferences = [inference]; const templateType = getTemplateTypeFromMappedType(target); - const readonlyMask = target.declaration.readonlyToken ? false : true; - const optionalMask = target.declaration.questionToken ? 0 : SymbolFlags.Optional; + const readonlyMask = isReadonlyMappedType(target) ? false : true; + const optionalMask = isOptionalMappedType(target) ? 0 : SymbolFlags.Optional; const members = createSymbolTable(); for (const prop of properties) { const propType = getTypeOfSymbol(prop); @@ -11399,6 +11398,27 @@ namespace ts { } return; } + else if (target.flags & TypeFlags.IndexedAccess) { + const targetConstraint = (target).objectType; + const inference = getInferenceInfoForType(targetConstraint); + if (inference) { + if (!inference.isFixed) { + const map = createObjectType(ObjectFlags.Mapped); + map.templateType = source; + map.constraintType = (target).indexType; + map.typeParameter = createType(TypeFlags.TypeParameter); + // TODO (weswigham): Ensure the name chosen for the unused "K" does not shadow any other type variables in the given scope, so as to not have a chance of breaking declaration emit + map.typeParameter.symbol = createSymbol(SymbolFlags.TypeParameter, "K" as __String); + map.typeParameter.constraint = map.constraintType; + map.modifiersType = (target).indexType; + map.hasQuestionToken = false; + map.hasReadonlyToken = false; + map.hasPossiblyHomomorphicConstraint = false; + (inference.indexes || (inference.indexes = [])).push(map); + } + return; + } + } } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { // If source and target are references to the same generic type, infer from type arguments @@ -11665,6 +11685,10 @@ namespace ts { const inference = context.inferences[index]; let inferredType = inference.inferredType; if (!inferredType) { + if (inference.indexes) { + // Build a candidate from all indexes + (inference.candidates || (inference.candidates = [])).push(getIntersectionType(inference.indexes)); + } if (inference.candidates) { // Extract all object literal types and replace them with a single widened and normalized type. const candidates = widenObjectLiteralCandidates(inference.candidates); @@ -20088,6 +20112,28 @@ namespace ts { forEach(node.types, checkSourceElement); } + function isReadonlyMappedType(type: MappedType) { + if (type.hasReadonlyToken === undefined) { + type.hasReadonlyToken = !!type.declaration.readonlyToken; + } + return type.hasReadonlyToken; + } + + function isOptionalMappedType(type: MappedType) { + if (type.hasQuestionToken === undefined) { + type.hasQuestionToken = !!type.declaration.questionToken; + } + return type.hasQuestionToken; + } + + function isPossiblyHomomorphicMappedType(type: MappedType) { + if (type.hasPossiblyHomomorphicConstraint === undefined) { + const constraint = type.declaration.typeParameter.constraint; + type.hasPossiblyHomomorphicConstraint = isTypeOperatorNode(constraint) && constraint.operator === SyntaxKind.KeyOfKeyword; + } + return type.hasPossiblyHomomorphicConstraint; + } + function checkIndexedAccessIndexType(type: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode) { if (!(type.flags & TypeFlags.IndexedAccess)) { return type; @@ -20097,7 +20143,7 @@ namespace ts { const indexType = (type).indexType; if (isTypeAssignableTo(indexType, getIndexType(objectType))) { if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && - getObjectFlags(objectType) & ObjectFlags.Mapped && (objectType).declaration.readonlyToken) { + getObjectFlags(objectType) & ObjectFlags.Mapped && isReadonlyMappedType(objectType)) { error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); } return type; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index dfd6831be42f7..890746dc27636 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3594,6 +3594,9 @@ namespace ts { constraintType?: Type; templateType?: Type; modifiersType?: Type; + hasQuestionToken?: boolean; + hasReadonlyToken?: boolean; + hasPossiblyHomomorphicConstraint?: boolean; } export interface EvolvingArrayType extends ObjectType { @@ -3744,6 +3747,7 @@ namespace ts { export interface InferenceInfo { typeParameter: TypeParameter; candidates: Type[]; + indexes: Type[]; // Partial candidates created by indexed accesses inferredType: Type; priority: InferencePriority; topLevel: boolean; diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index ad34924a097f9..c566c2df83734 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -2154,6 +2154,7 @@ declare namespace ts { interface InferenceInfo { typeParameter: TypeParameter; candidates: Type[]; + indexes: Type[]; inferredType: Type; priority: InferencePriority; topLevel: boolean; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index 4d06e48dc2565..fefe085fe11db 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -2154,6 +2154,7 @@ declare namespace ts { interface InferenceInfo { typeParameter: TypeParameter; candidates: Type[]; + indexes: Type[]; inferredType: Type; priority: InferencePriority; topLevel: boolean; diff --git a/tests/baselines/reference/indexAccessCombinedInference.js b/tests/baselines/reference/indexAccessCombinedInference.js new file mode 100644 index 0000000000000..a3f19fa7fcc67 --- /dev/null +++ b/tests/baselines/reference/indexAccessCombinedInference.js @@ -0,0 +1,29 @@ +//// [indexAccessCombinedInference.ts] +interface Args { + TA: object, + TY: object +} + +function foo( + a: T["TA"], + b: T["TY"]): T["TA"] & T["TY"] { + return undefined!; +} + +const x = foo({ + x: { + j: 12, + i: 11 + } +}, { y: 42 }); + +//// [indexAccessCombinedInference.js] +function foo(a, b) { + return undefined; +} +var x = foo({ + x: { + j: 12, + i: 11 + } +}, { y: 42 }); diff --git a/tests/baselines/reference/indexAccessCombinedInference.symbols b/tests/baselines/reference/indexAccessCombinedInference.symbols new file mode 100644 index 0000000000000..c422efe422c8c --- /dev/null +++ b/tests/baselines/reference/indexAccessCombinedInference.symbols @@ -0,0 +1,46 @@ +=== tests/cases/compiler/indexAccessCombinedInference.ts === +interface Args { +>Args : Symbol(Args, Decl(indexAccessCombinedInference.ts, 0, 0)) + + TA: object, +>TA : Symbol(Args.TA, Decl(indexAccessCombinedInference.ts, 0, 16)) + + TY: object +>TY : Symbol(Args.TY, Decl(indexAccessCombinedInference.ts, 1, 15)) +} + +function foo( +>foo : Symbol(foo, Decl(indexAccessCombinedInference.ts, 3, 1)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 5, 13)) +>Args : Symbol(Args, Decl(indexAccessCombinedInference.ts, 0, 0)) + + a: T["TA"], +>a : Symbol(a, Decl(indexAccessCombinedInference.ts, 5, 29)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 5, 13)) + + b: T["TY"]): T["TA"] & T["TY"] { +>b : Symbol(b, Decl(indexAccessCombinedInference.ts, 6, 15)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 5, 13)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 5, 13)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 5, 13)) + + return undefined!; +>undefined : Symbol(undefined) +} + +const x = foo({ +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 11, 5)) +>foo : Symbol(foo, Decl(indexAccessCombinedInference.ts, 3, 1)) + + x: { +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 11, 15)) + + j: 12, +>j : Symbol(j, Decl(indexAccessCombinedInference.ts, 12, 8)) + + i: 11 +>i : Symbol(i, Decl(indexAccessCombinedInference.ts, 13, 14)) + } +}, { y: 42 }); +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 16, 4)) + diff --git a/tests/baselines/reference/indexAccessCombinedInference.types b/tests/baselines/reference/indexAccessCombinedInference.types new file mode 100644 index 0000000000000..f32c28de6266f --- /dev/null +++ b/tests/baselines/reference/indexAccessCombinedInference.types @@ -0,0 +1,54 @@ +=== tests/cases/compiler/indexAccessCombinedInference.ts === +interface Args { +>Args : Args + + TA: object, +>TA : object + + TY: object +>TY : object +} + +function foo( +>foo : (a: T["TA"], b: T["TY"]) => T["TA"] & T["TY"] +>T : T +>Args : Args + + a: T["TA"], +>a : T["TA"] +>T : T + + b: T["TY"]): T["TA"] & T["TY"] { +>b : T["TY"] +>T : T +>T : T +>T : T + + return undefined!; +>undefined! : undefined +>undefined : undefined +} + +const x = foo({ +>x : { x: { j: number; i: number; }; } & { y: number; } +>foo({ x: { j: 12, i: 11 }}, { y: 42 }) : { x: { j: number; i: number; }; } & { y: number; } +>foo : (a: T["TA"], b: T["TY"]) => T["TA"] & T["TY"] +>{ x: { j: 12, i: 11 }} : { x: { j: number; i: number; }; } + + x: { +>x : { j: number; i: number; } +>{ j: 12, i: 11 } : { j: number; i: number; } + + j: 12, +>j : number +>12 : 12 + + i: 11 +>i : number +>11 : 11 + } +}, { y: 42 }); +>{ y: 42 } : { y: number; } +>y : number +>42 : 42 + diff --git a/tests/baselines/reference/inferingFromAny.types b/tests/baselines/reference/inferingFromAny.types index 72e2024d0224e..dc660cdebc17d 100644 --- a/tests/baselines/reference/inferingFromAny.types +++ b/tests/baselines/reference/inferingFromAny.types @@ -293,7 +293,7 @@ var a = f18(a); var a = f19(a, a); >a : any ->f19(a, a) : any +>f19(a, a) : { [K in K]: any; } >f19 : (k: K, x: T[K]) => T >a : any >a : any diff --git a/tests/cases/compiler/indexAccessCombinedInference.ts b/tests/cases/compiler/indexAccessCombinedInference.ts new file mode 100644 index 0000000000000..c1d7f03ac22ee --- /dev/null +++ b/tests/cases/compiler/indexAccessCombinedInference.ts @@ -0,0 +1,17 @@ +interface Args { + TA: object, + TY: object +} + +function foo( + a: T["TA"], + b: T["TY"]): T["TA"] & T["TY"] { + return undefined!; +} + +const x = foo({ + x: { + j: 12, + i: 11 + } +}, { y: 42 }); \ No newline at end of file From f5b208c23f20125dad1d5ba1ffc38cac1c0a7e4b Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 21 Nov 2017 16:57:34 -0800 Subject: [PATCH 2/6] Greatly simplify partial inference type instantiation through use of an alias in lib --- src/compiler/checker.ts | 79 +++++++------------ src/lib/es5.d.ts | 9 +++ .../baselines/reference/inferingFromAny.types | 2 +- 3 files changed, 38 insertions(+), 52 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 60d817623eaed..d5e27dbeba82d 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -2645,8 +2645,8 @@ namespace ts { function createMappedTypeNodeFromType(type: MappedType) { Debug.assert(!!(type.flags & TypeFlags.Object)); - const readonlyToken = isReadonlyMappedType(type) ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; - const questionToken = isOptionalMappedType(type) ? createToken(SyntaxKind.QuestionToken) : undefined; + const readonlyToken = type.declaration && type.declaration.readonlyToken ? createToken(SyntaxKind.ReadonlyKeyword) : undefined; + const questionToken = type.declaration && type.declaration.questionToken ? createToken(SyntaxKind.QuestionToken) : undefined; const typeParameterNode = typeParameterToDeclaration(getTypeParameterFromMappedType(type), context); const templateTypeNode = typeToTypeNodeHelper(getTemplateTypeFromMappedType(type), context); @@ -3717,7 +3717,7 @@ namespace ts { writePunctuation(writer, SyntaxKind.OpenBraceToken); writer.writeLine(); writer.increaseIndent(); - if (isReadonlyMappedType(type)) { + if (type.declaration.readonlyToken) { writeKeyword(writer, SyntaxKind.ReadonlyKeyword); writeSpace(writer); } @@ -3728,7 +3728,7 @@ namespace ts { writeSpace(writer); writeType(getConstraintTypeFromMappedType(type), TypeFormatFlags.None); writePunctuation(writer, SyntaxKind.CloseBracketToken); - if (isOptionalMappedType(type)) { + if (type.declaration.questionToken) { writePunctuation(writer, SyntaxKind.QuestionToken); } writePunctuation(writer, SyntaxKind.ColonToken); @@ -6108,9 +6108,11 @@ namespace ts { const constraintType = getConstraintTypeFromMappedType(type); const templateType = getTemplateTypeFromMappedType(type); const modifiersType = getApparentType(getModifiersTypeFromMappedType(type)); // The 'T' in 'keyof T' - const templateReadonly = isReadonlyMappedType(type); - const templateOptional = isOptionalMappedType(type); - if (isPossiblyHomomorphicMappedType(type)) { + const templateReadonly = !!type.declaration.readonlyToken; + const templateOptional = !!type.declaration.questionToken; + const constraintDeclaration = type.declaration.typeParameter.constraint; + if (constraintDeclaration.kind === SyntaxKind.TypeOperator && + (constraintDeclaration).operator === SyntaxKind.KeyOfKeyword) { // We have a { [P in keyof T]: X } for (const propertySymbol of getPropertiesOfType(modifiersType)) { addMemberForKeyType(getLiteralTypeFromPropertyName(propertySymbol), propertySymbol); @@ -6178,14 +6180,15 @@ namespace ts { function getTemplateTypeFromMappedType(type: MappedType) { return type.templateType || (type.templateType = type.declaration.type ? - instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), isOptionalMappedType(type)), type.mapper || identityMapper) : + instantiateType(addOptionality(getTypeFromTypeNode(type.declaration.type), !!type.declaration.questionToken), type.mapper || identityMapper) : unknownType); } function getModifiersTypeFromMappedType(type: MappedType) { if (!type.modifiersType) { - if (isPossiblyHomomorphicMappedType(type)) { - const constraintDeclaration = type.declaration.typeParameter.constraint; + const constraintDeclaration = type.declaration.typeParameter.constraint; + if (constraintDeclaration.kind === SyntaxKind.TypeOperator && + (constraintDeclaration).operator === SyntaxKind.KeyOfKeyword) { // If the constraint declaration is a 'keyof T' node, the modifiers type is T. We check // AST nodes here because, when T is a non-generic type, the logic below eagerly resolves // 'keyof T' to a literal union type and we can't recover T from that type. @@ -6205,8 +6208,8 @@ namespace ts { } function getMappedTypeModifiers(type: MappedType): MappedTypeModifiers { - return (isReadonlyMappedType(type) ? MappedTypeModifiers.Readonly : 0) | - (isOptionalMappedType(type) ? MappedTypeModifiers.Optional : 0); + return (type.declaration.readonlyToken ? MappedTypeModifiers.Readonly : 0) | + (type.declaration.questionToken ? MappedTypeModifiers.Optional : 0); } function getCombinedMappedTypeModifiers(type: MappedType): MappedTypeModifiers { @@ -6216,7 +6219,7 @@ namespace ts { } function isPartialMappedType(type: Type) { - return getObjectFlags(type) & ObjectFlags.Mapped && isOptionalMappedType(type as MappedType); + return getObjectFlags(type) & ObjectFlags.Mapped && !!(type).declaration.questionToken; } function isGenericMappedType(type: Type) { @@ -9882,7 +9885,7 @@ namespace ts { if (target.flags & TypeFlags.TypeParameter) { // A source type { [P in keyof T]: X } is related to a target type T if X is related to T[P]. if (getObjectFlags(source) & ObjectFlags.Mapped && getConstraintTypeFromMappedType(source) === getIndexType(target)) { - if (!isOptionalMappedType(source)) { + if (!(source).declaration.questionToken) { const templateType = getTemplateTypeFromMappedType(source); const indexedAccessType = getIndexedAccessType(target, getTypeParameterFromMappedType(source)); if (result = isRelatedTo(templateType, indexedAccessType, reportErrors)) { @@ -11270,8 +11273,8 @@ namespace ts { const inference = createInferenceInfo(typeParameter); const inferences = [inference]; const templateType = getTemplateTypeFromMappedType(target); - const readonlyMask = isReadonlyMappedType(target) ? false : true; - const optionalMask = isOptionalMappedType(target) ? 0 : SymbolFlags.Optional; + const readonlyMask = target.declaration.readonlyToken ? false : true; + const optionalMask = target.declaration.questionToken ? 0 : SymbolFlags.Optional; const members = createSymbolTable(); for (const prop of properties) { const propType = getTypeOfSymbol(prop); @@ -11403,18 +11406,14 @@ namespace ts { const inference = getInferenceInfoForType(targetConstraint); if (inference) { if (!inference.isFixed) { - const map = createObjectType(ObjectFlags.Mapped); - map.templateType = source; - map.constraintType = (target).indexType; - map.typeParameter = createType(TypeFlags.TypeParameter); - // TODO (weswigham): Ensure the name chosen for the unused "K" does not shadow any other type variables in the given scope, so as to not have a chance of breaking declaration emit - map.typeParameter.symbol = createSymbol(SymbolFlags.TypeParameter, "K" as __String); - map.typeParameter.constraint = map.constraintType; - map.modifiersType = (target).indexType; - map.hasQuestionToken = false; - map.hasReadonlyToken = false; - map.hasPossiblyHomomorphicConstraint = false; - (inference.indexes || (inference.indexes = [])).push(map); + // Instantiates instance of `type PartialInference = ({[K in Keys]: {[K1 in K]: T}})[Keys];` + // Where `T` is `source` and `Keys` is `target.indexType` + const inferenceTypeSymbol = getGlobalSymbol("PartialInference" as __String, SymbolFlags.Type, Diagnostics.Cannot_find_global_type_0); + const inferenceType = getDeclaredTypeOfSymbol(inferenceTypeSymbol); + if (inferenceType !== unknownType) { + const mapper = createTypeMapper(getSymbolLinks(inferenceTypeSymbol).typeParameters, [source, (target as IndexedAccessType).indexType]); + (inference.indexes || (inference.indexes = [])).push(instantiateType(inferenceType, mapper)); + } } return; } @@ -20112,28 +20111,6 @@ namespace ts { forEach(node.types, checkSourceElement); } - function isReadonlyMappedType(type: MappedType) { - if (type.hasReadonlyToken === undefined) { - type.hasReadonlyToken = !!type.declaration.readonlyToken; - } - return type.hasReadonlyToken; - } - - function isOptionalMappedType(type: MappedType) { - if (type.hasQuestionToken === undefined) { - type.hasQuestionToken = !!type.declaration.questionToken; - } - return type.hasQuestionToken; - } - - function isPossiblyHomomorphicMappedType(type: MappedType) { - if (type.hasPossiblyHomomorphicConstraint === undefined) { - const constraint = type.declaration.typeParameter.constraint; - type.hasPossiblyHomomorphicConstraint = isTypeOperatorNode(constraint) && constraint.operator === SyntaxKind.KeyOfKeyword; - } - return type.hasPossiblyHomomorphicConstraint; - } - function checkIndexedAccessIndexType(type: Type, accessNode: ElementAccessExpression | IndexedAccessTypeNode) { if (!(type.flags & TypeFlags.IndexedAccess)) { return type; @@ -20143,7 +20120,7 @@ namespace ts { const indexType = (type).indexType; if (isTypeAssignableTo(indexType, getIndexType(objectType))) { if (accessNode.kind === SyntaxKind.ElementAccessExpression && isAssignmentTarget(accessNode) && - getObjectFlags(objectType) & ObjectFlags.Mapped && isReadonlyMappedType(objectType)) { + getObjectFlags(objectType) & ObjectFlags.Mapped && (objectType).declaration.readonlyToken) { error(accessNode, Diagnostics.Index_signature_in_type_0_only_permits_reading, typeToString(objectType)); } return type; diff --git a/src/lib/es5.d.ts b/src/lib/es5.d.ts index 0f1a68de4c20d..88eeba043ebf2 100644 --- a/src/lib/es5.d.ts +++ b/src/lib/es5.d.ts @@ -1336,6 +1336,15 @@ type Record = { */ interface ThisType { } +/** + * Type instantiated to perform partial inferences from indexed accesses + */ +type PartialInference = ({ + [K in Keys]: { + [K1 in K]: T + } +})[Keys]; + /** * Represents a raw buffer of binary data, which is used to store data for the * different typed arrays. ArrayBuffers cannot be read from or written to directly, diff --git a/tests/baselines/reference/inferingFromAny.types b/tests/baselines/reference/inferingFromAny.types index dc660cdebc17d..70fa53cf218ea 100644 --- a/tests/baselines/reference/inferingFromAny.types +++ b/tests/baselines/reference/inferingFromAny.types @@ -293,7 +293,7 @@ var a = f18(a); var a = f19(a, a); >a : any ->f19(a, a) : { [K in K]: any; } +>f19(a, a) : { [K in Keys]: { [K1 in K]: any; }; }[K] >f19 : (k: K, x: T[K]) => T >a : any >a : any From ba064acf28177ec1fe61d1b4cedd7a1f1eabb972 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Tue, 21 Nov 2017 17:57:09 -0800 Subject: [PATCH 3/6] Add many more tests showing current behaviors --- .../reference/indexAccessCombinedInference.js | 135 +++++- .../indexAccessCombinedInference.symbols | 368 ++++++++++++++- .../indexAccessCombinedInference.types | 445 +++++++++++++++++- .../compiler/indexAccessCombinedInference.ts | 101 +++- 4 files changed, 1008 insertions(+), 41 deletions(-) diff --git a/tests/baselines/reference/indexAccessCombinedInference.js b/tests/baselines/reference/indexAccessCombinedInference.js index a3f19fa7fcc67..b31a76ef75725 100644 --- a/tests/baselines/reference/indexAccessCombinedInference.js +++ b/tests/baselines/reference/indexAccessCombinedInference.js @@ -1,29 +1,148 @@ //// [indexAccessCombinedInference.ts] +// Simple case interface Args { TA: object, TY: object } -function foo( +declare function foo( a: T["TA"], - b: T["TY"]): T["TA"] & T["TY"] { - return undefined!; -} + b: T["TY"]): T["TA"] & T["TY"]; const x = foo({ x: { j: 12, i: 11 } -}, { y: 42 }); +}, { y: 42 }); + +// Union result type +interface A { + foo: number; +} +interface B { + bar: string; +} +declare const something: A | B; + +const y = foo(something, { bat: 42 }); + +// Union key type +interface Args2 { + TA?: object, // Optional since only one of TA or TB needs to be infered in the below argument list + TB?: object, + TY: object +} +declare function foo2( + a: T["TA"] | T["TB"], + b: T["TY"]): {a: T["TA"], b: T["TB"]} & T["TY"]; +declare function foo3( // Morally equivalent to foo2 + a: T["TA" | "TB"], + b: T["TY"]): {a: T["TA"], b: T["TB"]} & T["TY"]; +let z = foo2({ + x: { + j: 12, + i: 11 + } +}, { y: 42 }); +let zz = foo3({ + x: { + j: 12, + i: 11 + } +}, { y: 42 }); +z = zz; +zz = z; + +// Higher-order +interface Args3 { + Key: "A" | "B", + A: object, + B: object, + Merge: object, +} +declare const either: "A" | "B"; +declare function pickOne(key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; + +const opt1 = pickOne("A", {x: 12}, {y: ""}, {z: /./}); +const opt2 = pickOne("B", {x: 12}, {y: ""}, {z: /./}); +const opt3 = pickOne(either, {x: 12}, {y: ""}, {z: /./}); + +const pickDelayed = (x: TKey) => pickOne(x, {j: x}, {i: x}, {chosen: x}); +const opt4 = pickDelayed("A"); +const opt5 = pickDelayed("B"); +const opt6 = pickDelayed(either); + +// Reopenable +interface Args3 { + /** + * One must make patched parameters optional, otherwise signatures expecting the unpatched + * interface (ie, pickOne above) will not be able to produce a type satisfying the interface + * (as there are no inference sites for the new members) and will fall back to the constraints on each member + */ + Extra?: object, +} +declare function pickOne(key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & {into: T["Merge"], extra: T["Extra"]}; +const opt7 = pickOne("A", {x: 12}, {y: ""}, {z: /./}, {z: /./}); +const opt8 = pickOne("B", {x: 12}, {y: ""}, {z: /./}, {z: /./}); +const opt9 = pickOne(either, {x: 12}, {y: ""}, {z: /./}, {z: /./}); + +// Interactions with `this` types +interface TPicker { + Key: keyof this, + X: number, + Y: string +} +declare function chooseLiteral(choice: T["Key"], x: T["X"], y:T["Y"]): T[T["Key"]]; +const cx = chooseLiteral("X", 1, "no"); +const cy = chooseLiteral("Y", 0, "yes"); +const ceither = chooseLiteral("X" as "X" | "Y", 1, "yes"); +const cneither = chooseLiteral("Key", 0, "no"); + +// Multiple inference sites +interface Args4 { + 0: object, + 1: Record, +} +declare function dualInputs(x: T["0"], y: T["0"], toDelay: T["1"]): T["0"] & {transformers: T["1"]}; + +const result = dualInputs({x: 0}, {x: 1}, {x: () => ""}); + //// [indexAccessCombinedInference.js] -function foo(a, b) { - return undefined; -} var x = foo({ x: { j: 12, i: 11 } }, { y: 42 }); +var y = foo(something, { bat: 42 }); +var z = foo2({ + x: { + j: 12, + i: 11 + } +}, { y: 42 }); +var zz = foo3({ + x: { + j: 12, + i: 11 + } +}, { y: 42 }); +z = zz; +zz = z; +var opt1 = pickOne("A", { x: 12 }, { y: "" }, { z: /./ }); +var opt2 = pickOne("B", { x: 12 }, { y: "" }, { z: /./ }); +var opt3 = pickOne(either, { x: 12 }, { y: "" }, { z: /./ }); +var pickDelayed = function (x) { return pickOne(x, { j: x }, { i: x }, { chosen: x }); }; +var opt4 = pickDelayed("A"); +var opt5 = pickDelayed("B"); +var opt6 = pickDelayed(either); +var opt7 = pickOne("A", { x: 12 }, { y: "" }, { z: /./ }, { z: /./ }); +var opt8 = pickOne("B", { x: 12 }, { y: "" }, { z: /./ }, { z: /./ }); +var opt9 = pickOne(either, { x: 12 }, { y: "" }, { z: /./ }, { z: /./ }); +var cx = chooseLiteral("X", 1, "no"); +var cy = chooseLiteral("Y", 0, "yes"); +var ceither = chooseLiteral("X", 1, "yes"); +var cneither = chooseLiteral("Key", 0, "no"); +var result = dualInputs({ x: 0 }, { x: 1 }, { x: function () { return ""; } }); diff --git a/tests/baselines/reference/indexAccessCombinedInference.symbols b/tests/baselines/reference/indexAccessCombinedInference.symbols index c422efe422c8c..d108b7d1e9c72 100644 --- a/tests/baselines/reference/indexAccessCombinedInference.symbols +++ b/tests/baselines/reference/indexAccessCombinedInference.symbols @@ -1,46 +1,372 @@ === tests/cases/compiler/indexAccessCombinedInference.ts === +// Simple case interface Args { >Args : Symbol(Args, Decl(indexAccessCombinedInference.ts, 0, 0)) TA: object, ->TA : Symbol(Args.TA, Decl(indexAccessCombinedInference.ts, 0, 16)) +>TA : Symbol(Args.TA, Decl(indexAccessCombinedInference.ts, 1, 16)) TY: object ->TY : Symbol(Args.TY, Decl(indexAccessCombinedInference.ts, 1, 15)) +>TY : Symbol(Args.TY, Decl(indexAccessCombinedInference.ts, 2, 15)) } -function foo( ->foo : Symbol(foo, Decl(indexAccessCombinedInference.ts, 3, 1)) ->T : Symbol(T, Decl(indexAccessCombinedInference.ts, 5, 13)) +declare function foo( +>foo : Symbol(foo, Decl(indexAccessCombinedInference.ts, 4, 1)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 6, 21)) >Args : Symbol(Args, Decl(indexAccessCombinedInference.ts, 0, 0)) a: T["TA"], ->a : Symbol(a, Decl(indexAccessCombinedInference.ts, 5, 29)) ->T : Symbol(T, Decl(indexAccessCombinedInference.ts, 5, 13)) +>a : Symbol(a, Decl(indexAccessCombinedInference.ts, 6, 37)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 6, 21)) - b: T["TY"]): T["TA"] & T["TY"] { ->b : Symbol(b, Decl(indexAccessCombinedInference.ts, 6, 15)) ->T : Symbol(T, Decl(indexAccessCombinedInference.ts, 5, 13)) ->T : Symbol(T, Decl(indexAccessCombinedInference.ts, 5, 13)) ->T : Symbol(T, Decl(indexAccessCombinedInference.ts, 5, 13)) + b: T["TY"]): T["TA"] & T["TY"]; +>b : Symbol(b, Decl(indexAccessCombinedInference.ts, 7, 15)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 6, 21)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 6, 21)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 6, 21)) - return undefined!; ->undefined : Symbol(undefined) +const x = foo({ +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 10, 5)) +>foo : Symbol(foo, Decl(indexAccessCombinedInference.ts, 4, 1)) + + x: { +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 10, 15)) + + j: 12, +>j : Symbol(j, Decl(indexAccessCombinedInference.ts, 11, 8)) + + i: 11 +>i : Symbol(i, Decl(indexAccessCombinedInference.ts, 12, 14)) + } +}, { y: 42 }); +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 15, 4)) + +// Union result type +interface A { +>A : Symbol(A, Decl(indexAccessCombinedInference.ts, 15, 14)) + + foo: number; +>foo : Symbol(A.foo, Decl(indexAccessCombinedInference.ts, 18, 13)) } +interface B { +>B : Symbol(B, Decl(indexAccessCombinedInference.ts, 20, 1)) -const x = foo({ ->x : Symbol(x, Decl(indexAccessCombinedInference.ts, 11, 5)) ->foo : Symbol(foo, Decl(indexAccessCombinedInference.ts, 3, 1)) + bar: string; +>bar : Symbol(B.bar, Decl(indexAccessCombinedInference.ts, 21, 13)) +} +declare const something: A | B; +>something : Symbol(something, Decl(indexAccessCombinedInference.ts, 24, 13)) +>A : Symbol(A, Decl(indexAccessCombinedInference.ts, 15, 14)) +>B : Symbol(B, Decl(indexAccessCombinedInference.ts, 20, 1)) + +const y = foo(something, { bat: 42 }); +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 26, 5)) +>foo : Symbol(foo, Decl(indexAccessCombinedInference.ts, 4, 1)) +>something : Symbol(something, Decl(indexAccessCombinedInference.ts, 24, 13)) +>bat : Symbol(bat, Decl(indexAccessCombinedInference.ts, 26, 26)) + +// Union key type +interface Args2 { +>Args2 : Symbol(Args2, Decl(indexAccessCombinedInference.ts, 26, 38)) + + TA?: object, // Optional since only one of TA or TB needs to be infered in the below argument list +>TA : Symbol(Args2.TA, Decl(indexAccessCombinedInference.ts, 29, 17)) + + TB?: object, +>TB : Symbol(Args2.TB, Decl(indexAccessCombinedInference.ts, 30, 16)) + + TY: object +>TY : Symbol(Args2.TY, Decl(indexAccessCombinedInference.ts, 31, 16)) +} +declare function foo2( +>foo2 : Symbol(foo2, Decl(indexAccessCombinedInference.ts, 33, 1)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 34, 22)) +>Args2 : Symbol(Args2, Decl(indexAccessCombinedInference.ts, 26, 38)) + + a: T["TA"] | T["TB"], +>a : Symbol(a, Decl(indexAccessCombinedInference.ts, 34, 39)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 34, 22)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 34, 22)) + + b: T["TY"]): {a: T["TA"], b: T["TB"]} & T["TY"]; +>b : Symbol(b, Decl(indexAccessCombinedInference.ts, 35, 25)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 34, 22)) +>a : Symbol(a, Decl(indexAccessCombinedInference.ts, 36, 18)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 34, 22)) +>b : Symbol(b, Decl(indexAccessCombinedInference.ts, 36, 29)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 34, 22)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 34, 22)) + +declare function foo3( // Morally equivalent to foo2 +>foo3 : Symbol(foo3, Decl(indexAccessCombinedInference.ts, 36, 52)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 37, 22)) +>Args2 : Symbol(Args2, Decl(indexAccessCombinedInference.ts, 26, 38)) + + a: T["TA" | "TB"], +>a : Symbol(a, Decl(indexAccessCombinedInference.ts, 37, 39)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 37, 22)) + + b: T["TY"]): {a: T["TA"], b: T["TB"]} & T["TY"]; +>b : Symbol(b, Decl(indexAccessCombinedInference.ts, 38, 22)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 37, 22)) +>a : Symbol(a, Decl(indexAccessCombinedInference.ts, 39, 18)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 37, 22)) +>b : Symbol(b, Decl(indexAccessCombinedInference.ts, 39, 29)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 37, 22)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 37, 22)) + +let z = foo2({ +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 40, 3)) +>foo2 : Symbol(foo2, Decl(indexAccessCombinedInference.ts, 33, 1)) + + x: { +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 40, 14)) + + j: 12, +>j : Symbol(j, Decl(indexAccessCombinedInference.ts, 41, 8)) + + i: 11 +>i : Symbol(i, Decl(indexAccessCombinedInference.ts, 42, 14)) + } +}, { y: 42 }); +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 45, 4)) + +let zz = foo3({ +>zz : Symbol(zz, Decl(indexAccessCombinedInference.ts, 46, 3)) +>foo3 : Symbol(foo3, Decl(indexAccessCombinedInference.ts, 36, 52)) x: { ->x : Symbol(x, Decl(indexAccessCombinedInference.ts, 11, 15)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 46, 15)) j: 12, ->j : Symbol(j, Decl(indexAccessCombinedInference.ts, 12, 8)) +>j : Symbol(j, Decl(indexAccessCombinedInference.ts, 47, 8)) i: 11 ->i : Symbol(i, Decl(indexAccessCombinedInference.ts, 13, 14)) +>i : Symbol(i, Decl(indexAccessCombinedInference.ts, 48, 14)) } }, { y: 42 }); ->y : Symbol(y, Decl(indexAccessCombinedInference.ts, 16, 4)) +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 51, 4)) + +z = zz; +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 40, 3)) +>zz : Symbol(zz, Decl(indexAccessCombinedInference.ts, 46, 3)) + +zz = z; +>zz : Symbol(zz, Decl(indexAccessCombinedInference.ts, 46, 3)) +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 40, 3)) + +// Higher-order +interface Args3 { +>Args3 : Symbol(Args3, Decl(indexAccessCombinedInference.ts, 53, 7), Decl(indexAccessCombinedInference.ts, 72, 33)) + + Key: "A" | "B", +>Key : Symbol(Args3.Key, Decl(indexAccessCombinedInference.ts, 56, 17)) + + A: object, +>A : Symbol(Args3.A, Decl(indexAccessCombinedInference.ts, 57, 19)) + + B: object, +>B : Symbol(Args3.B, Decl(indexAccessCombinedInference.ts, 58, 14)) + + Merge: object, +>Merge : Symbol(Args3.Merge, Decl(indexAccessCombinedInference.ts, 59, 14)) +} +declare const either: "A" | "B"; +>either : Symbol(either, Decl(indexAccessCombinedInference.ts, 62, 13)) + +declare function pickOne(key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; +>pickOne : Symbol(pickOne, Decl(indexAccessCombinedInference.ts, 62, 32), Decl(indexAccessCombinedInference.ts, 82, 1)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 63, 25)) +>Args3 : Symbol(Args3, Decl(indexAccessCombinedInference.ts, 53, 7), Decl(indexAccessCombinedInference.ts, 72, 33)) +>key : Symbol(key, Decl(indexAccessCombinedInference.ts, 63, 42)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 63, 25)) +>left : Symbol(left, Decl(indexAccessCombinedInference.ts, 63, 56)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 63, 25)) +>right : Symbol(right, Decl(indexAccessCombinedInference.ts, 63, 70)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 63, 25)) +>into : Symbol(into, Decl(indexAccessCombinedInference.ts, 63, 85)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 63, 25)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 63, 25)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 63, 25)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 63, 25)) + +const opt1 = pickOne("A", {x: 12}, {y: ""}, {z: /./}); +>opt1 : Symbol(opt1, Decl(indexAccessCombinedInference.ts, 65, 5)) +>pickOne : Symbol(pickOne, Decl(indexAccessCombinedInference.ts, 62, 32), Decl(indexAccessCombinedInference.ts, 82, 1)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 65, 27)) +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 65, 36)) +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 65, 45)) + +const opt2 = pickOne("B", {x: 12}, {y: ""}, {z: /./}); +>opt2 : Symbol(opt2, Decl(indexAccessCombinedInference.ts, 66, 5)) +>pickOne : Symbol(pickOne, Decl(indexAccessCombinedInference.ts, 62, 32), Decl(indexAccessCombinedInference.ts, 82, 1)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 66, 27)) +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 66, 36)) +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 66, 45)) + +const opt3 = pickOne(either, {x: 12}, {y: ""}, {z: /./}); +>opt3 : Symbol(opt3, Decl(indexAccessCombinedInference.ts, 67, 5)) +>pickOne : Symbol(pickOne, Decl(indexAccessCombinedInference.ts, 62, 32), Decl(indexAccessCombinedInference.ts, 82, 1)) +>either : Symbol(either, Decl(indexAccessCombinedInference.ts, 62, 13)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 67, 30)) +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 67, 39)) +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 67, 48)) + +const pickDelayed = (x: TKey) => pickOne(x, {j: x}, {i: x}, {chosen: x}); +>pickDelayed : Symbol(pickDelayed, Decl(indexAccessCombinedInference.ts, 69, 5)) +>TKey : Symbol(TKey, Decl(indexAccessCombinedInference.ts, 69, 21)) +>Args3 : Symbol(Args3, Decl(indexAccessCombinedInference.ts, 53, 7), Decl(indexAccessCombinedInference.ts, 72, 33)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 69, 48)) +>TKey : Symbol(TKey, Decl(indexAccessCombinedInference.ts, 69, 21)) +>pickOne : Symbol(pickOne, Decl(indexAccessCombinedInference.ts, 62, 32), Decl(indexAccessCombinedInference.ts, 82, 1)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 69, 48)) +>j : Symbol(j, Decl(indexAccessCombinedInference.ts, 69, 72)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 69, 48)) +>i : Symbol(i, Decl(indexAccessCombinedInference.ts, 69, 80)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 69, 48)) +>chosen : Symbol(chosen, Decl(indexAccessCombinedInference.ts, 69, 88)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 69, 48)) + +const opt4 = pickDelayed("A"); +>opt4 : Symbol(opt4, Decl(indexAccessCombinedInference.ts, 70, 5)) +>pickDelayed : Symbol(pickDelayed, Decl(indexAccessCombinedInference.ts, 69, 5)) + +const opt5 = pickDelayed("B"); +>opt5 : Symbol(opt5, Decl(indexAccessCombinedInference.ts, 71, 5)) +>pickDelayed : Symbol(pickDelayed, Decl(indexAccessCombinedInference.ts, 69, 5)) + +const opt6 = pickDelayed(either); +>opt6 : Symbol(opt6, Decl(indexAccessCombinedInference.ts, 72, 5)) +>pickDelayed : Symbol(pickDelayed, Decl(indexAccessCombinedInference.ts, 69, 5)) +>either : Symbol(either, Decl(indexAccessCombinedInference.ts, 62, 13)) + +// Reopenable +interface Args3 { +>Args3 : Symbol(Args3, Decl(indexAccessCombinedInference.ts, 53, 7), Decl(indexAccessCombinedInference.ts, 72, 33)) + + /** + * One must make patched parameters optional, otherwise signatures expecting the unpatched + * interface (ie, pickOne above) will not be able to produce a type satisfying the interface + * (as there are no inference sites for the new members) and will fall back to the constraints on each member + */ + Extra?: object, +>Extra : Symbol(Args3.Extra, Decl(indexAccessCombinedInference.ts, 75, 17)) +} +declare function pickOne(key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & {into: T["Merge"], extra: T["Extra"]}; +>pickOne : Symbol(pickOne, Decl(indexAccessCombinedInference.ts, 62, 32), Decl(indexAccessCombinedInference.ts, 82, 1)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 83, 25)) +>Args3 : Symbol(Args3, Decl(indexAccessCombinedInference.ts, 53, 7), Decl(indexAccessCombinedInference.ts, 72, 33)) +>key : Symbol(key, Decl(indexAccessCombinedInference.ts, 83, 42)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 83, 25)) +>left : Symbol(left, Decl(indexAccessCombinedInference.ts, 83, 56)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 83, 25)) +>right : Symbol(right, Decl(indexAccessCombinedInference.ts, 83, 70)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 83, 25)) +>into : Symbol(into, Decl(indexAccessCombinedInference.ts, 83, 85)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 83, 25)) +>extra : Symbol(extra, Decl(indexAccessCombinedInference.ts, 83, 103)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 83, 25)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 83, 25)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 83, 25)) +>into : Symbol(into, Decl(indexAccessCombinedInference.ts, 83, 139)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 83, 25)) +>extra : Symbol(extra, Decl(indexAccessCombinedInference.ts, 83, 156)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 83, 25)) + +const opt7 = pickOne("A", {x: 12}, {y: ""}, {z: /./}, {z: /./}); +>opt7 : Symbol(opt7, Decl(indexAccessCombinedInference.ts, 84, 5)) +>pickOne : Symbol(pickOne, Decl(indexAccessCombinedInference.ts, 62, 32), Decl(indexAccessCombinedInference.ts, 82, 1)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 84, 27)) +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 84, 36)) +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 84, 45)) +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 84, 55)) + +const opt8 = pickOne("B", {x: 12}, {y: ""}, {z: /./}, {z: /./}); +>opt8 : Symbol(opt8, Decl(indexAccessCombinedInference.ts, 85, 5)) +>pickOne : Symbol(pickOne, Decl(indexAccessCombinedInference.ts, 62, 32), Decl(indexAccessCombinedInference.ts, 82, 1)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 85, 27)) +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 85, 36)) +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 85, 45)) +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 85, 55)) + +const opt9 = pickOne(either, {x: 12}, {y: ""}, {z: /./}, {z: /./}); +>opt9 : Symbol(opt9, Decl(indexAccessCombinedInference.ts, 86, 5)) +>pickOne : Symbol(pickOne, Decl(indexAccessCombinedInference.ts, 62, 32), Decl(indexAccessCombinedInference.ts, 82, 1)) +>either : Symbol(either, Decl(indexAccessCombinedInference.ts, 62, 13)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 86, 30)) +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 86, 39)) +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 86, 48)) +>z : Symbol(z, Decl(indexAccessCombinedInference.ts, 86, 58)) + +// Interactions with `this` types +interface TPicker { +>TPicker : Symbol(TPicker, Decl(indexAccessCombinedInference.ts, 86, 67)) + + Key: keyof this, +>Key : Symbol(TPicker.Key, Decl(indexAccessCombinedInference.ts, 89, 19)) + + X: number, +>X : Symbol(TPicker.X, Decl(indexAccessCombinedInference.ts, 90, 20)) + + Y: string +>Y : Symbol(TPicker.Y, Decl(indexAccessCombinedInference.ts, 91, 14)) +} +declare function chooseLiteral(choice: T["Key"], x: T["X"], y:T["Y"]): T[T["Key"]]; +>chooseLiteral : Symbol(chooseLiteral, Decl(indexAccessCombinedInference.ts, 93, 1)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 94, 31)) +>TPicker : Symbol(TPicker, Decl(indexAccessCombinedInference.ts, 86, 67)) +>choice : Symbol(choice, Decl(indexAccessCombinedInference.ts, 94, 50)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 94, 31)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 94, 67)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 94, 31)) +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 94, 78)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 94, 31)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 94, 31)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 94, 31)) + +const cx = chooseLiteral("X", 1, "no"); +>cx : Symbol(cx, Decl(indexAccessCombinedInference.ts, 95, 5)) +>chooseLiteral : Symbol(chooseLiteral, Decl(indexAccessCombinedInference.ts, 93, 1)) + +const cy = chooseLiteral("Y", 0, "yes"); +>cy : Symbol(cy, Decl(indexAccessCombinedInference.ts, 96, 5)) +>chooseLiteral : Symbol(chooseLiteral, Decl(indexAccessCombinedInference.ts, 93, 1)) + +const ceither = chooseLiteral("X" as "X" | "Y", 1, "yes"); +>ceither : Symbol(ceither, Decl(indexAccessCombinedInference.ts, 97, 5)) +>chooseLiteral : Symbol(chooseLiteral, Decl(indexAccessCombinedInference.ts, 93, 1)) + +const cneither = chooseLiteral("Key", 0, "no"); +>cneither : Symbol(cneither, Decl(indexAccessCombinedInference.ts, 98, 5)) +>chooseLiteral : Symbol(chooseLiteral, Decl(indexAccessCombinedInference.ts, 93, 1)) + +// Multiple inference sites +interface Args4 { +>Args4 : Symbol(Args4, Decl(indexAccessCombinedInference.ts, 98, 47)) + + 0: object, + 1: Record, +>Record : Symbol(Record, Decl(lib.d.ts, --, --)) +>Function : Symbol(Function, Decl(lib.d.ts, --, --), Decl(lib.d.ts, --, --)) +} +declare function dualInputs(x: T["0"], y: T["0"], toDelay: T["1"]): T["0"] & {transformers: T["1"]}; +>dualInputs : Symbol(dualInputs, Decl(indexAccessCombinedInference.ts, 104, 1)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 105, 28)) +>Args4 : Symbol(Args4, Decl(indexAccessCombinedInference.ts, 98, 47)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 105, 45)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 105, 28)) +>y : Symbol(y, Decl(indexAccessCombinedInference.ts, 105, 55)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 105, 28)) +>toDelay : Symbol(toDelay, Decl(indexAccessCombinedInference.ts, 105, 66)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 105, 28)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 105, 28)) +>transformers : Symbol(transformers, Decl(indexAccessCombinedInference.ts, 105, 95)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 105, 28)) + +const result = dualInputs({x: 0}, {x: 1}, {x: () => ""}); +>result : Symbol(result, Decl(indexAccessCombinedInference.ts, 107, 5)) +>dualInputs : Symbol(dualInputs, Decl(indexAccessCombinedInference.ts, 104, 1)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 107, 27)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 107, 35)) +>x : Symbol(x, Decl(indexAccessCombinedInference.ts, 107, 43)) diff --git a/tests/baselines/reference/indexAccessCombinedInference.types b/tests/baselines/reference/indexAccessCombinedInference.types index f32c28de6266f..03badaade7613 100644 --- a/tests/baselines/reference/indexAccessCombinedInference.types +++ b/tests/baselines/reference/indexAccessCombinedInference.types @@ -1,4 +1,5 @@ === tests/cases/compiler/indexAccessCombinedInference.ts === +// Simple case interface Args { >Args : Args @@ -9,7 +10,7 @@ interface Args { >TY : object } -function foo( +declare function foo( >foo : (a: T["TA"], b: T["TY"]) => T["TA"] & T["TY"] >T : T >Args : Args @@ -18,17 +19,12 @@ function foo( >a : T["TA"] >T : T - b: T["TY"]): T["TA"] & T["TY"] { + b: T["TY"]): T["TA"] & T["TY"]; >b : T["TY"] >T : T >T : T >T : T - return undefined!; ->undefined! : undefined ->undefined : undefined -} - const x = foo({ >x : { x: { j: number; i: number; }; } & { y: number; } >foo({ x: { j: 12, i: 11 }}, { y: 42 }) : { x: { j: number; i: number; }; } & { y: number; } @@ -52,3 +48,438 @@ const x = foo({ >y : number >42 : 42 +// Union result type +interface A { +>A : A + + foo: number; +>foo : number +} +interface B { +>B : B + + bar: string; +>bar : string +} +declare const something: A | B; +>something : A | B +>A : A +>B : B + +const y = foo(something, { bat: 42 }); +>y : (A & { bat: number; }) | (B & { bat: number; }) +>foo(something, { bat: 42 }) : (A & { bat: number; }) | (B & { bat: number; }) +>foo : (a: T["TA"], b: T["TY"]) => T["TA"] & T["TY"] +>something : A | B +>{ bat: 42 } : { bat: number; } +>bat : number +>42 : 42 + +// Union key type +interface Args2 { +>Args2 : Args2 + + TA?: object, // Optional since only one of TA or TB needs to be infered in the below argument list +>TA : object + + TB?: object, +>TB : object + + TY: object +>TY : object +} +declare function foo2( +>foo2 : (a: T["TA"] | T["TB"], b: T["TY"]) => { a: T["TA"]; b: T["TB"]; } & T["TY"] +>T : T +>Args2 : Args2 + + a: T["TA"] | T["TB"], +>a : T["TA"] | T["TB"] +>T : T +>T : T + + b: T["TY"]): {a: T["TA"], b: T["TB"]} & T["TY"]; +>b : T["TY"] +>T : T +>a : T["TA"] +>T : T +>b : T["TB"] +>T : T +>T : T + +declare function foo3( // Morally equivalent to foo2 +>foo3 : (a: T["TA" | "TB"], b: T["TY"]) => { a: T["TA"]; b: T["TB"]; } & T["TY"] +>T : T +>Args2 : Args2 + + a: T["TA" | "TB"], +>a : T["TA" | "TB"] +>T : T + + b: T["TY"]): {a: T["TA"], b: T["TB"]} & T["TY"]; +>b : T["TY"] +>T : T +>a : T["TA"] +>T : T +>b : T["TB"] +>T : T +>T : T + +let z = foo2({ +>z : { a: { x: { j: number; i: number; }; }; b: { x: { j: number; i: number; }; }; } & { y: number; } +>foo2({ x: { j: 12, i: 11 }}, { y: 42 }) : { a: { x: { j: number; i: number; }; }; b: { x: { j: number; i: number; }; }; } & { y: number; } +>foo2 : (a: T["TA"] | T["TB"], b: T["TY"]) => { a: T["TA"]; b: T["TB"]; } & T["TY"] +>{ x: { j: 12, i: 11 }} : { x: { j: number; i: number; }; } + + x: { +>x : { j: number; i: number; } +>{ j: 12, i: 11 } : { j: number; i: number; } + + j: 12, +>j : number +>12 : 12 + + i: 11 +>i : number +>11 : 11 + } +}, { y: 42 }); +>{ y: 42 } : { y: number; } +>y : number +>42 : 42 + +let zz = foo3({ +>zz : { a: any; b: any; } & { y: number; } +>foo3({ x: { j: 12, i: 11 }}, { y: 42 }) : { a: any; b: any; } & { y: number; } +>foo3 : (a: T["TA" | "TB"], b: T["TY"]) => { a: T["TA"]; b: T["TB"]; } & T["TY"] +>{ x: { j: 12, i: 11 }} : { x: { j: number; i: number; }; } + + x: { +>x : { j: number; i: number; } +>{ j: 12, i: 11 } : { j: number; i: number; } + + j: 12, +>j : number +>12 : 12 + + i: 11 +>i : number +>11 : 11 + } +}, { y: 42 }); +>{ y: 42 } : { y: number; } +>y : number +>42 : 42 + +z = zz; +>z = zz : { a: any; b: any; } & { y: number; } +>z : { a: { x: { j: number; i: number; }; }; b: { x: { j: number; i: number; }; }; } & { y: number; } +>zz : { a: any; b: any; } & { y: number; } + +zz = z; +>zz = z : { a: { x: { j: number; i: number; }; }; b: { x: { j: number; i: number; }; }; } & { y: number; } +>zz : { a: any; b: any; } & { y: number; } +>z : { a: { x: { j: number; i: number; }; }; b: { x: { j: number; i: number; }; }; } & { y: number; } + +// Higher-order +interface Args3 { +>Args3 : Args3 + + Key: "A" | "B", +>Key : "A" | "B" + + A: object, +>A : object + + B: object, +>B : object + + Merge: object, +>Merge : object +} +declare const either: "A" | "B"; +>either : "A" | "B" + +declare function pickOne(key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; +>pickOne : { (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & { into: T["Merge"]; extra: T["Extra"]; }; } +>T : T +>Args3 : Args3 +>key : T["Key"] +>T : T +>left : T["A"] +>T : T +>right : T["B"] +>T : T +>into : T["Merge"] +>T : T +>T : T +>T : T +>T : T + +const opt1 = pickOne("A", {x: 12}, {y: ""}, {z: /./}); +>opt1 : { x: number; } & { z: RegExp; } +>pickOne("A", {x: 12}, {y: ""}, {z: /./}) : { x: number; } & { z: RegExp; } +>pickOne : { (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & { into: T["Merge"]; extra: T["Extra"]; }; } +>"A" : "A" +>{x: 12} : { x: number; } +>x : number +>12 : 12 +>{y: ""} : { y: string; } +>y : string +>"" : "" +>{z: /./} : { z: RegExp; } +>z : RegExp +>/./ : RegExp + +const opt2 = pickOne("B", {x: 12}, {y: ""}, {z: /./}); +>opt2 : { y: string; } & { z: RegExp; } +>pickOne("B", {x: 12}, {y: ""}, {z: /./}) : { y: string; } & { z: RegExp; } +>pickOne : { (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & { into: T["Merge"]; extra: T["Extra"]; }; } +>"B" : "B" +>{x: 12} : { x: number; } +>x : number +>12 : 12 +>{y: ""} : { y: string; } +>y : string +>"" : "" +>{z: /./} : { z: RegExp; } +>z : RegExp +>/./ : RegExp + +const opt3 = pickOne(either, {x: 12}, {y: ""}, {z: /./}); +>opt3 : ({ x: number; } & { z: RegExp; }) | ({ y: string; } & { z: RegExp; }) +>pickOne(either, {x: 12}, {y: ""}, {z: /./}) : ({ x: number; } & { z: RegExp; }) | ({ y: string; } & { z: RegExp; }) +>pickOne : { (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & { into: T["Merge"]; extra: T["Extra"]; }; } +>either : "A" | "B" +>{x: 12} : { x: number; } +>x : number +>12 : 12 +>{y: ""} : { y: string; } +>y : string +>"" : "" +>{z: /./} : { z: RegExp; } +>z : RegExp +>/./ : RegExp + +const pickDelayed = (x: TKey) => pickOne(x, {j: x}, {i: x}, {chosen: x}); +>pickDelayed : (x: TKey) => ({ Key: TKey; } & { A: { j: TKey; }; } & { B: { i: TKey; }; } & { Merge: { chosen: TKey; }; })[TKey] & { chosen: TKey; } +>(x: TKey) => pickOne(x, {j: x}, {i: x}, {chosen: x}) : (x: TKey) => ({ Key: TKey; } & { A: { j: TKey; }; } & { B: { i: TKey; }; } & { Merge: { chosen: TKey; }; })[TKey] & { chosen: TKey; } +>TKey : TKey +>Args3 : Args3 +>x : TKey +>TKey : TKey +>pickOne(x, {j: x}, {i: x}, {chosen: x}) : ({ Key: TKey; } & { A: { j: TKey; }; } & { B: { i: TKey; }; } & { Merge: { chosen: TKey; }; })[TKey] & { chosen: TKey; } +>pickOne : { (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & { into: T["Merge"]; extra: T["Extra"]; }; } +>x : TKey +>{j: x} : { j: TKey; } +>j : TKey +>x : TKey +>{i: x} : { i: TKey; } +>i : TKey +>x : TKey +>{chosen: x} : { chosen: TKey; } +>chosen : TKey +>x : TKey + +const opt4 = pickDelayed("A"); +>opt4 : { j: "A"; } & { chosen: "A"; } +>pickDelayed("A") : { j: "A"; } & { chosen: "A"; } +>pickDelayed : (x: TKey) => ({ Key: TKey; } & { A: { j: TKey; }; } & { B: { i: TKey; }; } & { Merge: { chosen: TKey; }; })[TKey] & { chosen: TKey; } +>"A" : "A" + +const opt5 = pickDelayed("B"); +>opt5 : { i: "B"; } & { chosen: "B"; } +>pickDelayed("B") : { i: "B"; } & { chosen: "B"; } +>pickDelayed : (x: TKey) => ({ Key: TKey; } & { A: { j: TKey; }; } & { B: { i: TKey; }; } & { Merge: { chosen: TKey; }; })[TKey] & { chosen: TKey; } +>"B" : "B" + +const opt6 = pickDelayed(either); +>opt6 : ({ j: "A" | "B"; } & { chosen: "A" | "B"; }) | ({ i: "A" | "B"; } & { chosen: "A" | "B"; }) +>pickDelayed(either) : ({ j: "A" | "B"; } & { chosen: "A" | "B"; }) | ({ i: "A" | "B"; } & { chosen: "A" | "B"; }) +>pickDelayed : (x: TKey) => ({ Key: TKey; } & { A: { j: TKey; }; } & { B: { i: TKey; }; } & { Merge: { chosen: TKey; }; })[TKey] & { chosen: TKey; } +>either : "A" | "B" + +// Reopenable +interface Args3 { +>Args3 : Args3 + + /** + * One must make patched parameters optional, otherwise signatures expecting the unpatched + * interface (ie, pickOne above) will not be able to produce a type satisfying the interface + * (as there are no inference sites for the new members) and will fall back to the constraints on each member + */ + Extra?: object, +>Extra : object +} +declare function pickOne(key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & {into: T["Merge"], extra: T["Extra"]}; +>pickOne : { (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & { into: T["Merge"]; extra: T["Extra"]; }; } +>T : T +>Args3 : Args3 +>key : T["Key"] +>T : T +>left : T["A"] +>T : T +>right : T["B"] +>T : T +>into : T["Merge"] +>T : T +>extra : T["Extra"] +>T : T +>T : T +>T : T +>into : T["Merge"] +>T : T +>extra : T["Extra"] +>T : T + +const opt7 = pickOne("A", {x: 12}, {y: ""}, {z: /./}, {z: /./}); +>opt7 : { x: number; } & { into: { z: RegExp; }; extra: { z: RegExp; }; } +>pickOne("A", {x: 12}, {y: ""}, {z: /./}, {z: /./}) : { x: number; } & { into: { z: RegExp; }; extra: { z: RegExp; }; } +>pickOne : { (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & { into: T["Merge"]; extra: T["Extra"]; }; } +>"A" : "A" +>{x: 12} : { x: number; } +>x : number +>12 : 12 +>{y: ""} : { y: string; } +>y : string +>"" : "" +>{z: /./} : { z: RegExp; } +>z : RegExp +>/./ : RegExp +>{z: /./} : { z: RegExp; } +>z : RegExp +>/./ : RegExp + +const opt8 = pickOne("B", {x: 12}, {y: ""}, {z: /./}, {z: /./}); +>opt8 : { y: string; } & { into: { z: RegExp; }; extra: { z: RegExp; }; } +>pickOne("B", {x: 12}, {y: ""}, {z: /./}, {z: /./}) : { y: string; } & { into: { z: RegExp; }; extra: { z: RegExp; }; } +>pickOne : { (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & { into: T["Merge"]; extra: T["Extra"]; }; } +>"B" : "B" +>{x: 12} : { x: number; } +>x : number +>12 : 12 +>{y: ""} : { y: string; } +>y : string +>"" : "" +>{z: /./} : { z: RegExp; } +>z : RegExp +>/./ : RegExp +>{z: /./} : { z: RegExp; } +>z : RegExp +>/./ : RegExp + +const opt9 = pickOne(either, {x: 12}, {y: ""}, {z: /./}, {z: /./}); +>opt9 : ({ x: number; } & { into: { z: RegExp; }; extra: { z: RegExp; }; }) | ({ y: string; } & { into: { z: RegExp; }; extra: { z: RegExp; }; }) +>pickOne(either, {x: 12}, {y: ""}, {z: /./}, {z: /./}) : ({ x: number; } & { into: { z: RegExp; }; extra: { z: RegExp; }; }) | ({ y: string; } & { into: { z: RegExp; }; extra: { z: RegExp; }; }) +>pickOne : { (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; (key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & { into: T["Merge"]; extra: T["Extra"]; }; } +>either : "A" | "B" +>{x: 12} : { x: number; } +>x : number +>12 : 12 +>{y: ""} : { y: string; } +>y : string +>"" : "" +>{z: /./} : { z: RegExp; } +>z : RegExp +>/./ : RegExp +>{z: /./} : { z: RegExp; } +>z : RegExp +>/./ : RegExp + +// Interactions with `this` types +interface TPicker { +>TPicker : TPicker + + Key: keyof this, +>Key : keyof this + + X: number, +>X : number + + Y: string +>Y : string +} +declare function chooseLiteral(choice: T["Key"], x: T["X"], y:T["Y"]): T[T["Key"]]; +>chooseLiteral : (choice: T["Key"], x: T["X"], y: T["Y"]) => T[T["Key"]] +>T : T +>TPicker : TPicker +>choice : T["Key"] +>T : T +>x : T["X"] +>T : T +>y : T["Y"] +>T : T +>T : T +>T : T + +const cx = chooseLiteral("X", 1, "no"); +>cx : 1 +>chooseLiteral("X", 1, "no") : 1 +>chooseLiteral : (choice: T["Key"], x: T["X"], y: T["Y"]) => T[T["Key"]] +>"X" : "X" +>1 : 1 +>"no" : "no" + +const cy = chooseLiteral("Y", 0, "yes"); +>cy : "yes" +>chooseLiteral("Y", 0, "yes") : "yes" +>chooseLiteral : (choice: T["Key"], x: T["X"], y: T["Y"]) => T[T["Key"]] +>"Y" : "Y" +>0 : 0 +>"yes" : "yes" + +const ceither = chooseLiteral("X" as "X" | "Y", 1, "yes"); +>ceither : 1 | "yes" +>chooseLiteral("X" as "X" | "Y", 1, "yes") : 1 | "yes" +>chooseLiteral : (choice: T["Key"], x: T["X"], y: T["Y"]) => T[T["Key"]] +>"X" as "X" | "Y" : "X" | "Y" +>"X" : "X" +>1 : 1 +>"yes" : "yes" + +const cneither = chooseLiteral("Key", 0, "no"); +>cneither : "Key" +>chooseLiteral("Key", 0, "no") : "Key" +>chooseLiteral : (choice: T["Key"], x: T["X"], y: T["Y"]) => T[T["Key"]] +>"Key" : "Key" +>0 : 0 +>"no" : "no" + +// Multiple inference sites +interface Args4 { +>Args4 : Args4 + + 0: object, + 1: Record, +>Record : Record +>Function : Function +} +declare function dualInputs(x: T["0"], y: T["0"], toDelay: T["1"]): T["0"] & {transformers: T["1"]}; +>dualInputs : (x: T["0"], y: T["0"], toDelay: T["1"]) => T["0"] & { transformers: T["1"]; } +>T : T +>Args4 : Args4 +>x : T["0"] +>T : T +>y : T["0"] +>T : T +>toDelay : T["1"] +>T : T +>T : T +>transformers : T["1"] +>T : T + +const result = dualInputs({x: 0}, {x: 1}, {x: () => ""}); +>result : { x: number; } & { x: number; } & { transformers: { x: () => ""; }; } +>dualInputs({x: 0}, {x: 1}, {x: () => ""}) : { x: number; } & { x: number; } & { transformers: { x: () => ""; }; } +>dualInputs : (x: T["0"], y: T["0"], toDelay: T["1"]) => T["0"] & { transformers: T["1"]; } +>{x: 0} : { x: number; } +>x : number +>0 : 0 +>{x: 1} : { x: number; } +>x : number +>1 : 1 +>{x: () => ""} : { x: () => ""; } +>x : () => "" +>() => "" : () => "" +>"" : "" + diff --git a/tests/cases/compiler/indexAccessCombinedInference.ts b/tests/cases/compiler/indexAccessCombinedInference.ts index c1d7f03ac22ee..59af2dd60fa1f 100644 --- a/tests/cases/compiler/indexAccessCombinedInference.ts +++ b/tests/cases/compiler/indexAccessCombinedInference.ts @@ -1,17 +1,108 @@ +// Simple case interface Args { TA: object, TY: object } -function foo( +declare function foo( a: T["TA"], - b: T["TY"]): T["TA"] & T["TY"] { - return undefined!; -} + b: T["TY"]): T["TA"] & T["TY"]; const x = foo({ x: { j: 12, i: 11 } -}, { y: 42 }); \ No newline at end of file +}, { y: 42 }); + +// Union result type +interface A { + foo: number; +} +interface B { + bar: string; +} +declare const something: A | B; + +const y = foo(something, { bat: 42 }); + +// Union key type +interface Args2 { + TA?: object, // Optional since only one of TA or TB needs to be infered in the below argument list + TB?: object, + TY: object +} +declare function foo2( + a: T["TA"] | T["TB"], + b: T["TY"]): {a: T["TA"], b: T["TB"]} & T["TY"]; +declare function foo3( // Morally equivalent to foo2 + a: T["TA" | "TB"], + b: T["TY"]): {a: T["TA"], b: T["TB"]} & T["TY"]; +let z = foo2({ + x: { + j: 12, + i: 11 + } +}, { y: 42 }); +let zz = foo3({ + x: { + j: 12, + i: 11 + } +}, { y: 42 }); +z = zz; +zz = z; + +// Higher-order +interface Args3 { + Key: "A" | "B", + A: object, + B: object, + Merge: object, +} +declare const either: "A" | "B"; +declare function pickOne(key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"]): T[T["Key"]] & T["Merge"]; + +const opt1 = pickOne("A", {x: 12}, {y: ""}, {z: /./}); +const opt2 = pickOne("B", {x: 12}, {y: ""}, {z: /./}); +const opt3 = pickOne(either, {x: 12}, {y: ""}, {z: /./}); + +const pickDelayed = (x: TKey) => pickOne(x, {j: x}, {i: x}, {chosen: x}); +const opt4 = pickDelayed("A"); +const opt5 = pickDelayed("B"); +const opt6 = pickDelayed(either); + +// Reopenable +interface Args3 { + /** + * One must make patched parameters optional, otherwise signatures expecting the unpatched + * interface (ie, pickOne above) will not be able to produce a type satisfying the interface + * (as there are no inference sites for the new members) and will fall back to the constraints on each member + */ + Extra?: object, +} +declare function pickOne(key: T["Key"], left: T["A"], right: T["B"], into: T["Merge"], extra: T["Extra"]): T[T["Key"]] & {into: T["Merge"], extra: T["Extra"]}; +const opt7 = pickOne("A", {x: 12}, {y: ""}, {z: /./}, {z: /./}); +const opt8 = pickOne("B", {x: 12}, {y: ""}, {z: /./}, {z: /./}); +const opt9 = pickOne(either, {x: 12}, {y: ""}, {z: /./}, {z: /./}); + +// Interactions with `this` types +interface TPicker { + Key: keyof this, + X: number, + Y: string +} +declare function chooseLiteral(choice: T["Key"], x: T["X"], y:T["Y"]): T[T["Key"]]; +const cx = chooseLiteral("X", 1, "no"); +const cy = chooseLiteral("Y", 0, "yes"); +const ceither = chooseLiteral("X" as "X" | "Y", 1, "yes"); +const cneither = chooseLiteral("Key", 0, "no"); + +// Multiple inference sites +interface Args4 { + 0: object, + 1: Record, +} +declare function dualInputs(x: T["0"], y: T["0"], toDelay: T["1"]): T["0"] & {transformers: T["1"]}; + +const result = dualInputs({x: 0}, {x: 1}, {x: () => ""}); From 93afc105479da2b36798b4f6a0a8f6cb87778512 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Fri, 1 Dec 2017 16:44:45 -0500 Subject: [PATCH 4/6] Discriminate partial inferences if not complete enough to satisfy constraint --- src/compiler/checker.ts | 33 +++++++++++- .../reference/typeInferenceOnIndexUnion.js | 13 +++++ .../typeInferenceOnIndexUnion.symbols | 45 ++++++++++++++++ .../reference/typeInferenceOnIndexUnion.types | 52 +++++++++++++++++++ .../compiler/typeInferenceOnIndexUnion.ts | 7 +++ 5 files changed, 149 insertions(+), 1 deletion(-) create mode 100644 tests/baselines/reference/typeInferenceOnIndexUnion.js create mode 100644 tests/baselines/reference/typeInferenceOnIndexUnion.symbols create mode 100644 tests/baselines/reference/typeInferenceOnIndexUnion.types create mode 100644 tests/cases/compiler/typeInferenceOnIndexUnion.ts diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d5e27dbeba82d..d9d96012fbc5a 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11686,7 +11686,38 @@ namespace ts { if (!inferredType) { if (inference.indexes) { // Build a candidate from all indexes - (inference.candidates || (inference.candidates = [])).push(getIntersectionType(inference.indexes)); + let aggregateInference = getIntersectionType(inference.indexes); + const constraint = getConstraintOfTypeParameter(context.signature.typeParameters[index]); + if (constraint) { + const instantiatedConstraint = instantiateType(constraint, context); + if (!context.compareTypes(aggregateInference, getTypeWithThisArgument(instantiatedConstraint, aggregateInference))) { + if (instantiatedConstraint.flags & TypeFlags.Union) { + const discriminantProps = findDiscriminantProperties(getPropertiesOfType(aggregateInference), instantiatedConstraint); + if (discriminantProps) { + let match: Type; + findDiscriminant: for (const p of discriminantProps) { + const candidatePropType = getTypeOfPropertyOfType(aggregateInference, p.escapedName); + for (const type of (instantiatedConstraint as UnionType).types) { + const propType = getTypeOfPropertyOfType(type, p.escapedName); + if (propType && checkTypeAssignableTo(candidatePropType, propType, /*errorNode*/ undefined)) { + if (match && match !== type) { + match = undefined; + break findDiscriminant; + } + else { + match = type; + } + } + } + } + if (match) { + aggregateInference = getSpreadType(match, aggregateInference, /*symbol*/ undefined, /*propegatedFlags*/ 0); + } + } + } + } + } + (inference.candidates || (inference.candidates = [])).push(aggregateInference); } if (inference.candidates) { // Extract all object literal types and replace them with a single widened and normalized type. diff --git a/tests/baselines/reference/typeInferenceOnIndexUnion.js b/tests/baselines/reference/typeInferenceOnIndexUnion.js new file mode 100644 index 0000000000000..fc32ef0653e85 --- /dev/null +++ b/tests/baselines/reference/typeInferenceOnIndexUnion.js @@ -0,0 +1,13 @@ +//// [typeInferenceOnIndexUnion.ts] +type Options = { k: "a", a: number } | { k: "b", b: string }; +declare function f(p: T["k"]): T; +const x = f("a"); // expect it to be `{ k: "a", a: number }` + +type Options2 = { k: "a", a: number, c: {} } | { k: "b", b: string, c: {} }; +declare function f2(p: T["k"], c: T["c"]): T; +const x2 = f2("a", { x: 1, y: 2 }); // expect it to be `{ k: "a", a: number, c: {x: number, y: number} }` + + +//// [typeInferenceOnIndexUnion.js] +var x = f("a"); // expect it to be `{ k: "a", a: number }` +var x2 = f2("a", { x: 1, y: 2 }); // expect it to be `{ k: "a", a: number, c: {x: number, y: number} }` diff --git a/tests/baselines/reference/typeInferenceOnIndexUnion.symbols b/tests/baselines/reference/typeInferenceOnIndexUnion.symbols new file mode 100644 index 0000000000000..b3a0d0dc98f91 --- /dev/null +++ b/tests/baselines/reference/typeInferenceOnIndexUnion.symbols @@ -0,0 +1,45 @@ +=== tests/cases/compiler/typeInferenceOnIndexUnion.ts === +type Options = { k: "a", a: number } | { k: "b", b: string }; +>Options : Symbol(Options, Decl(typeInferenceOnIndexUnion.ts, 0, 0)) +>k : Symbol(k, Decl(typeInferenceOnIndexUnion.ts, 0, 16)) +>a : Symbol(a, Decl(typeInferenceOnIndexUnion.ts, 0, 24)) +>k : Symbol(k, Decl(typeInferenceOnIndexUnion.ts, 0, 40)) +>b : Symbol(b, Decl(typeInferenceOnIndexUnion.ts, 0, 48)) + +declare function f(p: T["k"]): T; +>f : Symbol(f, Decl(typeInferenceOnIndexUnion.ts, 0, 61)) +>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 1, 19)) +>Options : Symbol(Options, Decl(typeInferenceOnIndexUnion.ts, 0, 0)) +>p : Symbol(p, Decl(typeInferenceOnIndexUnion.ts, 1, 38)) +>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 1, 19)) +>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 1, 19)) + +const x = f("a"); // expect it to be `{ k: "a", a: number }` +>x : Symbol(x, Decl(typeInferenceOnIndexUnion.ts, 2, 5)) +>f : Symbol(f, Decl(typeInferenceOnIndexUnion.ts, 0, 61)) + +type Options2 = { k: "a", a: number, c: {} } | { k: "b", b: string, c: {} }; +>Options2 : Symbol(Options2, Decl(typeInferenceOnIndexUnion.ts, 2, 17)) +>k : Symbol(k, Decl(typeInferenceOnIndexUnion.ts, 4, 17)) +>a : Symbol(a, Decl(typeInferenceOnIndexUnion.ts, 4, 25)) +>c : Symbol(c, Decl(typeInferenceOnIndexUnion.ts, 4, 36)) +>k : Symbol(k, Decl(typeInferenceOnIndexUnion.ts, 4, 48)) +>b : Symbol(b, Decl(typeInferenceOnIndexUnion.ts, 4, 56)) +>c : Symbol(c, Decl(typeInferenceOnIndexUnion.ts, 4, 67)) + +declare function f2(p: T["k"], c: T["c"]): T; +>f2 : Symbol(f2, Decl(typeInferenceOnIndexUnion.ts, 4, 76)) +>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 5, 20)) +>Options2 : Symbol(Options2, Decl(typeInferenceOnIndexUnion.ts, 2, 17)) +>p : Symbol(p, Decl(typeInferenceOnIndexUnion.ts, 5, 40)) +>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 5, 20)) +>c : Symbol(c, Decl(typeInferenceOnIndexUnion.ts, 5, 50)) +>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 5, 20)) +>T : Symbol(T, Decl(typeInferenceOnIndexUnion.ts, 5, 20)) + +const x2 = f2("a", { x: 1, y: 2 }); // expect it to be `{ k: "a", a: number, c: {x: number, y: number} }` +>x2 : Symbol(x2, Decl(typeInferenceOnIndexUnion.ts, 6, 5)) +>f2 : Symbol(f2, Decl(typeInferenceOnIndexUnion.ts, 4, 76)) +>x : Symbol(x, Decl(typeInferenceOnIndexUnion.ts, 6, 20)) +>y : Symbol(y, Decl(typeInferenceOnIndexUnion.ts, 6, 26)) + diff --git a/tests/baselines/reference/typeInferenceOnIndexUnion.types b/tests/baselines/reference/typeInferenceOnIndexUnion.types new file mode 100644 index 0000000000000..57e171d86ba74 --- /dev/null +++ b/tests/baselines/reference/typeInferenceOnIndexUnion.types @@ -0,0 +1,52 @@ +=== tests/cases/compiler/typeInferenceOnIndexUnion.ts === +type Options = { k: "a", a: number } | { k: "b", b: string }; +>Options : Options +>k : "a" +>a : number +>k : "b" +>b : string + +declare function f(p: T["k"]): T; +>f : (p: T["k"]) => T +>T : T +>Options : Options +>p : T["k"] +>T : T +>T : T + +const x = f("a"); // expect it to be `{ k: "a", a: number }` +>x : { k: "a"; a: number; } +>f("a") : { k: "a"; a: number; } +>f : (p: T["k"]) => T +>"a" : "a" + +type Options2 = { k: "a", a: number, c: {} } | { k: "b", b: string, c: {} }; +>Options2 : Options2 +>k : "a" +>a : number +>c : {} +>k : "b" +>b : string +>c : {} + +declare function f2(p: T["k"], c: T["c"]): T; +>f2 : (p: T["k"], c: T["c"]) => T +>T : T +>Options2 : Options2 +>p : T["k"] +>T : T +>c : T["c"] +>T : T +>T : T + +const x2 = f2("a", { x: 1, y: 2 }); // expect it to be `{ k: "a", a: number, c: {x: number, y: number} }` +>x2 : { k: "a"; c: { x: number; y: number; }; a: number; } +>f2("a", { x: 1, y: 2 }) : { k: "a"; c: { x: number; y: number; }; a: number; } +>f2 : (p: T["k"], c: T["c"]) => T +>"a" : "a" +>{ x: 1, y: 2 } : { x: number; y: number; } +>x : number +>1 : 1 +>y : number +>2 : 2 + diff --git a/tests/cases/compiler/typeInferenceOnIndexUnion.ts b/tests/cases/compiler/typeInferenceOnIndexUnion.ts new file mode 100644 index 0000000000000..35c5fb16a9fa8 --- /dev/null +++ b/tests/cases/compiler/typeInferenceOnIndexUnion.ts @@ -0,0 +1,7 @@ +type Options = { k: "a", a: number } | { k: "b", b: string }; +declare function f(p: T["k"]): T; +const x = f("a"); // expect it to be `{ k: "a", a: number }` + +type Options2 = { k: "a", a: number, c: {} } | { k: "b", b: string, c: {} }; +declare function f2(p: T["k"], c: T["c"]): T; +const x2 = f2("a", { x: 1, y: 2 }); // expect it to be `{ k: "a", a: number, c: {x: number, y: number} }` From 49e19617b41ee17a536ed50870c4e315c2c4f548 Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 21 Dec 2017 14:58:29 -0800 Subject: [PATCH 5/6] Move case to prefered location --- src/compiler/checker.ts | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d9d96012fbc5a..95b943ebde094 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11401,23 +11401,6 @@ namespace ts { } return; } - else if (target.flags & TypeFlags.IndexedAccess) { - const targetConstraint = (target).objectType; - const inference = getInferenceInfoForType(targetConstraint); - if (inference) { - if (!inference.isFixed) { - // Instantiates instance of `type PartialInference = ({[K in Keys]: {[K1 in K]: T}})[Keys];` - // Where `T` is `source` and `Keys` is `target.indexType` - const inferenceTypeSymbol = getGlobalSymbol("PartialInference" as __String, SymbolFlags.Type, Diagnostics.Cannot_find_global_type_0); - const inferenceType = getDeclaredTypeOfSymbol(inferenceTypeSymbol); - if (inferenceType !== unknownType) { - const mapper = createTypeMapper(getSymbolLinks(inferenceTypeSymbol).typeParameters, [source, (target as IndexedAccessType).indexType]); - (inference.indexes || (inference.indexes = [])).push(instantiateType(inferenceType, mapper)); - } - } - return; - } - } } if (getObjectFlags(source) & ObjectFlags.Reference && getObjectFlags(target) & ObjectFlags.Reference && (source).target === (target).target) { // If source and target are references to the same generic type, infer from type arguments @@ -11449,6 +11432,23 @@ namespace ts { inferFromTypes((source).objectType, (target).objectType); inferFromTypes((source).indexType, (target).indexType); } + else if (target.flags & TypeFlags.IndexedAccess) { + const targetConstraint = (target).objectType; + const inference = getInferenceInfoForType(targetConstraint); + if (inference) { + if (!inference.isFixed) { + // Instantiates instance of `type PartialInference = ({[K in Keys]: {[K1 in K]: T}})[Keys];` + // Where `T` is `source` and `Keys` is `target.indexType` + const inferenceTypeSymbol = getGlobalSymbol("PartialInference" as __String, SymbolFlags.Type, Diagnostics.Cannot_find_global_type_0); + const inferenceType = getDeclaredTypeOfSymbol(inferenceTypeSymbol); + if (inferenceType !== unknownType) { + const mapper = createTypeMapper(getSymbolLinks(inferenceTypeSymbol).typeParameters, [source, (target as IndexedAccessType).indexType]); + (inference.indexes || (inference.indexes = [])).push(instantiateType(inferenceType, mapper)); + } + } + return; + } + } else if (target.flags & TypeFlags.UnionOrIntersection) { const targetTypes = (target).types; let typeVariableCount = 0; From 96772e5d88626ab7063acc0bdeedd1e0505abffa Mon Sep 17 00:00:00 2001 From: Wesley Wigham Date: Thu, 21 Dec 2017 15:13:11 -0800 Subject: [PATCH 6/6] Small refactor to reduce how many comparisons are performed --- src/compiler/checker.ts | 38 ++++++++++++++++++-------------------- 1 file changed, 18 insertions(+), 20 deletions(-) diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 95b943ebde094..f6d05ad27b5e4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11690,29 +11690,27 @@ namespace ts { const constraint = getConstraintOfTypeParameter(context.signature.typeParameters[index]); if (constraint) { const instantiatedConstraint = instantiateType(constraint, context); - if (!context.compareTypes(aggregateInference, getTypeWithThisArgument(instantiatedConstraint, aggregateInference))) { - if (instantiatedConstraint.flags & TypeFlags.Union) { - const discriminantProps = findDiscriminantProperties(getPropertiesOfType(aggregateInference), instantiatedConstraint); - if (discriminantProps) { - let match: Type; - findDiscriminant: for (const p of discriminantProps) { - const candidatePropType = getTypeOfPropertyOfType(aggregateInference, p.escapedName); - for (const type of (instantiatedConstraint as UnionType).types) { - const propType = getTypeOfPropertyOfType(type, p.escapedName); - if (propType && checkTypeAssignableTo(candidatePropType, propType, /*errorNode*/ undefined)) { - if (match && match !== type) { - match = undefined; - break findDiscriminant; - } - else { - match = type; - } + if (instantiatedConstraint.flags & TypeFlags.Union && !context.compareTypes(aggregateInference, getTypeWithThisArgument(instantiatedConstraint, aggregateInference))) { + const discriminantProps = findDiscriminantProperties(getPropertiesOfType(aggregateInference), instantiatedConstraint); + if (discriminantProps) { + let match: Type; + findDiscriminant: for (const p of discriminantProps) { + const candidatePropType = getTypeOfPropertyOfType(aggregateInference, p.escapedName); + for (const type of (instantiatedConstraint as UnionType).types) { + const propType = getTypeOfPropertyOfType(type, p.escapedName); + if (propType && checkTypeAssignableTo(candidatePropType, propType, /*errorNode*/ undefined)) { + if (match && match !== type) { + match = undefined; + break findDiscriminant; + } + else { + match = type; } } } - if (match) { - aggregateInference = getSpreadType(match, aggregateInference, /*symbol*/ undefined, /*propegatedFlags*/ 0); - } + } + if (match) { + aggregateInference = getSpreadType(match, aggregateInference, /*symbol*/ undefined, /*propegatedFlags*/ 0); } } }