diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index dfd479127202c..63ff310112e42 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -23098,6 +23098,41 @@ export function createTypeChecker(host: TypeCheckerHost): TypeChecker { } } if (reportErrors) { + // Check if we're dealing with a discriminated union case where we can provide better error messages + if (target.flags & TypeFlags.Union && source.flags & TypeFlags.Object) { + const discriminantProps = findDiscriminantProperties(getPropertiesOfType(source), target); + if (discriminantProps && discriminantProps.length > 0) { + // For each discriminant property, check if the source property type contains values + // not present in any constituent of the target union + for (const prop of discriminantProps) { + const sourcePropType = getTypeOfSymbol(prop); + if (sourcePropType.flags & TypeFlags.Union) { + // Get all discriminant values from the target union for this property + const targetDiscriminantTypes: Type[] = []; + for (const constituent of (target as UnionType).types) { + const constituentPropType = getTypeOfPropertyOfType(constituent, prop.escapedName); + if (constituentPropType) { + forEachType(constituentPropType, t => targetDiscriminantTypes.push(t)); + } + } + const targetDiscriminantUnion = getUnionType(targetDiscriminantTypes); + // Find source types that are not assignable to the target discriminant union + const incompatibleTypes: Type[] = []; + for (const sourceType of (sourcePropType as UnionType).types) { + if (!isTypeAssignableTo(sourceType, targetDiscriminantUnion)) { + incompatibleTypes.push(sourceType); + } + } + if (incompatibleTypes.length > 0) { + const incompatibleTypesStr = incompatibleTypes.map(t => typeToString(t)).join(" | "); + const propName = symbolToString(prop); + reportError(Diagnostics.Type_0_is_not_assignable_to_type_1_The_type_s_2_are_not_present_in_the_discriminator_3_of_the_target, typeToString(source), typeToString(target), incompatibleTypesStr, propName); + return Ternary.False; + } + } + } + } + } // Elaborate only if we can find a best matching type in the target union const bestMatchingType = getBestMatchingType(source, target, isRelatedTo); if (bestMatchingType) { diff --git a/src/compiler/diagnosticMessages.json b/src/compiler/diagnosticMessages.json index c21fd11e5c7bf..3dafc9fc3bd74 100644 --- a/src/compiler/diagnosticMessages.json +++ b/src/compiler/diagnosticMessages.json @@ -4023,6 +4023,14 @@ "category": "Error", "code": 2882 }, + "Type '{0}' is not assignable to type '{1}'. The types of '{2}' are incompatible between these types.": { + "category": "Error", + "code": 2883 + }, + "Type '{0}' is not assignable to type '{1}'. The type(s) '{2}' are not present in the discriminator '{3}' of the target.": { + "category": "Error", + "code": 2884 + }, "Import declaration '{0}' is using private name '{1}'.": { "category": "Error", diff --git a/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.errors.txt b/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.errors.txt new file mode 100644 index 0000000000000..fe54da0784176 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.errors.txt @@ -0,0 +1,58 @@ +discriminatedUnionIncompatibleMemberErrorMessage.ts(12,7): error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. + Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. The type(s) '"baz"' are not present in the discriminator 'discriminator' of the target. +discriminatedUnionIncompatibleMemberErrorMessage.ts(17,7): error TS2322: Type '{ discriminator: WiderUnion; }' is not assignable to type 'Discriminated'. + Type '{ discriminator: WiderUnion; }' is not assignable to type 'Discriminated'. The type(s) '"baz" | "qux"' are not present in the discriminator 'discriminator' of the target. +discriminatedUnionIncompatibleMemberErrorMessage.ts(26,7): error TS2322: Type '{ kind: ShapeKind; }' is not assignable to type 'Shape'. + Type '{ kind: ShapeKind; }' is not assignable to type 'Shape'. The type(s) '"triangle"' are not present in the discriminator 'kind' of the target. +discriminatedUnionIncompatibleMemberErrorMessage.ts(33,7): error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated2'. + Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated2'. The type(s) '"baz"' are not present in the discriminator 'discriminator' of the target. + + +==== discriminatedUnionIncompatibleMemberErrorMessage.ts (4 errors) ==== + // Test case for issue #62737 + // Improve error messages when assigning objects with wider discriminator unions to discriminated union types + + // Basic case - single incompatible type + type Discriminated = + | { discriminator: "foo" } + | { discriminator: "bar" }; + + type UnionType = "foo" | "bar" | "baz"; + + const obj = { discriminator: "foo" as UnionType }; + const err: Discriminated = obj; // Error: "baz" is not present in discriminator + ~~~ +!!! error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. +!!! error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. The type(s) '"baz"' are not present in the discriminator 'discriminator' of the target. + + // Multiple incompatible types + type WiderUnion = "foo" | "bar" | "baz" | "qux"; + const obj2 = { discriminator: "foo" as WiderUnion }; + const err2: Discriminated = obj2; // Error: "baz" | "qux" are not present in discriminator + ~~~~ +!!! error TS2322: Type '{ discriminator: WiderUnion; }' is not assignable to type 'Discriminated'. +!!! error TS2322: Type '{ discriminator: WiderUnion; }' is not assignable to type 'Discriminated'. The type(s) '"baz" | "qux"' are not present in the discriminator 'discriminator' of the target. + + // Different discriminator property name + type Shape = + | { kind: "circle"; radius: number } + | { kind: "square"; side: number }; + + type ShapeKind = "circle" | "square" | "triangle"; + const shape = { kind: "circle" as ShapeKind }; + const err3: Shape = shape; // Error: "triangle" is not present in discriminator "kind" + ~~~~ +!!! error TS2322: Type '{ kind: ShapeKind; }' is not assignable to type 'Shape'. +!!! error TS2322: Type '{ kind: ShapeKind; }' is not assignable to type 'Shape'. The type(s) '"triangle"' are not present in the discriminator 'kind' of the target. + + // From issue #62603 + type Discriminated2 = + | { discriminator: "foo" } + | { discriminator: "bar" }; + + const unexpectedError: Discriminated2 = { discriminator: "foo" } as { discriminator: UnionType }; + ~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated2'. +!!! error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated2'. The type(s) '"baz"' are not present in the discriminator 'discriminator' of the target. + + \ No newline at end of file diff --git a/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.js b/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.js new file mode 100644 index 0000000000000..4fa38884b2877 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.js @@ -0,0 +1,50 @@ +//// [tests/cases/compiler/discriminatedUnionIncompatibleMemberErrorMessage.ts] //// + +//// [discriminatedUnionIncompatibleMemberErrorMessage.ts] +// Test case for issue #62737 +// Improve error messages when assigning objects with wider discriminator unions to discriminated union types + +// Basic case - single incompatible type +type Discriminated = + | { discriminator: "foo" } + | { discriminator: "bar" }; + +type UnionType = "foo" | "bar" | "baz"; + +const obj = { discriminator: "foo" as UnionType }; +const err: Discriminated = obj; // Error: "baz" is not present in discriminator + +// Multiple incompatible types +type WiderUnion = "foo" | "bar" | "baz" | "qux"; +const obj2 = { discriminator: "foo" as WiderUnion }; +const err2: Discriminated = obj2; // Error: "baz" | "qux" are not present in discriminator + +// Different discriminator property name +type Shape = + | { kind: "circle"; radius: number } + | { kind: "square"; side: number }; + +type ShapeKind = "circle" | "square" | "triangle"; +const shape = { kind: "circle" as ShapeKind }; +const err3: Shape = shape; // Error: "triangle" is not present in discriminator "kind" + +// From issue #62603 +type Discriminated2 = + | { discriminator: "foo" } + | { discriminator: "bar" }; + +const unexpectedError: Discriminated2 = { discriminator: "foo" } as { discriminator: UnionType }; + + + +//// [discriminatedUnionIncompatibleMemberErrorMessage.js] +"use strict"; +// Test case for issue #62737 +// Improve error messages when assigning objects with wider discriminator unions to discriminated union types +var obj = { discriminator: "foo" }; +var err = obj; // Error: "baz" is not present in discriminator +var obj2 = { discriminator: "foo" }; +var err2 = obj2; // Error: "baz" | "qux" are not present in discriminator +var shape = { kind: "circle" }; +var err3 = shape; // Error: "triangle" is not present in discriminator "kind" +var unexpectedError = { discriminator: "foo" }; diff --git a/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.symbols b/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.symbols new file mode 100644 index 0000000000000..f1225a2d192a0 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.symbols @@ -0,0 +1,86 @@ +//// [tests/cases/compiler/discriminatedUnionIncompatibleMemberErrorMessage.ts] //// + +=== discriminatedUnionIncompatibleMemberErrorMessage.ts === +// Test case for issue #62737 +// Improve error messages when assigning objects with wider discriminator unions to discriminated union types + +// Basic case - single incompatible type +type Discriminated = +>Discriminated : Symbol(Discriminated, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 0, 0)) + + | { discriminator: "foo" } +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 5, 5)) + + | { discriminator: "bar" }; +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 6, 5)) + +type UnionType = "foo" | "bar" | "baz"; +>UnionType : Symbol(UnionType, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 6, 29)) + +const obj = { discriminator: "foo" as UnionType }; +>obj : Symbol(obj, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 10, 5)) +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 10, 13)) +>UnionType : Symbol(UnionType, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 6, 29)) + +const err: Discriminated = obj; // Error: "baz" is not present in discriminator +>err : Symbol(err, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 11, 5)) +>Discriminated : Symbol(Discriminated, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 0, 0)) +>obj : Symbol(obj, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 10, 5)) + +// Multiple incompatible types +type WiderUnion = "foo" | "bar" | "baz" | "qux"; +>WiderUnion : Symbol(WiderUnion, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 11, 31)) + +const obj2 = { discriminator: "foo" as WiderUnion }; +>obj2 : Symbol(obj2, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 15, 5)) +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 15, 14)) +>WiderUnion : Symbol(WiderUnion, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 11, 31)) + +const err2: Discriminated = obj2; // Error: "baz" | "qux" are not present in discriminator +>err2 : Symbol(err2, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 16, 5)) +>Discriminated : Symbol(Discriminated, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 0, 0)) +>obj2 : Symbol(obj2, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 15, 5)) + +// Different discriminator property name +type Shape = +>Shape : Symbol(Shape, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 16, 33)) + + | { kind: "circle"; radius: number } +>kind : Symbol(kind, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 20, 5)) +>radius : Symbol(radius, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 20, 21)) + + | { kind: "square"; side: number }; +>kind : Symbol(kind, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 21, 5)) +>side : Symbol(side, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 21, 21)) + +type ShapeKind = "circle" | "square" | "triangle"; +>ShapeKind : Symbol(ShapeKind, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 21, 37)) + +const shape = { kind: "circle" as ShapeKind }; +>shape : Symbol(shape, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 24, 5)) +>kind : Symbol(kind, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 24, 15)) +>ShapeKind : Symbol(ShapeKind, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 21, 37)) + +const err3: Shape = shape; // Error: "triangle" is not present in discriminator "kind" +>err3 : Symbol(err3, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 25, 5)) +>Shape : Symbol(Shape, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 16, 33)) +>shape : Symbol(shape, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 24, 5)) + +// From issue #62603 +type Discriminated2 = +>Discriminated2 : Symbol(Discriminated2, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 25, 26)) + + | { discriminator: "foo" } +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 29, 5)) + + | { discriminator: "bar" }; +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 30, 5)) + +const unexpectedError: Discriminated2 = { discriminator: "foo" } as { discriminator: UnionType }; +>unexpectedError : Symbol(unexpectedError, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 32, 5)) +>Discriminated2 : Symbol(Discriminated2, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 25, 26)) +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 32, 41)) +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 32, 69)) +>UnionType : Symbol(UnionType, Decl(discriminatedUnionIncompatibleMemberErrorMessage.ts, 6, 29)) + + diff --git a/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.types b/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.types new file mode 100644 index 0000000000000..f1194f25aed77 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionIncompatibleMemberErrorMessage.types @@ -0,0 +1,131 @@ +//// [tests/cases/compiler/discriminatedUnionIncompatibleMemberErrorMessage.ts] //// + +=== discriminatedUnionIncompatibleMemberErrorMessage.ts === +// Test case for issue #62737 +// Improve error messages when assigning objects with wider discriminator unions to discriminated union types + +// Basic case - single incompatible type +type Discriminated = +>Discriminated : Discriminated +> : ^^^^^^^^^^^^^ + + | { discriminator: "foo" } +>discriminator : "foo" +> : ^^^^^ + + | { discriminator: "bar" }; +>discriminator : "bar" +> : ^^^^^ + +type UnionType = "foo" | "bar" | "baz"; +>UnionType : UnionType +> : ^^^^^^^^^ + +const obj = { discriminator: "foo" as UnionType }; +>obj : { discriminator: UnionType; } +> : ^^^^^^^^^^^^^^^^^ ^^^ +>{ discriminator: "foo" as UnionType } : { discriminator: UnionType; } +> : ^^^^^^^^^^^^^^^^^ ^^^ +>discriminator : UnionType +> : ^^^^^^^^^ +>"foo" as UnionType : UnionType +> : ^^^^^^^^^ +>"foo" : "foo" +> : ^^^^^ + +const err: Discriminated = obj; // Error: "baz" is not present in discriminator +>err : Discriminated +> : ^^^^^^^^^^^^^ +>obj : { discriminator: UnionType; } +> : ^^^^^^^^^^^^^^^^^ ^^^ + +// Multiple incompatible types +type WiderUnion = "foo" | "bar" | "baz" | "qux"; +>WiderUnion : WiderUnion +> : ^^^^^^^^^^ + +const obj2 = { discriminator: "foo" as WiderUnion }; +>obj2 : { discriminator: WiderUnion; } +> : ^^^^^^^^^^^^^^^^^ ^^^ +>{ discriminator: "foo" as WiderUnion } : { discriminator: WiderUnion; } +> : ^^^^^^^^^^^^^^^^^ ^^^ +>discriminator : WiderUnion +> : ^^^^^^^^^^ +>"foo" as WiderUnion : WiderUnion +> : ^^^^^^^^^^ +>"foo" : "foo" +> : ^^^^^ + +const err2: Discriminated = obj2; // Error: "baz" | "qux" are not present in discriminator +>err2 : Discriminated +> : ^^^^^^^^^^^^^ +>obj2 : { discriminator: WiderUnion; } +> : ^^^^^^^^^^^^^^^^^ ^^^ + +// Different discriminator property name +type Shape = +>Shape : Shape +> : ^^^^^ + + | { kind: "circle"; radius: number } +>kind : "circle" +> : ^^^^^^^^ +>radius : number +> : ^^^^^^ + + | { kind: "square"; side: number }; +>kind : "square" +> : ^^^^^^^^ +>side : number +> : ^^^^^^ + +type ShapeKind = "circle" | "square" | "triangle"; +>ShapeKind : ShapeKind +> : ^^^^^^^^^ + +const shape = { kind: "circle" as ShapeKind }; +>shape : { kind: ShapeKind; } +> : ^^^^^^^^ ^^^ +>{ kind: "circle" as ShapeKind } : { kind: ShapeKind; } +> : ^^^^^^^^ ^^^ +>kind : ShapeKind +> : ^^^^^^^^^ +>"circle" as ShapeKind : ShapeKind +> : ^^^^^^^^^ +>"circle" : "circle" +> : ^^^^^^^^ + +const err3: Shape = shape; // Error: "triangle" is not present in discriminator "kind" +>err3 : Shape +> : ^^^^^ +>shape : { kind: ShapeKind; } +> : ^^^^^^^^ ^^^ + +// From issue #62603 +type Discriminated2 = +>Discriminated2 : Discriminated2 +> : ^^^^^^^^^^^^^^ + + | { discriminator: "foo" } +>discriminator : "foo" +> : ^^^^^ + + | { discriminator: "bar" }; +>discriminator : "bar" +> : ^^^^^ + +const unexpectedError: Discriminated2 = { discriminator: "foo" } as { discriminator: UnionType }; +>unexpectedError : Discriminated2 +> : ^^^^^^^^^^^^^^ +>{ discriminator: "foo" } as { discriminator: UnionType } : { discriminator: UnionType; } +> : ^^^^^^^^^^^^^^^^^ ^^^ +>{ discriminator: "foo" } : { discriminator: "foo"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +>discriminator : "foo" +> : ^^^^^ +>"foo" : "foo" +> : ^^^^^ +>discriminator : UnionType +> : ^^^^^^^^^ + + diff --git a/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.errors.txt b/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.errors.txt new file mode 100644 index 0000000000000..3b968a81f0920 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.errors.txt @@ -0,0 +1,40 @@ +discriminatedUnionIncompatibleMemberMessage.ts(12,7): error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. + Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. The type(s) '"baz"' are not present in the discriminator 'discriminator' of the target. +discriminatedUnionIncompatibleMemberMessage.ts(17,7): error TS2322: Type '{ discriminator: UnionType2; }' is not assignable to type 'Discriminated'. + Type '{ discriminator: UnionType2; }' is not assignable to type 'Discriminated'. The type(s) '"baz" | "qux"' are not present in the discriminator 'discriminator' of the target. +discriminatedUnionIncompatibleMemberMessage.ts(20,7): error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. + Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. The type(s) '"baz"' are not present in the discriminator 'discriminator' of the target. + + +==== discriminatedUnionIncompatibleMemberMessage.ts (3 errors) ==== + // Test case for issue #62737: Show incompatible union members in discriminated union discriminator errors + + type Discriminated = + | { discriminator: "foo" } + | { discriminator: "bar" }; + + type UnionType = "foo" | "bar" | "baz"; + + const obj = { discriminator: "foo" as UnionType }; + + // This error should identify "baz" as the problematic type, not "foo" + const err: Discriminated = obj; + ~~~ +!!! error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. +!!! error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. The type(s) '"baz"' are not present in the discriminator 'discriminator' of the target. + + // Additional test case with multiple incompatible members + type UnionType2 = "foo" | "bar" | "baz" | "qux"; + const obj2 = { discriminator: "foo" as UnionType2 }; + const err2: Discriminated = obj2; + ~~~~ +!!! error TS2322: Type '{ discriminator: UnionType2; }' is not assignable to type 'Discriminated'. +!!! error TS2322: Type '{ discriminator: UnionType2; }' is not assignable to type 'Discriminated'. The type(s) '"baz" | "qux"' are not present in the discriminator 'discriminator' of the target. + + // Test case with expected error (expectedError reference) + const expectedError: Discriminated = { discriminator: "foo" } as { discriminator: UnionType }; + ~~~~~~~~~~~~~ +!!! error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. +!!! error TS2322: Type '{ discriminator: UnionType; }' is not assignable to type 'Discriminated'. The type(s) '"baz"' are not present in the discriminator 'discriminator' of the target. + + \ No newline at end of file diff --git a/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.js b/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.js new file mode 100644 index 0000000000000..a65c4b328d51f --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.js @@ -0,0 +1,36 @@ +//// [tests/cases/compiler/discriminatedUnionIncompatibleMemberMessage.ts] //// + +//// [discriminatedUnionIncompatibleMemberMessage.ts] +// Test case for issue #62737: Show incompatible union members in discriminated union discriminator errors + +type Discriminated = + | { discriminator: "foo" } + | { discriminator: "bar" }; + +type UnionType = "foo" | "bar" | "baz"; + +const obj = { discriminator: "foo" as UnionType }; + +// This error should identify "baz" as the problematic type, not "foo" +const err: Discriminated = obj; + +// Additional test case with multiple incompatible members +type UnionType2 = "foo" | "bar" | "baz" | "qux"; +const obj2 = { discriminator: "foo" as UnionType2 }; +const err2: Discriminated = obj2; + +// Test case with expected error (expectedError reference) +const expectedError: Discriminated = { discriminator: "foo" } as { discriminator: UnionType }; + + + +//// [discriminatedUnionIncompatibleMemberMessage.js] +"use strict"; +// Test case for issue #62737: Show incompatible union members in discriminated union discriminator errors +var obj = { discriminator: "foo" }; +// This error should identify "baz" as the problematic type, not "foo" +var err = obj; +var obj2 = { discriminator: "foo" }; +var err2 = obj2; +// Test case with expected error (expectedError reference) +var expectedError = { discriminator: "foo" }; diff --git a/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.symbols b/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.symbols new file mode 100644 index 0000000000000..9f2e440459ca7 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.symbols @@ -0,0 +1,51 @@ +//// [tests/cases/compiler/discriminatedUnionIncompatibleMemberMessage.ts] //// + +=== discriminatedUnionIncompatibleMemberMessage.ts === +// Test case for issue #62737: Show incompatible union members in discriminated union discriminator errors + +type Discriminated = +>Discriminated : Symbol(Discriminated, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 0, 0)) + + | { discriminator: "foo" } +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 3, 5)) + + | { discriminator: "bar" }; +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 4, 5)) + +type UnionType = "foo" | "bar" | "baz"; +>UnionType : Symbol(UnionType, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 4, 29)) + +const obj = { discriminator: "foo" as UnionType }; +>obj : Symbol(obj, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 8, 5)) +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 8, 13)) +>UnionType : Symbol(UnionType, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 4, 29)) + +// This error should identify "baz" as the problematic type, not "foo" +const err: Discriminated = obj; +>err : Symbol(err, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 11, 5)) +>Discriminated : Symbol(Discriminated, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 0, 0)) +>obj : Symbol(obj, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 8, 5)) + +// Additional test case with multiple incompatible members +type UnionType2 = "foo" | "bar" | "baz" | "qux"; +>UnionType2 : Symbol(UnionType2, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 11, 31)) + +const obj2 = { discriminator: "foo" as UnionType2 }; +>obj2 : Symbol(obj2, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 15, 5)) +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 15, 14)) +>UnionType2 : Symbol(UnionType2, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 11, 31)) + +const err2: Discriminated = obj2; +>err2 : Symbol(err2, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 16, 5)) +>Discriminated : Symbol(Discriminated, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 0, 0)) +>obj2 : Symbol(obj2, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 15, 5)) + +// Test case with expected error (expectedError reference) +const expectedError: Discriminated = { discriminator: "foo" } as { discriminator: UnionType }; +>expectedError : Symbol(expectedError, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 19, 5)) +>Discriminated : Symbol(Discriminated, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 0, 0)) +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 19, 38)) +>discriminator : Symbol(discriminator, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 19, 66)) +>UnionType : Symbol(UnionType, Decl(discriminatedUnionIncompatibleMemberMessage.ts, 4, 29)) + + diff --git a/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.types b/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.types new file mode 100644 index 0000000000000..d476e21411490 --- /dev/null +++ b/tests/baselines/reference/discriminatedUnionIncompatibleMemberMessage.types @@ -0,0 +1,79 @@ +//// [tests/cases/compiler/discriminatedUnionIncompatibleMemberMessage.ts] //// + +=== discriminatedUnionIncompatibleMemberMessage.ts === +// Test case for issue #62737: Show incompatible union members in discriminated union discriminator errors + +type Discriminated = +>Discriminated : Discriminated +> : ^^^^^^^^^^^^^ + + | { discriminator: "foo" } +>discriminator : "foo" +> : ^^^^^ + + | { discriminator: "bar" }; +>discriminator : "bar" +> : ^^^^^ + +type UnionType = "foo" | "bar" | "baz"; +>UnionType : UnionType +> : ^^^^^^^^^ + +const obj = { discriminator: "foo" as UnionType }; +>obj : { discriminator: UnionType; } +> : ^^^^^^^^^^^^^^^^^ ^^^ +>{ discriminator: "foo" as UnionType } : { discriminator: UnionType; } +> : ^^^^^^^^^^^^^^^^^ ^^^ +>discriminator : UnionType +> : ^^^^^^^^^ +>"foo" as UnionType : UnionType +> : ^^^^^^^^^ +>"foo" : "foo" +> : ^^^^^ + +// This error should identify "baz" as the problematic type, not "foo" +const err: Discriminated = obj; +>err : Discriminated +> : ^^^^^^^^^^^^^ +>obj : { discriminator: UnionType; } +> : ^^^^^^^^^^^^^^^^^ ^^^ + +// Additional test case with multiple incompatible members +type UnionType2 = "foo" | "bar" | "baz" | "qux"; +>UnionType2 : UnionType2 +> : ^^^^^^^^^^ + +const obj2 = { discriminator: "foo" as UnionType2 }; +>obj2 : { discriminator: UnionType2; } +> : ^^^^^^^^^^^^^^^^^ ^^^ +>{ discriminator: "foo" as UnionType2 } : { discriminator: UnionType2; } +> : ^^^^^^^^^^^^^^^^^ ^^^ +>discriminator : UnionType2 +> : ^^^^^^^^^^ +>"foo" as UnionType2 : UnionType2 +> : ^^^^^^^^^^ +>"foo" : "foo" +> : ^^^^^ + +const err2: Discriminated = obj2; +>err2 : Discriminated +> : ^^^^^^^^^^^^^ +>obj2 : { discriminator: UnionType2; } +> : ^^^^^^^^^^^^^^^^^ ^^^ + +// Test case with expected error (expectedError reference) +const expectedError: Discriminated = { discriminator: "foo" } as { discriminator: UnionType }; +>expectedError : Discriminated +> : ^^^^^^^^^^^^^ +>{ discriminator: "foo" } as { discriminator: UnionType } : { discriminator: UnionType; } +> : ^^^^^^^^^^^^^^^^^ ^^^ +>{ discriminator: "foo" } : { discriminator: "foo"; } +> : ^^^^^^^^^^^^^^^^^^^^^^^^^ +>discriminator : "foo" +> : ^^^^^ +>"foo" : "foo" +> : ^^^^^ +>discriminator : UnionType +> : ^^^^^^^^^ + + diff --git a/tests/cases/compiler/discriminatedUnionIncompatibleMemberErrorMessage.ts b/tests/cases/compiler/discriminatedUnionIncompatibleMemberErrorMessage.ts new file mode 100644 index 0000000000000..086b11652d79f --- /dev/null +++ b/tests/cases/compiler/discriminatedUnionIncompatibleMemberErrorMessage.ts @@ -0,0 +1,36 @@ +// @strict: true + +// Test case for issue #62737 +// Improve error messages when assigning objects with wider discriminator unions to discriminated union types + +// Basic case - single incompatible type +type Discriminated = + | { discriminator: "foo" } + | { discriminator: "bar" }; + +type UnionType = "foo" | "bar" | "baz"; + +const obj = { discriminator: "foo" as UnionType }; +const err: Discriminated = obj; // Error: "baz" is not present in discriminator + +// Multiple incompatible types +type WiderUnion = "foo" | "bar" | "baz" | "qux"; +const obj2 = { discriminator: "foo" as WiderUnion }; +const err2: Discriminated = obj2; // Error: "baz" | "qux" are not present in discriminator + +// Different discriminator property name +type Shape = + | { kind: "circle"; radius: number } + | { kind: "square"; side: number }; + +type ShapeKind = "circle" | "square" | "triangle"; +const shape = { kind: "circle" as ShapeKind }; +const err3: Shape = shape; // Error: "triangle" is not present in discriminator "kind" + +// From issue #62603 +type Discriminated2 = + | { discriminator: "foo" } + | { discriminator: "bar" }; + +const unexpectedError: Discriminated2 = { discriminator: "foo" } as { discriminator: UnionType }; + diff --git a/tests/cases/compiler/discriminatedUnionIncompatibleMemberMessage.ts b/tests/cases/compiler/discriminatedUnionIncompatibleMemberMessage.ts new file mode 100644 index 0000000000000..7dfc862928a3c --- /dev/null +++ b/tests/cases/compiler/discriminatedUnionIncompatibleMemberMessage.ts @@ -0,0 +1,23 @@ +// @strict: true + +// Test case for issue #62737: Show incompatible union members in discriminated union discriminator errors + +type Discriminated = + | { discriminator: "foo" } + | { discriminator: "bar" }; + +type UnionType = "foo" | "bar" | "baz"; + +const obj = { discriminator: "foo" as UnionType }; + +// This error should identify "baz" as the problematic type, not "foo" +const err: Discriminated = obj; + +// Additional test case with multiple incompatible members +type UnionType2 = "foo" | "bar" | "baz" | "qux"; +const obj2 = { discriminator: "foo" as UnionType2 }; +const err2: Discriminated = obj2; + +// Test case with expected error (expectedError reference) +const expectedError: Discriminated = { discriminator: "foo" } as { discriminator: UnionType }; +