Skip to content

Fix nested excess property checks when target is a union with non-discriminated + non-object constituents #30771

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12197,15 +12197,15 @@ namespace ts {
}
}

function isUnionOrIntersectionTypeWithoutNullableConstituents(type: Type): boolean {
function shouldTargetTypeCheckPropertiesForExcessMembers(type: Type): boolean {
if (!(type.flags & TypeFlags.UnionOrIntersection)) {
return false;
}
// at this point we know that this is union or intersection type possibly with nullable constituents.
// check if we still will have compound type if we ignore nullable components.
let seenNonNullable = false;
for (const t of (<UnionOrIntersectionType>type).types) {
if (t.flags & TypeFlags.Nullable) {
if (t.flags & TypeFlags.Nullable || !(t.flags & TypeFlags.Object)) {
continue;
}
if (seenNonNullable) {
Expand Down Expand Up @@ -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);
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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<A>'.
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<A>'.
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<A>'.
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<A>'.
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) ====
Expand All @@ -13,12 +15,14 @@ tests/cases/conformance/types/conditional/conditionalTypesExcessProperties.ts(9,

function testFunc2<A extends object>(a: A, sa: Something<A>) {
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<A>'.
!!! 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<A>'.
!!! 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; }'.
}

50 changes: 50 additions & 0 deletions tests/baselines/reference/nestedEPC.errors.txt
Original file line number Diff line number Diff line change
@@ -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'.

29 changes: 29 additions & 0 deletions tests/baselines/reference/nestedEPC.js
Original file line number Diff line number Diff line change
@@ -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' } };
46 changes: 46 additions & 0 deletions tests/baselines/reference/nestedEPC.symbols
Original file line number Diff line number Diff line change
@@ -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))

49 changes: 49 additions & 0 deletions tests/baselines/reference/nestedEPC.types
Original file line number Diff line number Diff line change
@@ -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"

Original file line number Diff line number Diff line change
Expand Up @@ -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[]'.
Expand Down Expand Up @@ -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" };
~~~~~~~~~~~~~~~
Expand Down
19 changes: 19 additions & 0 deletions tests/cases/compiler/nestedEPC.ts
Original file line number Diff line number Diff line change
@@ -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' } };