diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 04628ed4c1d..3271fada423 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -16875,20 +16875,24 @@ namespace ts { } function getBestMatchingType(source: Type, target: UnionOrIntersectionType, isRelatedTo = compareTypesAssignable) { - return findMatchingDiscriminantType(source, target, isRelatedTo) || + return findMatchingDiscriminantType(source, target, isRelatedTo, /*skipPartial*/ true) || findMatchingTypeReferenceOrTypeAliasReference(source, target) || findBestTypeForObjectLiteral(source, target) || findBestTypeForInvokable(source, target) || findMostOverlappyType(source, target); } - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary): Type | undefined; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type): Type; - function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type) { + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: undefined, skipPartial?: boolean): Type | undefined; + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue: Type, skipPartial?: boolean): Type; + function discriminateTypeByDiscriminableItems(target: UnionType, discriminators: [() => Type, __String][], related: (source: Type, target: Type) => boolean | Ternary, defaultValue?: Type, skipPartial?: boolean) { // undefined=unknown, true=discriminated, false=not discriminated // The state of each type progresses from left to right. Discriminated types stop at 'true'. const discriminable = target.types.map(_ => undefined) as (boolean | undefined)[]; for (const [getDiscriminatingType, propertyName] of discriminators) { + const targetProp = getUnionOrIntersectionProperty(target, propertyName); + if (skipPartial && targetProp && getCheckFlags(targetProp) & CheckFlags.ReadPartial) { + continue; + } let i = 0; for (const type of target.types) { const targetType = getTypeOfPropertyOfType(type, propertyName); @@ -37787,13 +37791,13 @@ namespace ts { } // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly - function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary) { + function findMatchingDiscriminantType(source: Type, target: Type, isRelatedTo: (source: Type, target: Type) => Ternary, skipPartial?: boolean) { if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { const sourceProperties = getPropertiesOfType(source); if (sourceProperties) { const sourcePropertiesFiltered = findDiscriminantProperties(sourceProperties, target); if (sourcePropertiesFiltered) { - return discriminateTypeByDiscriminableItems(target, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo); + return discriminateTypeByDiscriminableItems(target, map(sourcePropertiesFiltered, p => ([() => getTypeOfSymbol(p), p.escapedName] as [() => Type, __String])), isRelatedTo, /*defaultValue*/ undefined, skipPartial); } } } diff --git a/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.errors.txt b/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.errors.txt new file mode 100644 index 00000000000..6a80ea2132f --- /dev/null +++ b/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.errors.txt @@ -0,0 +1,28 @@ +tests/cases/compiler/unionErrorMessageOnMatchingDiscriminant.ts(21,5): error TS2322: Type 'null' is not assignable to type '{ a: string; }'. + + +==== tests/cases/compiler/unionErrorMessageOnMatchingDiscriminant.ts (1 errors) ==== + type A = { + type: 'a', + data: { a: string } + }; + + type B = { + type: 'b', + data: null + }; + + type C = { + type: 'c', + payload: string + }; + + type Union = A | B | C; + + // error + const foo: Union = { + type: 'a', + data: null + ~~~~ +!!! error TS2322: Type 'null' is not assignable to type '{ a: string; }'. + }; \ No newline at end of file diff --git a/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.js b/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.js new file mode 100644 index 00000000000..e3402bdb35a --- /dev/null +++ b/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.js @@ -0,0 +1,31 @@ +//// [unionErrorMessageOnMatchingDiscriminant.ts] +type A = { + type: 'a', + data: { a: string } +}; + +type B = { + type: 'b', + data: null +}; + +type C = { + type: 'c', + payload: string +}; + +type Union = A | B | C; + +// error +const foo: Union = { + type: 'a', + data: null +}; + +//// [unionErrorMessageOnMatchingDiscriminant.js] +"use strict"; +// error +var foo = { + type: 'a', + data: null +}; diff --git a/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.symbols b/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.symbols new file mode 100644 index 00000000000..2ad830dd2dd --- /dev/null +++ b/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.symbols @@ -0,0 +1,53 @@ +=== tests/cases/compiler/unionErrorMessageOnMatchingDiscriminant.ts === +type A = { +>A : Symbol(A, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 0, 0)) + + type: 'a', +>type : Symbol(type, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 0, 10)) + + data: { a: string } +>data : Symbol(data, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 1, 14)) +>a : Symbol(a, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 2, 11)) + +}; + +type B = { +>B : Symbol(B, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 3, 2)) + + type: 'b', +>type : Symbol(type, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 5, 10)) + + data: null +>data : Symbol(data, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 6, 14)) + +}; + +type C = { +>C : Symbol(C, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 8, 2)) + + type: 'c', +>type : Symbol(type, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 10, 10)) + + payload: string +>payload : Symbol(payload, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 11, 14)) + +}; + +type Union = A | B | C; +>Union : Symbol(Union, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 13, 2)) +>A : Symbol(A, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 0, 0)) +>B : Symbol(B, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 3, 2)) +>C : Symbol(C, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 8, 2)) + +// error +const foo: Union = { +>foo : Symbol(foo, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 18, 5)) +>Union : Symbol(Union, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 13, 2)) + + type: 'a', +>type : Symbol(type, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 18, 20)) + + data: null +>data : Symbol(data, Decl(unionErrorMessageOnMatchingDiscriminant.ts, 19, 14)) + +}; diff --git a/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.types b/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.types new file mode 100644 index 00000000000..4a02a5a5b88 --- /dev/null +++ b/tests/baselines/reference/unionErrorMessageOnMatchingDiscriminant.types @@ -0,0 +1,53 @@ +=== tests/cases/compiler/unionErrorMessageOnMatchingDiscriminant.ts === +type A = { +>A : A + + type: 'a', +>type : "a" + + data: { a: string } +>data : { a: string; } +>a : string + +}; + +type B = { +>B : B + + type: 'b', +>type : "b" + + data: null +>data : null +>null : null + +}; + +type C = { +>C : C + + type: 'c', +>type : "c" + + payload: string +>payload : string + +}; + +type Union = A | B | C; +>Union : Union + +// error +const foo: Union = { +>foo : Union +>{ type: 'a', data: null} : { type: "a"; data: null; } + + type: 'a', +>type : "a" +>'a' : "a" + + data: null +>data : null +>null : null + +}; diff --git a/tests/cases/compiler/unionErrorMessageOnMatchingDiscriminant.ts b/tests/cases/compiler/unionErrorMessageOnMatchingDiscriminant.ts new file mode 100644 index 00000000000..967d6d26144 --- /dev/null +++ b/tests/cases/compiler/unionErrorMessageOnMatchingDiscriminant.ts @@ -0,0 +1,23 @@ +// @strict: true +type A = { + type: 'a', + data: { a: string } +}; + +type B = { + type: 'b', + data: null +}; + +type C = { + type: 'c', + payload: string +}; + +type Union = A | B | C; + +// error +const foo: Union = { + type: 'a', + data: null +}; \ No newline at end of file