Skip to content

Commit 74c6bc1

Browse files
authored
Filter primitives from union when checking for mismatched excess props if nonprimitive type is present (#31708)
* Filter primitives from union when checking for mismatched excess props if nonprimitive type is present * Use maybeTypeOfKind
1 parent a0d164f commit 74c6bc1

6 files changed

+130
-2
lines changed

src/compiler/checker.ts

+12-2
Original file line numberDiff line numberDiff line change
@@ -12600,8 +12600,8 @@ namespace ts {
1260012600
result = typeRelatedToSomeType(getRegularTypeOfObjectLiteral(source), <UnionType>target, reportErrors && !(source.flags & TypeFlags.Primitive) && !(target.flags & TypeFlags.Primitive));
1260112601
if (result && isPerformingExcessPropertyChecks) {
1260212602
// Validate against excess props using the original `source`
12603-
const discriminantType = target.flags & TypeFlags.Union ? findMatchingDiscriminantType(source, target as UnionType) : undefined;
12604-
if (!propertiesRelatedTo(source, discriminantType || target, reportErrors, /*excludedProperties*/ undefined)) {
12603+
const discriminantType = findMatchingDiscriminantType(source, target as UnionType) || filterPrimitivesIfContainsNonPrimitive(target as UnionType);
12604+
if (!propertiesRelatedTo(source, discriminantType, reportErrors, /*excludedProperties*/ undefined)) {
1260512605
return Ternary.False;
1260612606
}
1260712607
}
@@ -12872,6 +12872,16 @@ namespace ts {
1287212872
return bestMatch;
1287312873
}
1287412874

12875+
function filterPrimitivesIfContainsNonPrimitive(type: UnionType) {
12876+
if (maybeTypeOfKind(type, TypeFlags.NonPrimitive)) {
12877+
const result = filterType(type, t => !(t.flags & TypeFlags.Primitive));
12878+
if (!(result.flags & TypeFlags.Never)) {
12879+
return result;
12880+
}
12881+
}
12882+
return type;
12883+
}
12884+
1287512885
// Keep this up-to-date with the same logic within `getApparentTypeOfContextualType`, since they should behave similarly
1287612886
function findMatchingDiscriminantType(source: Type, target: Type) {
1287712887
if (target.flags & TypeFlags.Union && source.flags & (TypeFlags.Intersection | TypeFlags.Object)) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
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; }'.
2+
3+
4+
==== tests/cases/compiler/discriminateObjectTypesOnly.ts (1 errors) ====
5+
type Thing = number | object;
6+
const k: Thing = { toFixed: null }; // OK, satisfies object
7+
8+
type Thing2 = number | { toFixed: null } | object;
9+
const q: Thing2 = { toFixed: null };
10+
const h: Thing2 = { toString: null }; // OK, satisfies object
11+
12+
type Thing3 = number | { toFixed: null, toString: undefined } | object;
13+
const l: Thing3 = { toString: undefined }; // error, toFixed isn't null
14+
~
15+
!!! error TS2741: Property 'toFixed' is missing in type '{ toString: undefined; }' but required in type '{ toFixed: null; toString: undefined; }'.
16+
!!! related TS2728 tests/cases/compiler/discriminateObjectTypesOnly.ts:8:26: 'toFixed' is declared here.
17+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
//// [discriminateObjectTypesOnly.ts]
2+
type Thing = number | object;
3+
const k: Thing = { toFixed: null }; // OK, satisfies object
4+
5+
type Thing2 = number | { toFixed: null } | object;
6+
const q: Thing2 = { toFixed: null };
7+
const h: Thing2 = { toString: null }; // OK, satisfies object
8+
9+
type Thing3 = number | { toFixed: null, toString: undefined } | object;
10+
const l: Thing3 = { toString: undefined }; // error, toFixed isn't null
11+
12+
13+
//// [discriminateObjectTypesOnly.js]
14+
"use strict";
15+
var k = { toFixed: null }; // OK, satisfies object
16+
var q = { toFixed: null };
17+
var h = { toString: null }; // OK, satisfies object
18+
var l = { toString: undefined }; // error, toFixed isn't null
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
=== tests/cases/compiler/discriminateObjectTypesOnly.ts ===
2+
type Thing = number | object;
3+
>Thing : Symbol(Thing, Decl(discriminateObjectTypesOnly.ts, 0, 0))
4+
5+
const k: Thing = { toFixed: null }; // OK, satisfies object
6+
>k : Symbol(k, Decl(discriminateObjectTypesOnly.ts, 1, 5))
7+
>Thing : Symbol(Thing, Decl(discriminateObjectTypesOnly.ts, 0, 0))
8+
>toFixed : Symbol(toFixed, Decl(discriminateObjectTypesOnly.ts, 1, 18))
9+
10+
type Thing2 = number | { toFixed: null } | object;
11+
>Thing2 : Symbol(Thing2, Decl(discriminateObjectTypesOnly.ts, 1, 35))
12+
>toFixed : Symbol(toFixed, Decl(discriminateObjectTypesOnly.ts, 3, 24))
13+
14+
const q: Thing2 = { toFixed: null };
15+
>q : Symbol(q, Decl(discriminateObjectTypesOnly.ts, 4, 5))
16+
>Thing2 : Symbol(Thing2, Decl(discriminateObjectTypesOnly.ts, 1, 35))
17+
>toFixed : Symbol(toFixed, Decl(discriminateObjectTypesOnly.ts, 4, 19))
18+
19+
const h: Thing2 = { toString: null }; // OK, satisfies object
20+
>h : Symbol(h, Decl(discriminateObjectTypesOnly.ts, 5, 5))
21+
>Thing2 : Symbol(Thing2, Decl(discriminateObjectTypesOnly.ts, 1, 35))
22+
>toString : Symbol(toString, Decl(discriminateObjectTypesOnly.ts, 5, 19))
23+
24+
type Thing3 = number | { toFixed: null, toString: undefined } | object;
25+
>Thing3 : Symbol(Thing3, Decl(discriminateObjectTypesOnly.ts, 5, 37))
26+
>toFixed : Symbol(toFixed, Decl(discriminateObjectTypesOnly.ts, 7, 24))
27+
>toString : Symbol(toString, Decl(discriminateObjectTypesOnly.ts, 7, 39))
28+
29+
const l: Thing3 = { toString: undefined }; // error, toFixed isn't null
30+
>l : Symbol(l, Decl(discriminateObjectTypesOnly.ts, 8, 5))
31+
>Thing3 : Symbol(Thing3, Decl(discriminateObjectTypesOnly.ts, 5, 37))
32+
>toString : Symbol(toString, Decl(discriminateObjectTypesOnly.ts, 8, 19))
33+
>undefined : Symbol(undefined)
34+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
=== tests/cases/compiler/discriminateObjectTypesOnly.ts ===
2+
type Thing = number | object;
3+
>Thing : Thing
4+
5+
const k: Thing = { toFixed: null }; // OK, satisfies object
6+
>k : Thing
7+
>{ toFixed: null } : { toFixed: null; }
8+
>toFixed : null
9+
>null : null
10+
11+
type Thing2 = number | { toFixed: null } | object;
12+
>Thing2 : Thing2
13+
>toFixed : null
14+
>null : null
15+
16+
const q: Thing2 = { toFixed: null };
17+
>q : Thing2
18+
>{ toFixed: null } : { toFixed: null; }
19+
>toFixed : null
20+
>null : null
21+
22+
const h: Thing2 = { toString: null }; // OK, satisfies object
23+
>h : Thing2
24+
>{ toString: null } : { toString: null; }
25+
>toString : null
26+
>null : null
27+
28+
type Thing3 = number | { toFixed: null, toString: undefined } | object;
29+
>Thing3 : Thing3
30+
>toFixed : null
31+
>null : null
32+
>toString : undefined
33+
34+
const l: Thing3 = { toString: undefined }; // error, toFixed isn't null
35+
>l : Thing3
36+
>{ toString: undefined } : { toString: undefined; }
37+
>toString : undefined
38+
>undefined : undefined
39+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
// @strict: true
2+
type Thing = number | object;
3+
const k: Thing = { toFixed: null }; // OK, satisfies object
4+
5+
type Thing2 = number | { toFixed: null } | object;
6+
const q: Thing2 = { toFixed: null };
7+
const h: Thing2 = { toString: null }; // OK, satisfies object
8+
9+
type Thing3 = number | { toFixed: null, toString: undefined } | object;
10+
const l: Thing3 = { toString: undefined }; // error, toFixed isn't null

0 commit comments

Comments
 (0)