diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 3fe96ca7ac45c..c4d9ce9e89c78 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12197,7 +12197,7 @@ namespace ts { } } - function isUnionOrIntersectionTypeWithoutNullableConstituents(type: Type): boolean { + function shouldTargetTypeCheckPropertiesForExcessMembers(type: Type): boolean { if (!(type.flags & TypeFlags.UnionOrIntersection)) { return false; } @@ -12205,7 +12205,7 @@ namespace ts { // check if we still will have compound type if we ignore nullable components. let seenNonNullable = false; for (const t of (type).types) { - if (t.flags & TypeFlags.Nullable) { + if (t.flags & TypeFlags.Nullable || !(t.flags & TypeFlags.Object)) { continue; } if (seenNonNullable) { @@ -12282,7 +12282,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) && !discriminantType) { + if (shouldTargetTypeCheckPropertiesForExcessMembers(target) && !discriminantType) { source = getRegularTypeOfObjectLiteral(source); } } diff --git a/tests/baselines/reference/conditionalTypesExcessProperties.errors.txt b/tests/baselines/reference/conditionalTypesExcessProperties.errors.txt index 24669d3426d93..cdfb0e688be29 100644 --- a/tests/baselines/reference/conditionalTypesExcessProperties.errors.txt +++ b/tests/baselines/reference/conditionalTypesExcessProperties.errors.txt @@ -1,7 +1,9 @@ -tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(8,5): error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'Something'. - Type '{ test: string; arg: A; }' is not assignable to type 'A extends object ? { arg: A; } : { arg?: undefined; }'. -tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(9,5): error TS2322: Type '{ test: string; arg: A; arr: A; }' is not assignable to type 'Something'. - Type '{ test: string; arg: A; arr: A; }' is not assignable to type 'A extends object ? { arg: A; } : { arg?: undefined; }'. +tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(8,24): error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'Something'. + Type '{ test: string; arg: A; }' is not assignable to type '{ test: string; }'. + Object literal may only specify known properties, and 'arg' does not exist in type '{ test: string; }'. +tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(9,25): error TS2322: Type '{ test: string; arg: A; arr: A; }' is not assignable to type 'Something'. + Type '{ test: string; arg: A; arr: A; }' is not assignable to type '{ test: string; }'. + Object literal may only specify known properties, and 'arg' does not exist in type '{ test: string; }'. ==== tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts (2 errors) ==== @@ -13,12 +15,14 @@ tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(9, function testFunc2(a: A, sa: Something) { sa = { test: 'hi', arg: a }; // not excess (but currently still not assignable) - ~~ + ~~~~~~ !!! error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'Something'. -!!! error TS2322: Type '{ test: string; arg: A; }' is not assignable to type 'A extends object ? { arg: A; } : { arg?: undefined; }'. +!!! error TS2322: Type '{ test: string; arg: A; }' is not assignable to type '{ test: string; }'. +!!! error TS2322: Object literal may only specify known properties, and 'arg' does not exist in type '{ test: string; }'. sa = { test: 'bye', arg: a, arr: a } // excess - ~~ + ~~~~~~ !!! error TS2322: Type '{ test: string; arg: A; arr: A; }' is not assignable to type 'Something'. -!!! error TS2322: Type '{ test: string; arg: A; arr: A; }' is not assignable to type 'A extends object ? { arg: A; } : { arg?: undefined; }'. +!!! error TS2322: Type '{ test: string; arg: A; arr: A; }' is not assignable to type '{ test: string; }'. +!!! error TS2322: Object literal may only specify known properties, and 'arg' does not exist in type '{ test: string; }'. } \ No newline at end of file diff --git a/tests/baselines/reference/nestedEPC.errors.txt b/tests/baselines/reference/nestedEPC.errors.txt new file mode 100644 index 0000000000000..2eb1e1cd3b54c --- /dev/null +++ b/tests/baselines/reference/nestedEPC.errors.txt @@ -0,0 +1,50 @@ +tests/cases/compiler/nestedEPC.ts(13,35): error TS2322: Type '{ INVALID_PROP_NAME: string; iconProp: string; }' is not assignable to type 'number | IProps'. + Object literal may only specify known properties, and 'INVALID_PROP_NAME' does not exist in type 'IProps'. +tests/cases/compiler/nestedEPC.ts(16,49): error TS2322: Type '{ nestedProp: { asdfasdf: string; }; iconProp: string; }' is not assignable to type 'number | IProps'. + Type '{ nestedProp: { asdfasdf: string; }; iconProp: string; }' is not assignable to type 'IProps'. + Types of property 'nestedProp' are incompatible. + Type '{ asdfasdf: string; }' is not assignable to type '{ testBool?: boolean; }'. + Object literal may only specify known properties, and 'asdfasdf' does not exist in type '{ testBool?: boolean; }'. +tests/cases/compiler/nestedEPC.ts(19,56): error TS2322: Type '{ nestedProps: { INVALID_PROP_NAME: string; iconProp: string; }; }' is not assignable to type 'number | INestedProps'. + Type '{ nestedProps: { INVALID_PROP_NAME: string; iconProp: string; }; }' is not assignable to type 'INestedProps'. + Types of property 'nestedProps' are incompatible. + Type '{ INVALID_PROP_NAME: string; iconProp: string; }' is not assignable to type 'IProps'. + Object literal may only specify known properties, and 'INVALID_PROP_NAME' does not exist in type 'IProps'. + + +==== tests/cases/compiler/nestedEPC.ts (3 errors) ==== + interface IProps { + iconProp?: string; + nestedProp?: { + testBool?: boolean; + } + } + + interface INestedProps { + nestedProps?: IProps; + } + + // These are the types of errors we want: + const propB1: IProps | number = { INVALID_PROP_NAME: 'share', iconProp: 'test' }; + ~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ INVALID_PROP_NAME: string; iconProp: string; }' is not assignable to type 'number | IProps'. +!!! error TS2322: Object literal may only specify known properties, and 'INVALID_PROP_NAME' does not exist in type 'IProps'. + + // Nested typing works here and we also get an expected error: + const propB2: IProps | number = { nestedProp: { asdfasdf: 'test' }, iconProp: 'test' }; + ~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ nestedProp: { asdfasdf: string; }; iconProp: string; }' is not assignable to type 'number | IProps'. +!!! error TS2322: Type '{ nestedProp: { asdfasdf: string; }; iconProp: string; }' is not assignable to type 'IProps'. +!!! error TS2322: Types of property 'nestedProp' are incompatible. +!!! error TS2322: Type '{ asdfasdf: string; }' is not assignable to type '{ testBool?: boolean; }'. +!!! error TS2322: Object literal may only specify known properties, and 'asdfasdf' does not exist in type '{ testBool?: boolean; }'. + + // Want an error generated here but there isn't one. + const propA1: INestedProps | number = { nestedProps: { INVALID_PROP_NAME: 'share', iconProp: 'test' } }; + ~~~~~~~~~~~~~~~~~~~~~~~~~~ +!!! error TS2322: Type '{ nestedProps: { INVALID_PROP_NAME: string; iconProp: string; }; }' is not assignable to type 'number | INestedProps'. +!!! error TS2322: Type '{ nestedProps: { INVALID_PROP_NAME: string; iconProp: string; }; }' is not assignable to type 'INestedProps'. +!!! error TS2322: Types of property 'nestedProps' are incompatible. +!!! error TS2322: Type '{ INVALID_PROP_NAME: string; iconProp: string; }' is not assignable to type 'IProps'. +!!! error TS2322: Object literal may only specify known properties, and 'INVALID_PROP_NAME' does not exist in type 'IProps'. + \ No newline at end of file diff --git a/tests/baselines/reference/nestedEPC.js b/tests/baselines/reference/nestedEPC.js new file mode 100644 index 0000000000000..4177704dcae02 --- /dev/null +++ b/tests/baselines/reference/nestedEPC.js @@ -0,0 +1,29 @@ +//// [nestedEPC.ts] +interface IProps { + iconProp?: string; + nestedProp?: { + testBool?: boolean; + } +} + +interface INestedProps { + nestedProps?: IProps; +} + +// These are the types of errors we want: +const propB1: IProps | number = { INVALID_PROP_NAME: 'share', iconProp: 'test' }; + +// Nested typing works here and we also get an expected error: +const propB2: IProps | number = { nestedProp: { asdfasdf: 'test' }, iconProp: 'test' }; + +// Want an error generated here but there isn't one. +const propA1: INestedProps | number = { nestedProps: { INVALID_PROP_NAME: 'share', iconProp: 'test' } }; + + +//// [nestedEPC.js] +// These are the types of errors we want: +var propB1 = { INVALID_PROP_NAME: 'share', iconProp: 'test' }; +// Nested typing works here and we also get an expected error: +var propB2 = { nestedProp: { asdfasdf: 'test' }, iconProp: 'test' }; +// Want an error generated here but there isn't one. +var propA1 = { nestedProps: { INVALID_PROP_NAME: 'share', iconProp: 'test' } }; diff --git a/tests/baselines/reference/nestedEPC.symbols b/tests/baselines/reference/nestedEPC.symbols new file mode 100644 index 0000000000000..f74397945f440 --- /dev/null +++ b/tests/baselines/reference/nestedEPC.symbols @@ -0,0 +1,46 @@ +=== tests/cases/compiler/nestedEPC.ts === +interface IProps { +>IProps : Symbol(IProps, Decl(nestedEPC.ts, 0, 0)) + + iconProp?: string; +>iconProp : Symbol(IProps.iconProp, Decl(nestedEPC.ts, 0, 18)) + + nestedProp?: { +>nestedProp : Symbol(IProps.nestedProp, Decl(nestedEPC.ts, 1, 20)) + + testBool?: boolean; +>testBool : Symbol(testBool, Decl(nestedEPC.ts, 2, 16)) + } +} + +interface INestedProps { +>INestedProps : Symbol(INestedProps, Decl(nestedEPC.ts, 5, 1)) + + nestedProps?: IProps; +>nestedProps : Symbol(INestedProps.nestedProps, Decl(nestedEPC.ts, 7, 24)) +>IProps : Symbol(IProps, Decl(nestedEPC.ts, 0, 0)) +} + +// These are the types of errors we want: +const propB1: IProps | number = { INVALID_PROP_NAME: 'share', iconProp: 'test' }; +>propB1 : Symbol(propB1, Decl(nestedEPC.ts, 12, 5)) +>IProps : Symbol(IProps, Decl(nestedEPC.ts, 0, 0)) +>INVALID_PROP_NAME : Symbol(INVALID_PROP_NAME, Decl(nestedEPC.ts, 12, 33)) +>iconProp : Symbol(iconProp, Decl(nestedEPC.ts, 12, 61)) + +// Nested typing works here and we also get an expected error: +const propB2: IProps | number = { nestedProp: { asdfasdf: 'test' }, iconProp: 'test' }; +>propB2 : Symbol(propB2, Decl(nestedEPC.ts, 15, 5)) +>IProps : Symbol(IProps, Decl(nestedEPC.ts, 0, 0)) +>nestedProp : Symbol(nestedProp, Decl(nestedEPC.ts, 15, 33)) +>asdfasdf : Symbol(asdfasdf, Decl(nestedEPC.ts, 15, 47)) +>iconProp : Symbol(iconProp, Decl(nestedEPC.ts, 15, 67)) + +// Want an error generated here but there isn't one. +const propA1: INestedProps | number = { nestedProps: { INVALID_PROP_NAME: 'share', iconProp: 'test' } }; +>propA1 : Symbol(propA1, Decl(nestedEPC.ts, 18, 5)) +>INestedProps : Symbol(INestedProps, Decl(nestedEPC.ts, 5, 1)) +>nestedProps : Symbol(nestedProps, Decl(nestedEPC.ts, 18, 39)) +>INVALID_PROP_NAME : Symbol(INVALID_PROP_NAME, Decl(nestedEPC.ts, 18, 54)) +>iconProp : Symbol(iconProp, Decl(nestedEPC.ts, 18, 82)) + diff --git a/tests/baselines/reference/nestedEPC.types b/tests/baselines/reference/nestedEPC.types new file mode 100644 index 0000000000000..7f9d6acf06f6e --- /dev/null +++ b/tests/baselines/reference/nestedEPC.types @@ -0,0 +1,49 @@ +=== tests/cases/compiler/nestedEPC.ts === +interface IProps { + iconProp?: string; +>iconProp : string + + nestedProp?: { +>nestedProp : { testBool?: boolean; } + + testBool?: boolean; +>testBool : boolean + } +} + +interface INestedProps { + nestedProps?: IProps; +>nestedProps : IProps +} + +// These are the types of errors we want: +const propB1: IProps | number = { INVALID_PROP_NAME: 'share', iconProp: 'test' }; +>propB1 : number | IProps +>{ INVALID_PROP_NAME: 'share', iconProp: 'test' } : { INVALID_PROP_NAME: string; iconProp: string; } +>INVALID_PROP_NAME : string +>'share' : "share" +>iconProp : string +>'test' : "test" + +// Nested typing works here and we also get an expected error: +const propB2: IProps | number = { nestedProp: { asdfasdf: 'test' }, iconProp: 'test' }; +>propB2 : number | IProps +>{ nestedProp: { asdfasdf: 'test' }, iconProp: 'test' } : { nestedProp: { asdfasdf: string; }; iconProp: string; } +>nestedProp : { asdfasdf: string; } +>{ asdfasdf: 'test' } : { asdfasdf: string; } +>asdfasdf : string +>'test' : "test" +>iconProp : string +>'test' : "test" + +// Want an error generated here but there isn't one. +const propA1: INestedProps | number = { nestedProps: { INVALID_PROP_NAME: 'share', iconProp: 'test' } }; +>propA1 : number | INestedProps +>{ nestedProps: { INVALID_PROP_NAME: 'share', iconProp: 'test' } } : { nestedProps: { INVALID_PROP_NAME: string; iconProp: string; }; } +>nestedProps : { INVALID_PROP_NAME: string; iconProp: string; } +>{ INVALID_PROP_NAME: 'share', iconProp: 'test' } : { INVALID_PROP_NAME: string; iconProp: string; } +>INVALID_PROP_NAME : string +>'share' : "share" +>iconProp : string +>'test' : "test" + diff --git a/tests/baselines/reference/objectLiteralExcessProperties.errors.txt b/tests/baselines/reference/objectLiteralExcessProperties.errors.txt index e4fc4d69fca99..3411c6b8be139 100644 --- a/tests/baselines/reference/objectLiteralExcessProperties.errors.txt +++ b/tests/baselines/reference/objectLiteralExcessProperties.errors.txt @@ -13,8 +13,9 @@ tests/cases/compiler/objectLiteralExcessProperties.ts(17,26): error TS2322: Type Object literal may only specify known properties, but 'foreward' does not exist in type 'Book & Cover'. Did you mean to write 'foreword'? tests/cases/compiler/objectLiteralExcessProperties.ts(19,57): error TS2322: Type '{ foreword: string; color: string; price: number; }' is not assignable to type 'Book & Cover'. Object literal may only specify known properties, and 'price' does not exist in type 'Book & Cover'. -tests/cases/compiler/objectLiteralExcessProperties.ts(21,5): error TS2322: Type '{ foreword: string; price: number; }' is not assignable to type 'Book & number'. - Type '{ foreword: string; price: number; }' is not assignable to type 'number'. +tests/cases/compiler/objectLiteralExcessProperties.ts(21,43): error TS2322: Type '{ foreword: string; price: number; }' is not assignable to type 'Book & number'. + Type '{ foreword: string; price: number; }' is not assignable to type 'Book'. + Object literal may only specify known properties, and 'price' does not exist in type 'Book'. tests/cases/compiler/objectLiteralExcessProperties.ts(23,29): error TS2322: Type '{ couleur: string; }' is not assignable to type 'Cover | Cover[]'. Object literal may only specify known properties, and 'couleur' does not exist in type 'Cover | Cover[]'. tests/cases/compiler/objectLiteralExcessProperties.ts(25,27): error TS2322: Type '{ forewarned: string; }' is not assignable to type 'Book | Book[]'. @@ -76,9 +77,10 @@ tests/cases/compiler/objectLiteralExcessProperties.ts(49,44): error TS2322: Type !!! error TS2322: Object literal may only specify known properties, and 'price' does not exist in type 'Book & Cover'. var b7: Book & number = { foreword: "hi", price: 10.99 }; - ~~ + ~~~~~~~~~~~~ !!! error TS2322: Type '{ foreword: string; price: number; }' is not assignable to type 'Book & number'. -!!! error TS2322: Type '{ foreword: string; price: number; }' is not assignable to type 'number'. +!!! error TS2322: Type '{ foreword: string; price: number; }' is not assignable to type 'Book'. +!!! error TS2322: Object literal may only specify known properties, and 'price' does not exist in type 'Book'. var b8: Cover | Cover[] = { couleur : "non" }; ~~~~~~~~~~~~~~~ diff --git a/tests/cases/compiler/nestedEPC.ts b/tests/cases/compiler/nestedEPC.ts new file mode 100644 index 0000000000000..9c8bebe1ff161 --- /dev/null +++ b/tests/cases/compiler/nestedEPC.ts @@ -0,0 +1,19 @@ +interface IProps { + iconProp?: string; + nestedProp?: { + testBool?: boolean; + } +} + +interface INestedProps { + nestedProps?: IProps; +} + +// These are the types of errors we want: +const propB1: IProps | number = { INVALID_PROP_NAME: 'share', iconProp: 'test' }; + +// Nested typing works here and we also get an expected error: +const propB2: IProps | number = { nestedProp: { asdfasdf: 'test' }, iconProp: 'test' }; + +// Want an error generated here but there isn't one. +const propA1: INestedProps | number = { nestedProps: { INVALID_PROP_NAME: 'share', iconProp: 'test' } };