Skip to content

Commit

Permalink
Discriminate partial inferences if not complete enough to satisfy con…
Browse files Browse the repository at this point in the history
…straint
  • Loading branch information
weswigham committed Dec 1, 2017
1 parent aa9a886 commit 031a370
Show file tree
Hide file tree
Showing 5 changed files with 149 additions and 1 deletion.
33 changes: 32 additions & 1 deletion src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11552,7 +11552,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.
Expand Down
13 changes: 13 additions & 0 deletions tests/baselines/reference/typeInferenceOnIndexUnion.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//// [typeInferenceOnIndexUnion.ts]
type Options = { k: "a", a: number } | { k: "b", b: string };
declare function f<T extends Options>(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<T extends Options2>(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} }`
45 changes: 45 additions & 0 deletions tests/baselines/reference/typeInferenceOnIndexUnion.symbols
Original file line number Diff line number Diff line change
@@ -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<T extends Options>(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<T extends Options2>(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))

52 changes: 52 additions & 0 deletions tests/baselines/reference/typeInferenceOnIndexUnion.types
Original file line number Diff line number Diff line change
@@ -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<T extends Options>(p: T["k"]): T;
>f : <T extends Options>(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 : <T extends Options>(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<T extends Options2>(p: T["k"], c: T["c"]): T;
>f2 : <T extends Options2>(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 : <T extends Options2>(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

7 changes: 7 additions & 0 deletions tests/cases/compiler/typeInferenceOnIndexUnion.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
type Options = { k: "a", a: number } | { k: "b", b: string };
declare function f<T extends Options>(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<T extends Options2>(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} }`

0 comments on commit 031a370

Please sign in to comment.