diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index ae1ded9d0d8df..5002819645ff7 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -12313,7 +12313,7 @@ namespace ts { return getReducedType(getApparentType(getReducedType(type))); } - function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + function createUnionOrIntersectionProperty(containingType: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean, fromIsDiscriminantProperty?: boolean | undefined): Symbol | undefined { let singleProp: Symbol | undefined; let propSet: ESMap | undefined; let indexTypes: Type[] | undefined; @@ -12438,7 +12438,7 @@ namespace ts { else if (type !== firstType) { checkFlags |= CheckFlags.HasNonUniformType; } - if (isLiteralType(type) || isPatternLiteralType(type) || type === uniqueLiteralType) { + if ((isLiteralType(type) && (!fromIsDiscriminantProperty || !(type.flags & TypeFlags.Boolean && type.flags & TypeFlags.Union))) || isPatternLiteralType(type) || type === uniqueLiteralType) { checkFlags |= CheckFlags.HasLiteralType; } if (type.flags & TypeFlags.Never && type !== uniqueLiteralType) { @@ -12481,11 +12481,11 @@ namespace ts { // constituents, in which case the isPartial flag is set when the containing type is union type. We need // these partial properties when identifying discriminant properties, but otherwise they are filtered out // and do not appear to be present in the union type. - function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean): Symbol | undefined { + function getUnionOrIntersectionProperty(type: UnionOrIntersectionType, name: __String, skipObjectFunctionPropertyAugment?: boolean, fromIsDiscriminantProperty?: boolean | undefined): Symbol | undefined { let property = type.propertyCacheWithoutObjectFunctionPropertyAugment?.get(name) || !skipObjectFunctionPropertyAugment ? type.propertyCache?.get(name) : undefined; if (!property) { - property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment); + property = createUnionOrIntersectionProperty(type, name, skipObjectFunctionPropertyAugment, fromIsDiscriminantProperty); if (property) { const properties = skipObjectFunctionPropertyAugment ? type.propertyCacheWithoutObjectFunctionPropertyAugment ||= createSymbolTable() : @@ -23219,7 +23219,7 @@ namespace ts { function isDiscriminantProperty(type: Type | undefined, name: __String) { if (type && type.flags & TypeFlags.Union) { - const prop = getUnionOrIntersectionProperty(type as UnionType, name); + const prop = getUnionOrIntersectionProperty(type as UnionType, name, /* skipObjectFunctionPropertyAugment */ undefined, /* fromIsDiscriminantProperty */ true); if (prop && getCheckFlags(prop) & CheckFlags.SyntheticProperty) { if ((prop as TransientSymbol).isDiscriminantProperty === undefined) { (prop as TransientSymbol).isDiscriminantProperty = diff --git a/tests/baselines/reference/_44856.js b/tests/baselines/reference/_44856.js new file mode 100644 index 0000000000000..298533a275687 --- /dev/null +++ b/tests/baselines/reference/_44856.js @@ -0,0 +1,76 @@ +//// [_44856.ts] +// The expected rule for properties of union type (|) is +// (1) must contain all required properties of at least one union member +// (2) may contain additional properties that belong to any union member + +{ + type A2 = {a: string; b: string}; + type A3 = {a: string; b: boolean; c: number}; + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: 1}; // ❌ assignment strictly checked, extra prop from A3 not allowed + } +} + +{ + type A2 = {a: string; b: string}; + type A3 = {a: string; b: boolean; c: boolean}; + type A4 = {a: string; b: number; c: number}; + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: true}; // ❌ assignment strictly checked, extra prop from A3 not allowed + } + { + // BEHAVING AS EXPECTED + const b: A2|A3|A4 = {a: '', b: '', c: true}; // ✔ assignments allow extra props of other union types + } + } + + { + type A2 = {a: string; b: string}; + type A3 = {a: string; b: boolean; c: boolean}; + type A4 = {a: string; b: boolean; c: number}; + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: true}; // ❌ assignment strictly checked, extra prop from A3 not allowed + } + { + // BEHAVING AS EXPECTED + const b: A2|A3|A4 = {a: '', b: '', c: true}; // ✔ assignments allow extra props of other union types + } + } + + +//// [_44856.js] +// The expected rule for properties of union type (|) is +// (1) must contain all required properties of at least one union member +// (2) may contain additional properties that belong to any union member +{ + { + // THIS IS THE UNEXPECTED OUTLIER + var b = { a: '', b: '', c: 1 }; // ❌ assignment strictly checked, extra prop from A3 not allowed + } +} +{ + { + // THIS IS THE UNEXPECTED OUTLIER + var b = { a: '', b: '', c: true }; // ❌ assignment strictly checked, extra prop from A3 not allowed + } + { + // BEHAVING AS EXPECTED + var b = { a: '', b: '', c: true }; // ✔ assignments allow extra props of other union types + } +} +{ + { + // THIS IS THE UNEXPECTED OUTLIER + var b = { a: '', b: '', c: true }; // ❌ assignment strictly checked, extra prop from A3 not allowed + } + { + // BEHAVING AS EXPECTED + var b = { a: '', b: '', c: true }; // ✔ assignments allow extra props of other union types + } +} diff --git a/tests/baselines/reference/_44856.symbols b/tests/baselines/reference/_44856.symbols new file mode 100644 index 0000000000000..2da24884770dc --- /dev/null +++ b/tests/baselines/reference/_44856.symbols @@ -0,0 +1,111 @@ +=== tests/cases/compiler/_44856.ts === +// The expected rule for properties of union type (|) is +// (1) must contain all required properties of at least one union member +// (2) may contain additional properties that belong to any union member + +{ + type A2 = {a: string; b: string}; +>A2 : Symbol(A2, Decl(_44856.ts, 4, 1)) +>a : Symbol(a, Decl(_44856.ts, 5, 15)) +>b : Symbol(b, Decl(_44856.ts, 5, 25)) + + type A3 = {a: string; b: boolean; c: number}; +>A3 : Symbol(A3, Decl(_44856.ts, 5, 37)) +>a : Symbol(a, Decl(_44856.ts, 6, 15)) +>b : Symbol(b, Decl(_44856.ts, 6, 25)) +>c : Symbol(c, Decl(_44856.ts, 6, 37)) + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: 1}; // ❌ assignment strictly checked, extra prop from A3 not allowed +>b : Symbol(b, Decl(_44856.ts, 10, 11)) +>A2 : Symbol(A2, Decl(_44856.ts, 4, 1)) +>A3 : Symbol(A3, Decl(_44856.ts, 5, 37)) +>a : Symbol(a, Decl(_44856.ts, 10, 24)) +>b : Symbol(b, Decl(_44856.ts, 10, 30)) +>c : Symbol(c, Decl(_44856.ts, 10, 37)) + } +} + +{ + type A2 = {a: string; b: string}; +>A2 : Symbol(A2, Decl(_44856.ts, 14, 1)) +>a : Symbol(a, Decl(_44856.ts, 15, 15)) +>b : Symbol(b, Decl(_44856.ts, 15, 25)) + + type A3 = {a: string; b: boolean; c: boolean}; +>A3 : Symbol(A3, Decl(_44856.ts, 15, 37)) +>a : Symbol(a, Decl(_44856.ts, 16, 15)) +>b : Symbol(b, Decl(_44856.ts, 16, 25)) +>c : Symbol(c, Decl(_44856.ts, 16, 37)) + + type A4 = {a: string; b: number; c: number}; +>A4 : Symbol(A4, Decl(_44856.ts, 16, 50)) +>a : Symbol(a, Decl(_44856.ts, 17, 15)) +>b : Symbol(b, Decl(_44856.ts, 17, 25)) +>c : Symbol(c, Decl(_44856.ts, 17, 36)) + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: true}; // ❌ assignment strictly checked, extra prop from A3 not allowed +>b : Symbol(b, Decl(_44856.ts, 21, 11)) +>A2 : Symbol(A2, Decl(_44856.ts, 14, 1)) +>A3 : Symbol(A3, Decl(_44856.ts, 15, 37)) +>a : Symbol(a, Decl(_44856.ts, 21, 24)) +>b : Symbol(b, Decl(_44856.ts, 21, 30)) +>c : Symbol(c, Decl(_44856.ts, 21, 37)) + } + { + // BEHAVING AS EXPECTED + const b: A2|A3|A4 = {a: '', b: '', c: true}; // ✔ assignments allow extra props of other union types +>b : Symbol(b, Decl(_44856.ts, 25, 11)) +>A2 : Symbol(A2, Decl(_44856.ts, 14, 1)) +>A3 : Symbol(A3, Decl(_44856.ts, 15, 37)) +>A4 : Symbol(A4, Decl(_44856.ts, 16, 50)) +>a : Symbol(a, Decl(_44856.ts, 25, 27)) +>b : Symbol(b, Decl(_44856.ts, 25, 33)) +>c : Symbol(c, Decl(_44856.ts, 25, 40)) + } + } + + { + type A2 = {a: string; b: string}; +>A2 : Symbol(A2, Decl(_44856.ts, 29, 3)) +>a : Symbol(a, Decl(_44856.ts, 30, 15)) +>b : Symbol(b, Decl(_44856.ts, 30, 25)) + + type A3 = {a: string; b: boolean; c: boolean}; +>A3 : Symbol(A3, Decl(_44856.ts, 30, 37)) +>a : Symbol(a, Decl(_44856.ts, 31, 15)) +>b : Symbol(b, Decl(_44856.ts, 31, 25)) +>c : Symbol(c, Decl(_44856.ts, 31, 37)) + + type A4 = {a: string; b: boolean; c: number}; +>A4 : Symbol(A4, Decl(_44856.ts, 31, 50)) +>a : Symbol(a, Decl(_44856.ts, 32, 15)) +>b : Symbol(b, Decl(_44856.ts, 32, 25)) +>c : Symbol(c, Decl(_44856.ts, 32, 37)) + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: true}; // ❌ assignment strictly checked, extra prop from A3 not allowed +>b : Symbol(b, Decl(_44856.ts, 36, 11)) +>A2 : Symbol(A2, Decl(_44856.ts, 29, 3)) +>A3 : Symbol(A3, Decl(_44856.ts, 30, 37)) +>a : Symbol(a, Decl(_44856.ts, 36, 24)) +>b : Symbol(b, Decl(_44856.ts, 36, 30)) +>c : Symbol(c, Decl(_44856.ts, 36, 37)) + } + { + // BEHAVING AS EXPECTED + const b: A2|A3|A4 = {a: '', b: '', c: true}; // ✔ assignments allow extra props of other union types +>b : Symbol(b, Decl(_44856.ts, 40, 11)) +>A2 : Symbol(A2, Decl(_44856.ts, 29, 3)) +>A3 : Symbol(A3, Decl(_44856.ts, 30, 37)) +>A4 : Symbol(A4, Decl(_44856.ts, 31, 50)) +>a : Symbol(a, Decl(_44856.ts, 40, 27)) +>b : Symbol(b, Decl(_44856.ts, 40, 33)) +>c : Symbol(c, Decl(_44856.ts, 40, 40)) + } + } + diff --git a/tests/baselines/reference/_44856.types b/tests/baselines/reference/_44856.types new file mode 100644 index 0000000000000..d43944069c0a1 --- /dev/null +++ b/tests/baselines/reference/_44856.types @@ -0,0 +1,119 @@ +=== tests/cases/compiler/_44856.ts === +// The expected rule for properties of union type (|) is +// (1) must contain all required properties of at least one union member +// (2) may contain additional properties that belong to any union member + +{ + type A2 = {a: string; b: string}; +>A2 : { a: string; b: string; } +>a : string +>b : string + + type A3 = {a: string; b: boolean; c: number}; +>A3 : { a: string; b: boolean; c: number; } +>a : string +>b : boolean +>c : number + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: 1}; // ❌ assignment strictly checked, extra prop from A3 not allowed +>b : { a: string; b: string; } | { a: string; b: boolean; c: number; } +>{a: '', b: '', c: 1} : { a: string; b: string; c: number; } +>a : string +>'' : "" +>b : string +>'' : "" +>c : number +>1 : 1 + } +} + +{ + type A2 = {a: string; b: string}; +>A2 : { a: string; b: string; } +>a : string +>b : string + + type A3 = {a: string; b: boolean; c: boolean}; +>A3 : { a: string; b: boolean; c: boolean; } +>a : string +>b : boolean +>c : boolean + + type A4 = {a: string; b: number; c: number}; +>A4 : { a: string; b: number; c: number; } +>a : string +>b : number +>c : number + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: true}; // ❌ assignment strictly checked, extra prop from A3 not allowed +>b : { a: string; b: string; } | { a: string; b: boolean; c: boolean; } +>{a: '', b: '', c: true} : { a: string; b: string; c: true; } +>a : string +>'' : "" +>b : string +>'' : "" +>c : true +>true : true + } + { + // BEHAVING AS EXPECTED + const b: A2|A3|A4 = {a: '', b: '', c: true}; // ✔ assignments allow extra props of other union types +>b : { a: string; b: string; } | { a: string; b: boolean; c: boolean; } | { a: string; b: number; c: number; } +>{a: '', b: '', c: true} : { a: string; b: string; c: true; } +>a : string +>'' : "" +>b : string +>'' : "" +>c : true +>true : true + } + } + + { + type A2 = {a: string; b: string}; +>A2 : { a: string; b: string; } +>a : string +>b : string + + type A3 = {a: string; b: boolean; c: boolean}; +>A3 : { a: string; b: boolean; c: boolean; } +>a : string +>b : boolean +>c : boolean + + type A4 = {a: string; b: boolean; c: number}; +>A4 : { a: string; b: boolean; c: number; } +>a : string +>b : boolean +>c : number + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: true}; // ❌ assignment strictly checked, extra prop from A3 not allowed +>b : { a: string; b: string; } | { a: string; b: boolean; c: boolean; } +>{a: '', b: '', c: true} : { a: string; b: string; c: true; } +>a : string +>'' : "" +>b : string +>'' : "" +>c : true +>true : true + } + { + // BEHAVING AS EXPECTED + const b: A2|A3|A4 = {a: '', b: '', c: true}; // ✔ assignments allow extra props of other union types +>b : { a: string; b: string; } | { a: string; b: boolean; c: boolean; } | { a: string; b: boolean; c: number; } +>{a: '', b: '', c: true} : { a: string; b: string; c: true; } +>a : string +>'' : "" +>b : string +>'' : "" +>c : true +>true : true + } + } + diff --git a/tests/cases/compiler/_44856.ts b/tests/cases/compiler/_44856.ts new file mode 100644 index 0000000000000..f31663b2a2b5b --- /dev/null +++ b/tests/cases/compiler/_44856.ts @@ -0,0 +1,43 @@ +// The expected rule for properties of union type (|) is +// (1) must contain all required properties of at least one union member +// (2) may contain additional properties that belong to any union member + +{ + type A2 = {a: string; b: string}; + type A3 = {a: string; b: boolean; c: number}; + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: 1}; // ❌ assignment strictly checked, extra prop from A3 not allowed + } +} + +{ + type A2 = {a: string; b: string}; + type A3 = {a: string; b: boolean; c: boolean}; + type A4 = {a: string; b: number; c: number}; + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: true}; // ❌ assignment strictly checked, extra prop from A3 not allowed + } + { + // BEHAVING AS EXPECTED + const b: A2|A3|A4 = {a: '', b: '', c: true}; // ✔ assignments allow extra props of other union types + } + } + + { + type A2 = {a: string; b: string}; + type A3 = {a: string; b: boolean; c: boolean}; + type A4 = {a: string; b: boolean; c: number}; + + { + // THIS IS THE UNEXPECTED OUTLIER + const b: A2|A3 = {a: '', b: '', c: true}; // ❌ assignment strictly checked, extra prop from A3 not allowed + } + { + // BEHAVING AS EXPECTED + const b: A2|A3|A4 = {a: '', b: '', c: true}; // ✔ assignments allow extra props of other union types + } + }