diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 2104fffe49a08..a4ad3af7d9a35 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12600,8 +12600,8 @@ namespace ts { result = typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive)); if (result && isPerformingExcessPropertyChecks) { // Validate against excess props using the original `source` - const discriminantType = target.flags & TypeFlags.Union ? findMatchingDiscriminantType(source, target as UnionType) : undefined; - if (!propertiesRelatedTo(source, discriminantType || target, reportErrors, /*excludedProperties*/ undefined)) { + const discriminantType = findMatchingDiscriminantType(source, target as UnionType) || filterPrimitivesIfContainsNonPrimitive(target as UnionType); + if (!propertiesRelatedTo(source, discriminantType, reportErrors, /*excludedProperties*/ undefined)) { return Ternary.False; } } @@ -12872,6 +12872,16 @@ namespace ts { return bestMatch; } + function filterPrimitivesIfContainsNonPrimitive(type: UnionType) { + if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) { + const result = filterType(type, t => !(t.flags & TypeFlags.Primitive)); + if (!(result.flags & TypeFlags.Never)) { + return result; + } + } + return type; + } + // Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly function findMatchingDiscriminantType(source: Type, target: Type) { if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) { diff --git a/tests/baselines/reference/discriminateObjectTypesOnly.errors.txt b/tests/baselines/reference/discriminateObjectTypesOnly.errors.txt new file mode 100644 index 0000000000000..efe5bf4ccedb2 --- /dev/null +++ b/tests/baselines/reference/discriminateObjectTypesOnly.errors.txt @@ -0,0 +1,17 @@ +tests/cases/compiler/discriminateObjectTypesOnly.ts(9,7): error TS2741: Property 'toFixed' is missing in type '{ toString: undefined; }' but required in type '{ toFixed: null; toString: undefined; }'. + + +==== tests/cases/compiler/discriminateObjectTypesOnly.ts (1 errors) ==== + type Thing = number | object; + const k: Thing = { toFixed: null }; // OK, satisfies object + + type Thing2 = number | { toFixed: null } | object; + const q: Thing2 = { toFixed: null }; + const h: Thing2 = { toString: null }; // OK, satisfies object + + type Thing3 = number | { toFixed: null, toString: undefined } | object; + const l: Thing3 = { toString: undefined }; // error, toFixed isn't null + ~ +!!! error TS2741: Property 'toFixed' is missing in type '{ toString: undefined; }' but required in type '{ toFixed: null; toString: undefined; }'. +!!! related TS2728 tests/cases/compiler/discriminateObjectTypesOnly.ts:8:26: 'toFixed' is declared here. + \ No newline at end of file diff --git a/tests/baselines/reference/discriminateObjectTypesOnly.js b/tests/baselines/reference/discriminateObjectTypesOnly.js new file mode 100644 index 0000000000000..9c91918444db7 --- /dev/null +++ b/tests/baselines/reference/discriminateObjectTypesOnly.js @@ -0,0 +1,18 @@ +//// [discriminateObjectTypesOnly.ts] +type Thing = number | object; +const k: Thing = { toFixed: null }; // OK, satisfies object + +type Thing2 = number | { toFixed: null } | object; +const q: Thing2 = { toFixed: null }; +const h: Thing2 = { toString: null }; // OK, satisfies object + +type Thing3 = number | { toFixed: null, toString: undefined } | object; +const l: Thing3 = { toString: undefined }; // error, toFixed isn't null + + +//// [discriminateObjectTypesOnly.js] +"use strict"; +var k = { toFixed: null }; // OK, satisfies object +var q = { toFixed: null }; +var h = { toString: null }; // OK, satisfies object +var l = { toString: undefined }; // error, toFixed isn't null diff --git a/tests/baselines/reference/discriminateObjectTypesOnly.symbols b/tests/baselines/reference/discriminateObjectTypesOnly.symbols new file mode 100644 index 0000000000000..c240a8b629066 --- /dev/null +++ b/tests/baselines/reference/discriminateObjectTypesOnly.symbols @@ -0,0 +1,34 @@ +=== tests/cases/compiler/discriminateObjectTypesOnly.ts === +type Thing = number | object; +>Thing : Symbol(Thing, Decl(discriminateObjectTypesOnly.ts, 0, 0)) + +const k: Thing = { toFixed: null }; // OK, satisfies object +>k : Symbol(k, Decl(discriminateObjectTypesOnly.ts, 1, 5)) +>Thing : Symbol(Thing, Decl(discriminateObjectTypesOnly.ts, 0, 0)) +>toFixed : Symbol(toFixed, Decl(discriminateObjectTypesOnly.ts, 1, 18)) + +type Thing2 = number | { toFixed: null } | object; +>Thing2 : Symbol(Thing2, Decl(discriminateObjectTypesOnly.ts, 1, 35)) +>toFixed : Symbol(toFixed, Decl(discriminateObjectTypesOnly.ts, 3, 24)) + +const q: Thing2 = { toFixed: null }; +>q : Symbol(q, Decl(discriminateObjectTypesOnly.ts, 4, 5)) +>Thing2 : Symbol(Thing2, Decl(discriminateObjectTypesOnly.ts, 1, 35)) +>toFixed : Symbol(toFixed, Decl(discriminateObjectTypesOnly.ts, 4, 19)) + +const h: Thing2 = { toString: null }; // OK, satisfies object +>h : Symbol(h, Decl(discriminateObjectTypesOnly.ts, 5, 5)) +>Thing2 : Symbol(Thing2, Decl(discriminateObjectTypesOnly.ts, 1, 35)) +>toString : Symbol(toString, Decl(discriminateObjectTypesOnly.ts, 5, 19)) + +type Thing3 = number | { toFixed: null, toString: undefined } | object; +>Thing3 : Symbol(Thing3, Decl(discriminateObjectTypesOnly.ts, 5, 37)) +>toFixed : Symbol(toFixed, Decl(discriminateObjectTypesOnly.ts, 7, 24)) +>toString : Symbol(toString, Decl(discriminateObjectTypesOnly.ts, 7, 39)) + +const l: Thing3 = { toString: undefined }; // error, toFixed isn't null +>l : Symbol(l, Decl(discriminateObjectTypesOnly.ts, 8, 5)) +>Thing3 : Symbol(Thing3, Decl(discriminateObjectTypesOnly.ts, 5, 37)) +>toString : Symbol(toString, Decl(discriminateObjectTypesOnly.ts, 8, 19)) +>undefined : Symbol(undefined) + diff --git a/tests/baselines/reference/discriminateObjectTypesOnly.types b/tests/baselines/reference/discriminateObjectTypesOnly.types new file mode 100644 index 0000000000000..27b686b4a67f3 --- /dev/null +++ b/tests/baselines/reference/discriminateObjectTypesOnly.types @@ -0,0 +1,39 @@ +=== tests/cases/compiler/discriminateObjectTypesOnly.ts === +type Thing = number | object; +>Thing : Thing + +const k: Thing = { toFixed: null }; // OK, satisfies object +>k : Thing +>{ toFixed: null } : { toFixed: null; } +>toFixed : null +>null : null + +type Thing2 = number | { toFixed: null } | object; +>Thing2 : Thing2 +>toFixed : null +>null : null + +const q: Thing2 = { toFixed: null }; +>q : Thing2 +>{ toFixed: null } : { toFixed: null; } +>toFixed : null +>null : null + +const h: Thing2 = { toString: null }; // OK, satisfies object +>h : Thing2 +>{ toString: null } : { toString: null; } +>toString : null +>null : null + +type Thing3 = number | { toFixed: null, toString: undefined } | object; +>Thing3 : Thing3 +>toFixed : null +>null : null +>toString : undefined + +const l: Thing3 = { toString: undefined }; // error, toFixed isn't null +>l : Thing3 +>{ toString: undefined } : { toString: undefined; } +>toString : undefined +>undefined : undefined + diff --git a/tests/cases/compiler/discriminateObjectTypesOnly.ts b/tests/cases/compiler/discriminateObjectTypesOnly.ts new file mode 100644 index 0000000000000..f6ce06c5de4cc --- /dev/null +++ b/tests/cases/compiler/discriminateObjectTypesOnly.ts @@ -0,0 +1,10 @@ +// @strict: true +type Thing = number | object; +const k: Thing = { toFixed: null }; // OK, satisfies object + +type Thing2 = number | { toFixed: null } | object; +const q: Thing2 = { toFixed: null }; +const h: Thing2 = { toString: null }; // OK, satisfies object + +type Thing3 = number | { toFixed: null, toString: undefined } | object; +const l: Thing3 = { toString: undefined }; // error, toFixed isn't null