diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index d24646529192c..418be79878e35 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -4608,7 +4608,7 @@ namespace ts { if (!names.has(prop.escapedName) && !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected)) && isSpreadableProperty(prop)) { - members.set(prop.escapedName, getNonReadonlySymbol(prop)); + members.set(prop.escapedName, getSpreadSymbol(prop)); } } const stringIndexInfo = getIndexInfoOfType(source, IndexKind.String); @@ -9887,7 +9887,7 @@ namespace ts { skippedPrivateMembers.set(rightProp.escapedName, true); } else if (isSpreadableProperty(rightProp)) { - members.set(rightProp.escapedName, getNonReadonlySymbol(rightProp)); + members.set(rightProp.escapedName, getSpreadSymbol(rightProp)); } } @@ -9911,7 +9911,7 @@ namespace ts { } } else { - members.set(leftProp.escapedName, getNonReadonlySymbol(leftProp)); + members.set(leftProp.escapedName, getSpreadSymbol(leftProp)); } } @@ -9929,18 +9929,19 @@ namespace ts { /** We approximate own properties as non-methods plus methods that are inside the object literal */ function isSpreadableProperty(prop: Symbol): boolean { - return prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor) - ? !prop.declarations.some(decl => isClassLike(decl.parent)) - : !(prop.flags & SymbolFlags.SetAccessor); // Setter without getter is not spreadable + return !(prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor | SymbolFlags.SetAccessor)) || + !prop.declarations.some(decl => isClassLike(decl.parent)); } - function getNonReadonlySymbol(prop: Symbol) { - if (!isReadonlySymbol(prop)) { + function getSpreadSymbol(prop: Symbol) { + const isReadonly = isReadonlySymbol(prop); + const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor); + if (!isReadonly && !isSetonlyAccessor) { return prop; } const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional); const result = createSymbol(flags, prop.escapedName); - result.type = getTypeOfSymbol(prop); + result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop); result.declarations = prop.declarations; result.nameType = prop.nameType; result.syntheticOrigin = prop; diff --git a/tests/baselines/reference/objectSpreadNegative.errors.txt b/tests/baselines/reference/objectSpreadNegative.errors.txt index 1af059068733e..2f1956408715b 100644 --- a/tests/baselines/reference/objectSpreadNegative.errors.txt +++ b/tests/baselines/reference/objectSpreadNegative.errors.txt @@ -13,7 +13,7 @@ tests/cases/conformance/types/spread/objectSpreadNegative.ts(34,20): error TS269 tests/cases/conformance/types/spread/objectSpreadNegative.ts(36,20): error TS2698: Spread types may only be created from object types. tests/cases/conformance/types/spread/objectSpreadNegative.ts(38,19): error TS2698: Spread types may only be created from object types. tests/cases/conformance/types/spread/objectSpreadNegative.ts(43,1): error TS2349: Cannot invoke an expression whose type lacks a call signature. Type '{}' has no compatible call signatures. -tests/cases/conformance/types/spread/objectSpreadNegative.ts(47,12): error TS2339: Property 'b' does not exist on type '{}'. +tests/cases/conformance/types/spread/objectSpreadNegative.ts(47,1): error TS2322: Type '12' is not assignable to type 'undefined'. tests/cases/conformance/types/spread/objectSpreadNegative.ts(53,9): error TS2339: Property 'm' does not exist on type '{ p: number; }'. tests/cases/conformance/types/spread/objectSpreadNegative.ts(58,11): error TS2339: Property 'a' does not exist on type '{}'. tests/cases/conformance/types/spread/objectSpreadNegative.ts(62,14): error TS2698: Spread types may only be created from object types. @@ -95,8 +95,8 @@ tests/cases/conformance/types/spread/objectSpreadNegative.ts(65,14): error TS269 // write-only properties get skipped let setterOnly = { ...{ set b (bad: number) { } } }; setterOnly.b = 12; // error, 'b' does not exist - ~ -!!! error TS2339: Property 'b' does not exist on type '{}'. + ~~~~~~~~~~~~ +!!! error TS2322: Type '12' is not assignable to type 'undefined'. // methods are skipped because they aren't enumerable class C { p = 1; m() { } } diff --git a/tests/baselines/reference/objectSpreadNegative.symbols b/tests/baselines/reference/objectSpreadNegative.symbols index 4bff16d9be602..110d4557d2a51 100644 --- a/tests/baselines/reference/objectSpreadNegative.symbols +++ b/tests/baselines/reference/objectSpreadNegative.symbols @@ -132,7 +132,9 @@ let setterOnly = { ...{ set b (bad: number) { } } }; >bad : Symbol(bad, Decl(objectSpreadNegative.ts, 45, 31)) setterOnly.b = 12; // error, 'b' does not exist +>setterOnly.b : Symbol(b, Decl(objectSpreadNegative.ts, 45, 23)) >setterOnly : Symbol(setterOnly, Decl(objectSpreadNegative.ts, 45, 3)) +>b : Symbol(b, Decl(objectSpreadNegative.ts, 45, 23)) // methods are skipped because they aren't enumerable class C { p = 1; m() { } } diff --git a/tests/baselines/reference/objectSpreadNegative.types b/tests/baselines/reference/objectSpreadNegative.types index 26fd5642d2301..837670d58cae6 100644 --- a/tests/baselines/reference/objectSpreadNegative.types +++ b/tests/baselines/reference/objectSpreadNegative.types @@ -173,17 +173,17 @@ spreadFunc(); // error, no call signature // write-only properties get skipped let setterOnly = { ...{ set b (bad: number) { } } }; ->setterOnly : {} ->{ ...{ set b (bad: number) { } } } : {} +>setterOnly : { b: undefined; } +>{ ...{ set b (bad: number) { } } } : { b: undefined; } >{ set b (bad: number) { } } : { b: number; } >b : number >bad : number setterOnly.b = 12; // error, 'b' does not exist >setterOnly.b = 12 : 12 ->setterOnly.b : any ->setterOnly : {} ->b : any +>setterOnly.b : undefined +>setterOnly : { b: undefined; } +>b : undefined >12 : 12 // methods are skipped because they aren't enumerable diff --git a/tests/baselines/reference/objectSpreadSetonlyAccessor.js b/tests/baselines/reference/objectSpreadSetonlyAccessor.js new file mode 100644 index 0000000000000..17b854e85e5ab --- /dev/null +++ b/tests/baselines/reference/objectSpreadSetonlyAccessor.js @@ -0,0 +1,9 @@ +//// [objectSpreadSetonlyAccessor.ts] +const o1: { foo: number, bar: undefined } = { foo: 1, ... { set bar(_v: number) { } } } +const o2: { foo: undefined } = { foo: 1, ... { set foo(_v: number) { } } } + + +//// [objectSpreadSetonlyAccessor.js] +"use strict"; +const o1 = { foo: 1, ...{ set bar(_v) { } } }; +const o2 = { foo: 1, ...{ set foo(_v) { } } }; diff --git a/tests/baselines/reference/objectSpreadSetonlyAccessor.symbols b/tests/baselines/reference/objectSpreadSetonlyAccessor.symbols new file mode 100644 index 0000000000000..d8f0281c6c029 --- /dev/null +++ b/tests/baselines/reference/objectSpreadSetonlyAccessor.symbols @@ -0,0 +1,16 @@ +=== tests/cases/conformance/types/spread/objectSpreadSetonlyAccessor.ts === +const o1: { foo: number, bar: undefined } = { foo: 1, ... { set bar(_v: number) { } } } +>o1 : Symbol(o1, Decl(objectSpreadSetonlyAccessor.ts, 0, 5)) +>foo : Symbol(foo, Decl(objectSpreadSetonlyAccessor.ts, 0, 11)) +>bar : Symbol(bar, Decl(objectSpreadSetonlyAccessor.ts, 0, 24)) +>foo : Symbol(foo, Decl(objectSpreadSetonlyAccessor.ts, 0, 45)) +>bar : Symbol(bar, Decl(objectSpreadSetonlyAccessor.ts, 0, 59)) +>_v : Symbol(_v, Decl(objectSpreadSetonlyAccessor.ts, 0, 68)) + +const o2: { foo: undefined } = { foo: 1, ... { set foo(_v: number) { } } } +>o2 : Symbol(o2, Decl(objectSpreadSetonlyAccessor.ts, 1, 5)) +>foo : Symbol(foo, Decl(objectSpreadSetonlyAccessor.ts, 1, 11)) +>foo : Symbol(foo, Decl(objectSpreadSetonlyAccessor.ts, 1, 32)) +>foo : Symbol(foo, Decl(objectSpreadSetonlyAccessor.ts, 1, 46)) +>_v : Symbol(_v, Decl(objectSpreadSetonlyAccessor.ts, 1, 55)) + diff --git a/tests/baselines/reference/objectSpreadSetonlyAccessor.types b/tests/baselines/reference/objectSpreadSetonlyAccessor.types new file mode 100644 index 0000000000000..f9e482f008b58 --- /dev/null +++ b/tests/baselines/reference/objectSpreadSetonlyAccessor.types @@ -0,0 +1,22 @@ +=== tests/cases/conformance/types/spread/objectSpreadSetonlyAccessor.ts === +const o1: { foo: number, bar: undefined } = { foo: 1, ... { set bar(_v: number) { } } } +>o1 : { foo: number; bar: undefined; } +>foo : number +>bar : undefined +>{ foo: 1, ... { set bar(_v: number) { } } } : { bar: undefined; foo: number; } +>foo : number +>1 : 1 +>{ set bar(_v: number) { } } : { bar: number; } +>bar : number +>_v : number + +const o2: { foo: undefined } = { foo: 1, ... { set foo(_v: number) { } } } +>o2 : { foo: undefined; } +>foo : undefined +>{ foo: 1, ... { set foo(_v: number) { } } } : { foo: undefined; } +>foo : number +>1 : 1 +>{ set foo(_v: number) { } } : { foo: number; } +>foo : number +>_v : number + diff --git a/tests/cases/conformance/types/spread/objectSpreadSetonlyAccessor.ts b/tests/cases/conformance/types/spread/objectSpreadSetonlyAccessor.ts new file mode 100644 index 0000000000000..1a783230b5965 --- /dev/null +++ b/tests/cases/conformance/types/spread/objectSpreadSetonlyAccessor.ts @@ -0,0 +1,4 @@ +// @strict: true +// @target: esnext +const o1: { foo: number, bar: undefined } = { foo: 1, ... { set bar(_v: number) { } } } +const o2: { foo: undefined } = { foo: 1, ... { set foo(_v: number) { } } }