diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 87d95b64d31cb..f6d05ad27b5e4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11197,7 +11197,8 @@ namespace ts { inferredType: undefined, priority: undefined, topLevel: true, - isFixed: false + isFixed: false, + indexes: undefined, }; } @@ -11208,7 +11209,8 @@ namespace ts { inferredType: inference.inferredType, priority: inference.priority, topLevel: inference.topLevel, - isFixed: inference.isFixed + isFixed: inference.isFixed, + indexes: inference.indexes && inference.indexes.slice(), }; } @@ -11430,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; @@ -11665,6 +11684,39 @@ namespace ts { const inference = context.inferences[index]; let inferredType = inference.inferredType; if (!inferredType) { + if (inference.indexes) { + // Build a candidate from all indexes + let aggregateInference = getIntersectionType(inference.indexes); + const constraint = getConstraintOfTypeParameter(context.signature.typeParameters[index]); + if (constraint) { + const instantiatedConstraint = instantiateType(constraint, context); + 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); + } + } + } + } + (inference.candidates || (inference.candidates = [])).push(aggregateInference); + } if (inference.candidates) { // Extract all object literal types and replace them with a single widened and normalized type. const candidates = widenObjectLiteralCandidates(inference.candidates); 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/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/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..b31a76ef75725 --- /dev/null +++ b/tests/baselines/reference/indexAccessCombinedInference.js @@ -0,0 +1,148 @@ +//// [indexAccessCombinedInference.ts] +// Simple case +interface Args { + TA: object, + TY: object +} + +declare function foo( + a: T["TA"], + b: T["TY"]): T["TA"] & T["TY"]; + +const x = foo({ + x: { + j: 12, + i: 11 + } +}, { 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] +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 new file mode 100644 index 0000000000000..d108b7d1e9c72 --- /dev/null +++ b/tests/baselines/reference/indexAccessCombinedInference.symbols @@ -0,0 +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, 1, 16)) + + TY: object +>TY : Symbol(Args.TY, Decl(indexAccessCombinedInference.ts, 2, 15)) +} + +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, 6, 37)) +>T : Symbol(T, Decl(indexAccessCombinedInference.ts, 6, 21)) + + 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)) + +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)) + + 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, 46, 15)) + + j: 12, +>j : Symbol(j, Decl(indexAccessCombinedInference.ts, 47, 8)) + + i: 11 +>i : Symbol(i, Decl(indexAccessCombinedInference.ts, 48, 14)) + } +}, { y: 42 }); +>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 new file mode 100644 index 0000000000000..03badaade7613 --- /dev/null +++ b/tests/baselines/reference/indexAccessCombinedInference.types @@ -0,0 +1,485 @@ +=== tests/cases/compiler/indexAccessCombinedInference.ts === +// Simple case +interface Args { +>Args : Args + + TA: object, +>TA : object + + TY: object +>TY : object +} + +declare 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 + +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 + +// 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/baselines/reference/inferingFromAny.types b/tests/baselines/reference/inferingFromAny.types index 72e2024d0224e..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) : any +>f19(a, a) : { [K in Keys]: { [K1 in K]: any; }; }[K] >f19 : (k: K, x: T[K]) => T >a : any >a : any 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/indexAccessCombinedInference.ts b/tests/cases/compiler/indexAccessCombinedInference.ts new file mode 100644 index 0000000000000..59af2dd60fa1f --- /dev/null +++ b/tests/cases/compiler/indexAccessCombinedInference.ts @@ -0,0 +1,108 @@ +// Simple case +interface Args { + TA: object, + TY: object +} + +declare function foo( + a: T["TA"], + b: T["TY"]): T["TA"] & T["TY"]; + +const x = foo({ + x: { + j: 12, + i: 11 + } +}, { 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: () => ""}); 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} }`