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:
Nathan Shively-Sanders 2018-01-25 15:59:57 -08:00 committed by GitHub
commit b80081d0cb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 208 additions and 10 deletions

View File

@ -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)) {

View File

@ -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 }
}
}

View File

@ -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"
}
};

View File

@ -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))
}
}

View File

@ -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"
}
}

View File

@ -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 }
}
}