diff --git a/tsc/internal/checker/inference.go b/tsc/internal/checker/inference.go index 8b0cc87aedef4..5daee24ccaadd 100644 --- a/tsc/internal/checker/inference.go +++ b/tsc/internal/checker/inference.go @@ -108,6 +108,14 @@ func (c *Checker) inferFromTypes(n *InferenceState, source *Type, target *Type) // the matching types. Types closely match when they are instantiations of the same // object type or instantiations of the same type alias. sources, targets := c.inferFromMatchingTypes(n, tempSources, tempTargets, (*Checker).isTypeCloselyMatchedBy, true /*sort*/) + // Next, if every source constituent has a target constituent with matching discriminants, infer + // between those and remove the matching types. For example, [false, string] matches [false, T] + // but not [true, U]. Otherwise an unmatched source could lose targets it relates to. + if len(sources) != 0 && core.Every(sources, func(s *Type) bool { + return core.Some(targets, func(t *Type) bool { return c.isTypeMatchedByDiscriminants(s, t) }) + }) { + sources, targets = c.inferFromMatchingTypes(n, sources, targets, (*Checker).isTypeMatchedByDiscriminants, true /*sort*/) + } if len(targets) == 0 { return } @@ -1241,6 +1249,52 @@ func (c *Checker) isTypeCloselyMatchedBy(s *Type, t *Type) bool { s.alias != nil && t.alias != nil && len(s.alias.typeArguments) != 0 && s.alias.symbol == t.alias.symbol } +// Types match by discriminants when they have a unit-typed property (or required tuple element) of +// the same type, none of different types, and aren't definitely unrelated. +func (c *Checker) isTypeMatchedByDiscriminants(s *Type, t *Type) bool { + if s.flags&TypeFlagsObject == 0 || t.flags&TypeFlagsObject == 0 || isTupleType(s) != isTupleType(t) || c.typesDefinitelyUnrelated(s, t) { + return false + } + matched := false + if isTupleType(s) { + sourceInfos := s.TargetTupleType().elementInfos + targetInfos := t.TargetTupleType().elementInfos + for i := range min(s.TargetTupleType().fixedLength, t.TargetTupleType().fixedLength) { + if sourceInfos[i].flags&ElementFlagsRequired != 0 && targetInfos[i].flags&ElementFlagsRequired != 0 { + switch c.compareDiscriminantTypes(c.getTypeArguments(s)[i], c.getTypeArguments(t)[i]) { + case TernaryFalse: + return false + case TernaryTrue: + matched = true + } + } + } + return matched + } + for _, targetProp := range c.getPropertiesOfType(t) { + if sourceProp := c.getPropertyOfType(s, targetProp.Name()); sourceProp != nil { + switch c.compareDiscriminantTypes(c.getTypeOfSymbol(sourceProp), c.getTypeOfSymbol(targetProp)) { + case TernaryFalse: + return false + case TernaryTrue: + matched = true + } + } + } + return matched +} + +// True for identical unit types, False for different unit types, Maybe otherwise. +func (c *Checker) compareDiscriminantTypes(s *Type, t *Type) Ternary { + if s.flags&TypeFlagsUnit == 0 || t.flags&TypeFlagsUnit == 0 { + return TernaryMaybe + } + if c.getRegularTypeOfLiteralType(s) == c.getRegularTypeOfLiteralType(t) { + return TernaryTrue + } + return TernaryFalse +} + // Create an object with properties named in the string literal type. Every property has type `any`. func (c *Checker) createEmptyObjectTypeFromStringLiteral(t *Type) *Type { members := ast.NewSymbolTable() diff --git a/tsc/testdata/baselines/reference/compiler/inferenceFromDiscriminatedUnionConstituents.errors.txt b/tsc/testdata/baselines/reference/compiler/inferenceFromDiscriminatedUnionConstituents.errors.txt new file mode 100644 index 0000000000000..c78a544c5cab2 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/inferenceFromDiscriminatedUnionConstituents.errors.txt @@ -0,0 +1,68 @@ +inferenceFromDiscriminatedUnionConstituents.ts(36,32): error TS2345: Argument of type '[(false | undefined)?, (string | undefined)?] | [(true | undefined)?, (number | undefined)?]' is not assignable to parameter of type '[(false | undefined)?, (string | undefined)?] | [(true | undefined)?, (string | undefined)?]'. + Type '[(true | undefined)?, (number | undefined)?]' is not assignable to type '[(false | undefined)?, (string | undefined)?] | [(true | undefined)?, (string | undefined)?]'. + Type '[(true | undefined)?, (number | undefined)?]' is not assignable to type '[(false | undefined)?, (string | undefined)?]'. + Type at position 0 in source is not compatible with type at position 0 in target. + Type 'true | undefined' is not assignable to type 'false | undefined'. + Type 'true' is not assignable to type 'false'. +inferenceFromDiscriminatedUnionConstituents.ts(40,30): error TS2345: Argument of type '[string, boolean] | [number, bigint]' is not assignable to parameter of type '[string, boolean] | [number, boolean]'. + Type '[number, bigint]' is not assignable to type '[string, boolean] | [number, boolean]'. + Type '[number, bigint]' is not assignable to type '[string, boolean]'. + Type at position 0 in source is not compatible with type at position 0 in target. + Type 'number' is not assignable to type 'string'. + + +==== inferenceFromDiscriminatedUnionConstituents.ts (2 errors) ==== + // https://github.com/microsoft/TypeScript/issues/63019 + + type ReadA = () => [false, R] | [true, R | undefined]; + type ReadB = () => [false, R] | [true, R | undefined]; + const test = (_: ReadA): ReadB => _; + + type ReadObjA = () => { done: false; value: R } | { done: true; value: R | undefined }; + type ReadObjB = () => { done: false; value: R } | { done: true; value: R | undefined }; + const testObj = (_: ReadObjA): ReadObjB => _; + + // Constituents are matched by their discriminants when inferring to a union. + + declare function pairs(x: [false, T] | [true, U]): [T, U]; + declare const tuples: [false, string] | [true, number]; + const fromTuples = pairs(tuples); // [string, number] + + declare function objects(x: { kind: "a"; value: T } | { kind: "b"; value: U }): [T, U]; + declare const objs: { kind: "a"; value: string } | { kind: "b"; value: number }; + const fromObjects = objects(objs); // [string, number] + + declare function enums(x: { kind: E.A; value: T } | { kind: E.B; value: U }): [T, U]; + declare const enumObjs: { kind: E.A; value: string } | { kind: E.B; value: number }; + const fromEnums = enums(enumObjs); // [string, number] + enum E { A, B } + + // Constituents with conflicting discriminants are not matched, even when another discriminant agrees. + + declare function multi(x: { a: 1; b: 1; value: T } | { a: 1; b: 2; value: U }): [T, U]; + declare const multiObjs: { a: 1; b: 1; value: string } | { a: 1; b: 2; value: number }; + const fromMulti = multi(multiObjs); // [string, number] + + // Optional tuple elements and non-unit types are not discriminants, so inference is unchanged. + + declare function optional(x: [false?, T?] | [true?, U?]): [T, U]; + declare const optionals: [false?, string?] | [true?, number?]; + const fromOptionals = optional(optionals); + ~~~~~~~~~ +!!! error TS2345: Argument of type '[(false | undefined)?, (string | undefined)?] | [(true | undefined)?, (number | undefined)?]' is not assignable to parameter of type '[(false | undefined)?, (string | undefined)?] | [(true | undefined)?, (string | undefined)?]'. +!!! error TS2345: Type '[(true | undefined)?, (number | undefined)?]' is not assignable to type '[(false | undefined)?, (string | undefined)?] | [(true | undefined)?, (string | undefined)?]'. +!!! error TS2345: Type '[(true | undefined)?, (number | undefined)?]' is not assignable to type '[(false | undefined)?, (string | undefined)?]'. +!!! error TS2345: Type at position 0 in source is not compatible with type at position 0 in target. +!!! error TS2345: Type 'true | undefined' is not assignable to type 'false | undefined'. +!!! error TS2345: Type 'true' is not assignable to type 'false'. + + declare function nonUnit(x: [string, T] | [number, U]): [T, U]; + declare const nonUnits: [string, boolean] | [number, bigint]; + const fromNonUnits = nonUnit(nonUnits); + ~~~~~~~~ +!!! error TS2345: Argument of type '[string, boolean] | [number, bigint]' is not assignable to parameter of type '[string, boolean] | [number, boolean]'. +!!! error TS2345: Type '[number, bigint]' is not assignable to type '[string, boolean] | [number, boolean]'. +!!! error TS2345: Type '[number, bigint]' is not assignable to type '[string, boolean]'. +!!! error TS2345: Type at position 0 in source is not compatible with type at position 0 in target. +!!! error TS2345: Type 'number' is not assignable to type 'string'. + \ No newline at end of file diff --git a/tsc/testdata/baselines/reference/compiler/inferenceFromDiscriminatedUnionConstituents.symbols b/tsc/testdata/baselines/reference/compiler/inferenceFromDiscriminatedUnionConstituents.symbols new file mode 100644 index 0000000000000..a47122f5eb981 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/inferenceFromDiscriminatedUnionConstituents.symbols @@ -0,0 +1,206 @@ +//// [tests/cases/compiler/inferenceFromDiscriminatedUnionConstituents.ts] //// + +=== inferenceFromDiscriminatedUnionConstituents.ts === +// https://github.com/microsoft/TypeScript/issues/63019 + +type ReadA = () => [false, R] | [true, R | undefined]; +>ReadA : Symbol(ReadA, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 0, 0)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 2, 14)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 2, 14)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 2, 14)) + +type ReadB = () => [false, R] | [true, R | undefined]; +>ReadB : Symbol(ReadB, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 2, 57)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 3, 14)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 3, 14)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 3, 14)) + +const test = (_: ReadA): ReadB => _; +>test : Symbol(test, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 4, 5)) +>_ : Symbol(_, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 4, 14)) +>ReadA : Symbol(ReadA, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 0, 0)) +>ReadB : Symbol(ReadB, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 2, 57)) +>_ : Symbol(_, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 4, 14)) + +type ReadObjA = () => { done: false; value: R } | { done: true; value: R | undefined }; +>ReadObjA : Symbol(ReadObjA, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 4, 36)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 6, 17)) +>done : Symbol(done, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 6, 26)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 6, 39)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 6, 17)) +>done : Symbol(done, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 6, 54)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 6, 66)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 6, 17)) + +type ReadObjB = () => { done: false; value: R } | { done: true; value: R | undefined }; +>ReadObjB : Symbol(ReadObjB, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 6, 90)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 7, 17)) +>done : Symbol(done, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 7, 26)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 7, 39)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 7, 17)) +>done : Symbol(done, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 7, 54)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 7, 66)) +>R : Symbol(R, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 7, 17)) + +const testObj = (_: ReadObjA): ReadObjB => _; +>testObj : Symbol(testObj, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 8, 5)) +>_ : Symbol(_, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 8, 17)) +>ReadObjA : Symbol(ReadObjA, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 4, 36)) +>ReadObjB : Symbol(ReadObjB, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 6, 90)) +>_ : Symbol(_, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 8, 17)) + +// Constituents are matched by their discriminants when inferring to a union. + +declare function pairs(x: [false, T] | [true, U]): [T, U]; +>pairs : Symbol(pairs, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 8, 45)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 12, 23)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 12, 25)) +>x : Symbol(x, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 12, 29)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 12, 23)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 12, 25)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 12, 23)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 12, 25)) + +declare const tuples: [false, string] | [true, number]; +>tuples : Symbol(tuples, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 13, 13)) + +const fromTuples = pairs(tuples); // [string, number] +>fromTuples : Symbol(fromTuples, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 14, 5)) +>pairs : Symbol(pairs, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 8, 45)) +>tuples : Symbol(tuples, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 13, 13)) + +declare function objects(x: { kind: "a"; value: T } | { kind: "b"; value: U }): [T, U]; +>objects : Symbol(objects, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 14, 33)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 16, 25)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 16, 27)) +>x : Symbol(x, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 16, 31)) +>kind : Symbol(kind, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 16, 35)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 16, 46)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 16, 25)) +>kind : Symbol(kind, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 16, 61)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 16, 72)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 16, 27)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 16, 25)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 16, 27)) + +declare const objs: { kind: "a"; value: string } | { kind: "b"; value: number }; +>objs : Symbol(objs, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 17, 13)) +>kind : Symbol(kind, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 17, 21)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 17, 32)) +>kind : Symbol(kind, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 17, 52)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 17, 63)) + +const fromObjects = objects(objs); // [string, number] +>fromObjects : Symbol(fromObjects, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 18, 5)) +>objects : Symbol(objects, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 14, 33)) +>objs : Symbol(objs, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 17, 13)) + +declare function enums(x: { kind: E.A; value: T } | { kind: E.B; value: U }): [T, U]; +>enums : Symbol(enums, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 18, 34)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 20, 23)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 20, 25)) +>x : Symbol(x, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 20, 29)) +>kind : Symbol(kind, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 20, 33)) +>E : Symbol(E, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 22, 34)) +>A : Symbol(E.A, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 23, 8)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 20, 44)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 20, 23)) +>kind : Symbol(kind, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 20, 59)) +>E : Symbol(E, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 22, 34)) +>B : Symbol(E.B, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 23, 11)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 20, 70)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 20, 25)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 20, 23)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 20, 25)) + +declare const enumObjs: { kind: E.A; value: string } | { kind: E.B; value: number }; +>enumObjs : Symbol(enumObjs, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 21, 13)) +>kind : Symbol(kind, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 21, 25)) +>E : Symbol(E, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 22, 34)) +>A : Symbol(E.A, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 23, 8)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 21, 36)) +>kind : Symbol(kind, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 21, 56)) +>E : Symbol(E, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 22, 34)) +>B : Symbol(E.B, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 23, 11)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 21, 67)) + +const fromEnums = enums(enumObjs); // [string, number] +>fromEnums : Symbol(fromEnums, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 22, 5)) +>enums : Symbol(enums, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 18, 34)) +>enumObjs : Symbol(enumObjs, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 21, 13)) + +enum E { A, B } +>E : Symbol(E, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 22, 34)) +>A : Symbol(E.A, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 23, 8)) +>B : Symbol(E.B, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 23, 11)) + +// Constituents with conflicting discriminants are not matched, even when another discriminant agrees. + +declare function multi(x: { a: 1; b: 1; value: T } | { a: 1; b: 2; value: U }): [T, U]; +>multi : Symbol(multi, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 23, 15)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 23)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 25)) +>x : Symbol(x, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 29)) +>a : Symbol(a, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 33)) +>b : Symbol(b, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 39)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 45)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 23)) +>a : Symbol(a, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 60)) +>b : Symbol(b, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 66)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 72)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 25)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 23)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 27, 25)) + +declare const multiObjs: { a: 1; b: 1; value: string } | { a: 1; b: 2; value: number }; +>multiObjs : Symbol(multiObjs, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 28, 13)) +>a : Symbol(a, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 28, 26)) +>b : Symbol(b, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 28, 32)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 28, 38)) +>a : Symbol(a, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 28, 58)) +>b : Symbol(b, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 28, 64)) +>value : Symbol(value, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 28, 70)) + +const fromMulti = multi(multiObjs); // [string, number] +>fromMulti : Symbol(fromMulti, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 29, 5)) +>multi : Symbol(multi, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 23, 15)) +>multiObjs : Symbol(multiObjs, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 28, 13)) + +// Optional tuple elements and non-unit types are not discriminants, so inference is unchanged. + +declare function optional(x: [false?, T?] | [true?, U?]): [T, U]; +>optional : Symbol(optional, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 29, 35)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 33, 26)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 33, 28)) +>x : Symbol(x, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 33, 32)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 33, 26)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 33, 28)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 33, 26)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 33, 28)) + +declare const optionals: [false?, string?] | [true?, number?]; +>optionals : Symbol(optionals, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 34, 13)) + +const fromOptionals = optional(optionals); +>fromOptionals : Symbol(fromOptionals, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 35, 5)) +>optional : Symbol(optional, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 29, 35)) +>optionals : Symbol(optionals, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 34, 13)) + +declare function nonUnit(x: [string, T] | [number, U]): [T, U]; +>nonUnit : Symbol(nonUnit, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 35, 42)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 37, 25)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 37, 27)) +>x : Symbol(x, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 37, 31)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 37, 25)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 37, 27)) +>T : Symbol(T, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 37, 25)) +>U : Symbol(U, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 37, 27)) + +declare const nonUnits: [string, boolean] | [number, bigint]; +>nonUnits : Symbol(nonUnits, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 38, 13)) + +const fromNonUnits = nonUnit(nonUnits); +>fromNonUnits : Symbol(fromNonUnits, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 39, 5)) +>nonUnit : Symbol(nonUnit, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 35, 42)) +>nonUnits : Symbol(nonUnits, Decl(inferenceFromDiscriminatedUnionConstituents.ts, 38, 13)) + diff --git a/tsc/testdata/baselines/reference/compiler/inferenceFromDiscriminatedUnionConstituents.types b/tsc/testdata/baselines/reference/compiler/inferenceFromDiscriminatedUnionConstituents.types new file mode 100644 index 0000000000000..e3b7fe140b409 --- /dev/null +++ b/tsc/testdata/baselines/reference/compiler/inferenceFromDiscriminatedUnionConstituents.types @@ -0,0 +1,174 @@ +//// [tests/cases/compiler/inferenceFromDiscriminatedUnionConstituents.ts] //// + +=== inferenceFromDiscriminatedUnionConstituents.ts === +// https://github.com/microsoft/TypeScript/issues/63019 + +type ReadA = () => [false, R] | [true, R | undefined]; +>ReadA : ReadA +>false : false +>true : true + +type ReadB = () => [false, R] | [true, R | undefined]; +>ReadB : ReadB +>false : false +>true : true + +const test = (_: ReadA): ReadB => _; +>test : (_: ReadA) => ReadB +>(_: ReadA): ReadB => _ : (_: ReadA) => ReadB +>_ : ReadA +>_ : ReadA + +type ReadObjA = () => { done: false; value: R } | { done: true; value: R | undefined }; +>ReadObjA : ReadObjA +>done : false +>false : false +>value : R +>done : true +>true : true +>value : R | undefined + +type ReadObjB = () => { done: false; value: R } | { done: true; value: R | undefined }; +>ReadObjB : ReadObjB +>done : false +>false : false +>value : R +>done : true +>true : true +>value : R | undefined + +const testObj = (_: ReadObjA): ReadObjB => _; +>testObj : (_: ReadObjA) => ReadObjB +>(_: ReadObjA): ReadObjB => _ : (_: ReadObjA) => ReadObjB +>_ : ReadObjA +>_ : ReadObjA + +// Constituents are matched by their discriminants when inferring to a union. + +declare function pairs(x: [false, T] | [true, U]): [T, U]; +>pairs : (x: [false, T] | [true, U]) => [T, U] +>x : [false, T] | [true, U] +>false : false +>true : true + +declare const tuples: [false, string] | [true, number]; +>tuples : [false, string] | [true, number] +>false : false +>true : true + +const fromTuples = pairs(tuples); // [string, number] +>fromTuples : [string, number] +>pairs(tuples) : [string, number] +>pairs : (x: [false, T] | [true, U]) => [T, U] +>tuples : [false, string] | [true, number] + +declare function objects(x: { kind: "a"; value: T } | { kind: "b"; value: U }): [T, U]; +>objects : (x: { kind: "a"; value: T; } | { kind: "b"; value: U; }) => [T, U] +>x : { kind: "a"; value: T; } | { kind: "b"; value: U; } +>kind : "a" +>value : T +>kind : "b" +>value : U + +declare const objs: { kind: "a"; value: string } | { kind: "b"; value: number }; +>objs : { kind: "a"; value: string; } | { kind: "b"; value: number; } +>kind : "a" +>value : string +>kind : "b" +>value : number + +const fromObjects = objects(objs); // [string, number] +>fromObjects : [string, number] +>objects(objs) : [string, number] +>objects : (x: { kind: "a"; value: T; } | { kind: "b"; value: U; }) => [T, U] +>objs : { kind: "a"; value: string; } | { kind: "b"; value: number; } + +declare function enums(x: { kind: E.A; value: T } | { kind: E.B; value: U }): [T, U]; +>enums : (x: { kind: E.A; value: T; } | { kind: E.B; value: U; }) => [T, U] +>x : { kind: E.A; value: T; } | { kind: E.B; value: U; } +>kind : E.A +>E : any +>value : T +>kind : E.B +>E : any +>value : U + +declare const enumObjs: { kind: E.A; value: string } | { kind: E.B; value: number }; +>enumObjs : { kind: E.A; value: string; } | { kind: E.B; value: number; } +>kind : E.A +>E : any +>value : string +>kind : E.B +>E : any +>value : number + +const fromEnums = enums(enumObjs); // [string, number] +>fromEnums : [string, number] +>enums(enumObjs) : [string, number] +>enums : (x: { kind: E.A; value: T; } | { kind: E.B; value: U; }) => [T, U] +>enumObjs : { kind: E.A; value: string; } | { kind: E.B; value: number; } + +enum E { A, B } +>E : E +>A : E.A +>B : E.B + +// Constituents with conflicting discriminants are not matched, even when another discriminant agrees. + +declare function multi(x: { a: 1; b: 1; value: T } | { a: 1; b: 2; value: U }): [T, U]; +>multi : (x: { a: 1; b: 1; value: T; } | { a: 1; b: 2; value: U; }) => [T, U] +>x : { a: 1; b: 1; value: T; } | { a: 1; b: 2; value: U; } +>a : 1 +>b : 1 +>value : T +>a : 1 +>b : 2 +>value : U + +declare const multiObjs: { a: 1; b: 1; value: string } | { a: 1; b: 2; value: number }; +>multiObjs : { a: 1; b: 1; value: string; } | { a: 1; b: 2; value: number; } +>a : 1 +>b : 1 +>value : string +>a : 1 +>b : 2 +>value : number + +const fromMulti = multi(multiObjs); // [string, number] +>fromMulti : [string, number] +>multi(multiObjs) : [string, number] +>multi : (x: { a: 1; b: 1; value: T; } | { a: 1; b: 2; value: U; }) => [T, U] +>multiObjs : { a: 1; b: 1; value: string; } | { a: 1; b: 2; value: number; } + +// Optional tuple elements and non-unit types are not discriminants, so inference is unchanged. + +declare function optional(x: [false?, T?] | [true?, U?]): [T, U]; +>optional : (x: [false?, T?] | [true?, U?]) => [T, U] +>x : [(false | undefined)?, (T | undefined)?] | [(true | undefined)?, (U | undefined)?] +>false : false +>true : true + +declare const optionals: [false?, string?] | [true?, number?]; +>optionals : [(false | undefined)?, (string | undefined)?] | [(true | undefined)?, (number | undefined)?] +>false : false +>true : true + +const fromOptionals = optional(optionals); +>fromOptionals : [string, string] +>optional(optionals) : [string, string] +>optional : (x: [false?, T?] | [true?, U?]) => [T, U] +>optionals : [(false | undefined)?, (string | undefined)?] | [(true | undefined)?, (number | undefined)?] + +declare function nonUnit(x: [string, T] | [number, U]): [T, U]; +>nonUnit : (x: [string, T] | [number, U]) => [T, U] +>x : [string, T] | [number, U] + +declare const nonUnits: [string, boolean] | [number, bigint]; +>nonUnits : [string, boolean] | [number, bigint] + +const fromNonUnits = nonUnit(nonUnits); +>fromNonUnits : [boolean, boolean] +>nonUnit(nonUnits) : [boolean, boolean] +>nonUnit : (x: [string, T] | [number, U]) => [T, U] +>nonUnits : [string, boolean] | [number, bigint] + diff --git a/tsc/testdata/tests/cases/compiler/inferenceFromDiscriminatedUnionConstituents.ts b/tsc/testdata/tests/cases/compiler/inferenceFromDiscriminatedUnionConstituents.ts new file mode 100644 index 0000000000000..063a64a995aea --- /dev/null +++ b/tsc/testdata/tests/cases/compiler/inferenceFromDiscriminatedUnionConstituents.ts @@ -0,0 +1,44 @@ +// @target: es2015 +// @strict: true +// @noEmit: true + +// https://github.com/microsoft/TypeScript/issues/63019 + +type ReadA = () => [false, R] | [true, R | undefined]; +type ReadB = () => [false, R] | [true, R | undefined]; +const test = (_: ReadA): ReadB => _; + +type ReadObjA = () => { done: false; value: R } | { done: true; value: R | undefined }; +type ReadObjB = () => { done: false; value: R } | { done: true; value: R | undefined }; +const testObj = (_: ReadObjA): ReadObjB => _; + +// Constituents are matched by their discriminants when inferring to a union. + +declare function pairs(x: [false, T] | [true, U]): [T, U]; +declare const tuples: [false, string] | [true, number]; +const fromTuples = pairs(tuples); // [string, number] + +declare function objects(x: { kind: "a"; value: T } | { kind: "b"; value: U }): [T, U]; +declare const objs: { kind: "a"; value: string } | { kind: "b"; value: number }; +const fromObjects = objects(objs); // [string, number] + +declare function enums(x: { kind: E.A; value: T } | { kind: E.B; value: U }): [T, U]; +declare const enumObjs: { kind: E.A; value: string } | { kind: E.B; value: number }; +const fromEnums = enums(enumObjs); // [string, number] +enum E { A, B } + +// Constituents with conflicting discriminants are not matched, even when another discriminant agrees. + +declare function multi(x: { a: 1; b: 1; value: T } | { a: 1; b: 2; value: U }): [T, U]; +declare const multiObjs: { a: 1; b: 1; value: string } | { a: 1; b: 2; value: number }; +const fromMulti = multi(multiObjs); // [string, number] + +// Optional tuple elements and non-unit types are not discriminants, so inference is unchanged. + +declare function optional(x: [false?, T?] | [true?, U?]): [T, U]; +declare const optionals: [false?, string?] | [true?, number?]; +const fromOptionals = optional(optionals); + +declare function nonUnit(x: [string, T] | [number, U]): [T, U]; +declare const nonUnits: [string, boolean] | [number, bigint]; +const fromNonUnits = nonUnit(nonUnits);