mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-05 08:11:30 -06:00
Merge pull request #21285 from Microsoft/nested-excess-property-checking-for-discriminated-unions
Nested excess property checking for discriminated unions
This commit is contained in:
commit
b80081d0cb
@ -9225,7 +9225,8 @@ namespace ts {
|
||||
isSimpleTypeRelatedTo(source, target, relation, reportErrors ? reportError : undefined)) return Ternary.True;
|
||||
|
||||
if (isObjectLiteralType(source) && source.flags & TypeFlags.FreshLiteral) {
|
||||
if (hasExcessProperties(<FreshObjectLiteralType>source, target, reportErrors)) {
|
||||
const discriminantType = target.flags & TypeFlags.Union ? findMatchingDiscriminantType(source, target as UnionType) : undefined;
|
||||
if (hasExcessProperties(<FreshObjectLiteralType>source, target, discriminantType, reportErrors)) {
|
||||
if (reportErrors) {
|
||||
reportRelationError(headMessage, source, target);
|
||||
}
|
||||
@ -9235,7 +9236,7 @@ namespace ts {
|
||||
// and intersection types are further deconstructed on the target side, we don't want to
|
||||
// make the check again (as it might fail for a partial target type). Therefore we obtain
|
||||
// the regular source type and proceed with that.
|
||||
if (isUnionOrIntersectionTypeWithoutNullableConstituents(target)) {
|
||||
if (isUnionOrIntersectionTypeWithoutNullableConstituents(target) && !discriminantType) {
|
||||
source = getRegularTypeOfObjectLiteral(source);
|
||||
}
|
||||
}
|
||||
@ -9336,19 +9337,16 @@ namespace ts {
|
||||
return Ternary.False;
|
||||
}
|
||||
|
||||
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, reportErrors: boolean): boolean {
|
||||
function hasExcessProperties(source: FreshObjectLiteralType, target: Type, discriminant: Type | undefined, reportErrors: boolean): boolean {
|
||||
if (maybeTypeOfKind(target, TypeFlags.Object) && !(getObjectFlags(target) & ObjectFlags.ObjectLiteralPatternWithComputedProperties)) {
|
||||
const isComparingJsxAttributes = !!(source.flags & TypeFlags.JsxAttributes);
|
||||
if ((relation === assignableRelation || relation === comparableRelation) &&
|
||||
(isTypeSubsetOf(globalObjectType, target) || (!isComparingJsxAttributes && isEmptyObjectType(target)))) {
|
||||
return false;
|
||||
}
|
||||
if (target.flags & TypeFlags.Union) {
|
||||
const discriminantType = findMatchingDiscriminantType(source, target as UnionType);
|
||||
if (discriminantType) {
|
||||
// check excess properties against discriminant type only, not the entire union
|
||||
return hasExcessProperties(source, discriminantType, reportErrors);
|
||||
}
|
||||
if (discriminant) {
|
||||
// check excess properties against discriminant type only, not the entire union
|
||||
return hasExcessProperties(source, discriminant, /*discriminant*/ undefined, reportErrors);
|
||||
}
|
||||
for (const prop of getPropertiesOfObjectType(source)) {
|
||||
if (!isKnownProperty(target, prop.escapedName, isComparingJsxAttributes)) {
|
||||
|
||||
@ -21,9 +21,14 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(49,35): error TS2322: Type
|
||||
Object literal may only specify known properties, and 'second' does not exist in type '{ a: 1; b: 1; first: string; }'.
|
||||
tests/cases/compiler/excessPropertyCheckWithUnions.ts(50,35): error TS2322: Type '{ a: 1; b: 1; first: string; third: string; }' is not assignable to type 'Overlapping'.
|
||||
Object literal may only specify known properties, and 'third' does not exist in type '{ a: 1; b: 1; first: string; }'.
|
||||
tests/cases/compiler/excessPropertyCheckWithUnions.ts(66,9): error TS2322: Type '{ kind: "A"; n: { a: string; b: string; }; }' is not assignable to type 'AB'.
|
||||
Type '{ kind: "A"; n: { a: string; b: string; }; }' is not assignable to type '{ kind: "A"; n: AN; }'.
|
||||
Types of property 'n' are incompatible.
|
||||
Type '{ a: string; b: string; }' is not assignable to type 'AN'.
|
||||
Object literal may only specify known properties, and 'b' does not exist in type 'AN'.
|
||||
|
||||
|
||||
==== tests/cases/compiler/excessPropertyCheckWithUnions.ts (9 errors) ====
|
||||
==== tests/cases/compiler/excessPropertyCheckWithUnions.ts (10 errors) ====
|
||||
type ADT = {
|
||||
tag: "A",
|
||||
a1: string
|
||||
@ -112,4 +117,29 @@ tests/cases/compiler/excessPropertyCheckWithUnions.ts(50,35): error TS2322: Type
|
||||
declare let t1: { a: any, b: any, c: any } | { c: any, d: any, e: any }
|
||||
let t2 = { ...t1 }
|
||||
t0 = t2
|
||||
|
||||
// Nested excess property checks work with discriminated unions
|
||||
type AN = { a: string } | { c: string }
|
||||
type BN = { b: string }
|
||||
type AB = { kind: "A", n: AN } | { kind: "B", n: BN }
|
||||
const abab: AB = {
|
||||
kind: "A",
|
||||
n: {
|
||||
a: "a",
|
||||
b: "b", // excess -- kind: "A"
|
||||
~~~~~~
|
||||
!!! error TS2322: Type '{ kind: "A"; n: { a: string; b: string; }; }' is not assignable to type 'AB'.
|
||||
!!! error TS2322: Type '{ kind: "A"; n: { a: string; b: string; }; }' is not assignable to type '{ kind: "A"; n: AN; }'.
|
||||
!!! error TS2322: Types of property 'n' are incompatible.
|
||||
!!! error TS2322: Type '{ a: string; b: string; }' is not assignable to type 'AN'.
|
||||
!!! error TS2322: Object literal may only specify known properties, and 'b' does not exist in type 'AN'.
|
||||
}
|
||||
}
|
||||
const abac: AB = {
|
||||
kind: "A",
|
||||
n: {
|
||||
a: "a",
|
||||
c: "c", // ok -- kind: "A", an: { a: string } | { c: string }
|
||||
}
|
||||
}
|
||||
|
||||
@ -55,6 +55,25 @@ declare let t0: { a: any, b: any } | { d: any, e: any }
|
||||
declare let t1: { a: any, b: any, c: any } | { c: any, d: any, e: any }
|
||||
let t2 = { ...t1 }
|
||||
t0 = t2
|
||||
|
||||
// Nested excess property checks work with discriminated unions
|
||||
type AN = { a: string } | { c: string }
|
||||
type BN = { b: string }
|
||||
type AB = { kind: "A", n: AN } | { kind: "B", n: BN }
|
||||
const abab: AB = {
|
||||
kind: "A",
|
||||
n: {
|
||||
a: "a",
|
||||
b: "b", // excess -- kind: "A"
|
||||
}
|
||||
}
|
||||
const abac: AB = {
|
||||
kind: "A",
|
||||
n: {
|
||||
a: "a",
|
||||
c: "c", // ok -- kind: "A", an: { a: string } | { c: string }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//// [excessPropertyCheckWithUnions.js]
|
||||
@ -89,3 +108,17 @@ over = { a: 1, b: 1, first: "ok", second: "error" };
|
||||
over = { a: 1, b: 1, first: "ok", third: "error" };
|
||||
var t2 = __assign({}, t1);
|
||||
t0 = t2;
|
||||
var abab = {
|
||||
kind: "A",
|
||||
n: {
|
||||
a: "a",
|
||||
b: "b"
|
||||
}
|
||||
};
|
||||
var abac = {
|
||||
kind: "A",
|
||||
n: {
|
||||
a: "a",
|
||||
c: "c"
|
||||
}
|
||||
};
|
||||
|
||||
@ -167,3 +167,57 @@ t0 = t2
|
||||
>t0 : Symbol(t0, Decl(excessPropertyCheckWithUnions.ts, 52, 11))
|
||||
>t2 : Symbol(t2, Decl(excessPropertyCheckWithUnions.ts, 54, 3))
|
||||
|
||||
// Nested excess property checks work with discriminated unions
|
||||
type AN = { a: string } | { c: string }
|
||||
>AN : Symbol(AN, Decl(excessPropertyCheckWithUnions.ts, 55, 7))
|
||||
>a : Symbol(a, Decl(excessPropertyCheckWithUnions.ts, 58, 11))
|
||||
>c : Symbol(c, Decl(excessPropertyCheckWithUnions.ts, 58, 27))
|
||||
|
||||
type BN = { b: string }
|
||||
>BN : Symbol(BN, Decl(excessPropertyCheckWithUnions.ts, 58, 39))
|
||||
>b : Symbol(b, Decl(excessPropertyCheckWithUnions.ts, 59, 11))
|
||||
|
||||
type AB = { kind: "A", n: AN } | { kind: "B", n: BN }
|
||||
>AB : Symbol(AB, Decl(excessPropertyCheckWithUnions.ts, 59, 23))
|
||||
>kind : Symbol(kind, Decl(excessPropertyCheckWithUnions.ts, 60, 11))
|
||||
>n : Symbol(n, Decl(excessPropertyCheckWithUnions.ts, 60, 22))
|
||||
>AN : Symbol(AN, Decl(excessPropertyCheckWithUnions.ts, 55, 7))
|
||||
>kind : Symbol(kind, Decl(excessPropertyCheckWithUnions.ts, 60, 34))
|
||||
>n : Symbol(n, Decl(excessPropertyCheckWithUnions.ts, 60, 45))
|
||||
>BN : Symbol(BN, Decl(excessPropertyCheckWithUnions.ts, 58, 39))
|
||||
|
||||
const abab: AB = {
|
||||
>abab : Symbol(abab, Decl(excessPropertyCheckWithUnions.ts, 61, 5))
|
||||
>AB : Symbol(AB, Decl(excessPropertyCheckWithUnions.ts, 59, 23))
|
||||
|
||||
kind: "A",
|
||||
>kind : Symbol(kind, Decl(excessPropertyCheckWithUnions.ts, 61, 18))
|
||||
|
||||
n: {
|
||||
>n : Symbol(n, Decl(excessPropertyCheckWithUnions.ts, 62, 14))
|
||||
|
||||
a: "a",
|
||||
>a : Symbol(a, Decl(excessPropertyCheckWithUnions.ts, 63, 8))
|
||||
|
||||
b: "b", // excess -- kind: "A"
|
||||
>b : Symbol(b, Decl(excessPropertyCheckWithUnions.ts, 64, 15))
|
||||
}
|
||||
}
|
||||
const abac: AB = {
|
||||
>abac : Symbol(abac, Decl(excessPropertyCheckWithUnions.ts, 68, 5))
|
||||
>AB : Symbol(AB, Decl(excessPropertyCheckWithUnions.ts, 59, 23))
|
||||
|
||||
kind: "A",
|
||||
>kind : Symbol(kind, Decl(excessPropertyCheckWithUnions.ts, 68, 18))
|
||||
|
||||
n: {
|
||||
>n : Symbol(n, Decl(excessPropertyCheckWithUnions.ts, 69, 14))
|
||||
|
||||
a: "a",
|
||||
>a : Symbol(a, Decl(excessPropertyCheckWithUnions.ts, 70, 8))
|
||||
|
||||
c: "c", // ok -- kind: "A", an: { a: string } | { c: string }
|
||||
>c : Symbol(c, Decl(excessPropertyCheckWithUnions.ts, 71, 15))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -221,3 +221,67 @@ t0 = t2
|
||||
>t0 : { a: any; b: any; } | { d: any; e: any; }
|
||||
>t2 : { a: any; b: any; c: any; } | { c: any; d: any; e: any; }
|
||||
|
||||
// Nested excess property checks work with discriminated unions
|
||||
type AN = { a: string } | { c: string }
|
||||
>AN : AN
|
||||
>a : string
|
||||
>c : string
|
||||
|
||||
type BN = { b: string }
|
||||
>BN : BN
|
||||
>b : string
|
||||
|
||||
type AB = { kind: "A", n: AN } | { kind: "B", n: BN }
|
||||
>AB : AB
|
||||
>kind : "A"
|
||||
>n : AN
|
||||
>AN : AN
|
||||
>kind : "B"
|
||||
>n : BN
|
||||
>BN : BN
|
||||
|
||||
const abab: AB = {
|
||||
>abab : AB
|
||||
>AB : AB
|
||||
>{ kind: "A", n: { a: "a", b: "b", // excess -- kind: "A" }} : { kind: "A"; n: { a: string; b: string; }; }
|
||||
|
||||
kind: "A",
|
||||
>kind : "A"
|
||||
>"A" : "A"
|
||||
|
||||
n: {
|
||||
>n : { a: string; b: string; }
|
||||
>{ a: "a", b: "b", // excess -- kind: "A" } : { a: string; b: string; }
|
||||
|
||||
a: "a",
|
||||
>a : string
|
||||
>"a" : "a"
|
||||
|
||||
b: "b", // excess -- kind: "A"
|
||||
>b : string
|
||||
>"b" : "b"
|
||||
}
|
||||
}
|
||||
const abac: AB = {
|
||||
>abac : AB
|
||||
>AB : AB
|
||||
>{ kind: "A", n: { a: "a", c: "c", // ok -- kind: "A", an: { a: string } | { c: string } }} : { kind: "A"; n: { a: string; c: string; }; }
|
||||
|
||||
kind: "A",
|
||||
>kind : "A"
|
||||
>"A" : "A"
|
||||
|
||||
n: {
|
||||
>n : { a: string; c: string; }
|
||||
>{ a: "a", c: "c", // ok -- kind: "A", an: { a: string } | { c: string } } : { a: string; c: string; }
|
||||
|
||||
a: "a",
|
||||
>a : string
|
||||
>"a" : "a"
|
||||
|
||||
c: "c", // ok -- kind: "A", an: { a: string } | { c: string }
|
||||
>c : string
|
||||
>"c" : "c"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -55,3 +55,22 @@ declare let t0: { a: any, b: any } | { d: any, e: any }
|
||||
declare let t1: { a: any, b: any, c: any } | { c: any, d: any, e: any }
|
||||
let t2 = { ...t1 }
|
||||
t0 = t2
|
||||
|
||||
// Nested excess property checks work with discriminated unions
|
||||
type AN = { a: string } | { c: string }
|
||||
type BN = { b: string }
|
||||
type AB = { kind: "A", n: AN } | { kind: "B", n: BN }
|
||||
const abab: AB = {
|
||||
kind: "A",
|
||||
n: {
|
||||
a: "a",
|
||||
b: "b", // excess -- kind: "A"
|
||||
}
|
||||
}
|
||||
const abac: AB = {
|
||||
kind: "A",
|
||||
n: {
|
||||
a: "a",
|
||||
c: "c", // ok -- kind: "A", an: { a: string } | { c: string }
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user