Skip to content

Commit ce11e2c

Browse files
committed
Set-only accessors spread to undefined
Previously they were skipped. The runtime behaviour is to create a property of type undefined, unlike (for example) spreading numbers or other primitives. So now spreading a set-only accessor creates a property of type undefined: ```ts const o: { foo: undefined } = { ...{ set foo(v: number) { } } } ``` Notably, `o.foo: undefined` not `number`. Fixes #26337
1 parent 60efb65 commit ce11e2c

8 files changed

+70
-17
lines changed

src/compiler/checker.ts

+9-9
Original file line numberDiff line numberDiff line change
@@ -4608,7 +4608,7 @@ namespace ts {
46084608
if (!names.has(prop.escapedName)
46094609
&& !(getDeclarationModifierFlagsFromSymbol(prop) & (ModifierFlags.Private | ModifierFlags.Protected))
46104610
&& isSpreadableProperty(prop)) {
4611-
members.set(prop.escapedName, getNonReadonlySymbol(prop));
4611+
members.set(prop.escapedName, getSpreadSymbol(prop));
46124612
}
46134613
}
46144614
const stringIndexInfo = getIndexInfoOfType(source, IndexKind.String);
@@ -9887,7 +9887,7 @@ namespace ts {
98879887
skippedPrivateMembers.set(rightProp.escapedName, true);
98889888
}
98899889
else if (isSpreadableProperty(rightProp)) {
9890-
members.set(rightProp.escapedName, getNonReadonlySymbol(rightProp));
9890+
members.set(rightProp.escapedName, getSpreadSymbol(rightProp));
98919891
}
98929892
}
98939893

@@ -9911,7 +9911,7 @@ namespace ts {
99119911
}
99129912
}
99139913
else {
9914-
members.set(leftProp.escapedName, getNonReadonlySymbol(leftProp));
9914+
members.set(leftProp.escapedName, getSpreadSymbol(leftProp));
99159915
}
99169916
}
99179917

@@ -9929,18 +9929,18 @@ namespace ts {
99299929

99309930
/** We approximate own properties as non-methods plus methods that are inside the object literal */
99319931
function isSpreadableProperty(prop: Symbol): boolean {
9932-
return prop.flags & (SymbolFlags.Method | SymbolFlags.GetAccessor)
9933-
? !prop.declarations.some(decl => isClassLike(decl.parent))
9934-
: !(prop.flags & SymbolFlags.SetAccessor); // Setter without getter is not spreadable
9932+
return !(prop.flags & SymbolFlags.Method) || !prop.declarations.some(decl => isClassLike(decl.parent));
99359933
}
99369934

9937-
function getNonReadonlySymbol(prop: Symbol) {
9938-
if (!isReadonlySymbol(prop)) {
9935+
function getSpreadSymbol(prop: Symbol) {
9936+
const isReadonly = isReadonlySymbol(prop);
9937+
const isSetonlyAccessor = prop.flags & SymbolFlags.SetAccessor && !(prop.flags & SymbolFlags.GetAccessor);
9938+
if (!isReadonly && !isSetonlyAccessor) {
99399939
return prop;
99409940
}
99419941
const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional);
99429942
const result = createSymbol(flags, prop.escapedName);
9943-
result.type = getTypeOfSymbol(prop);
9943+
result.type = isSetonlyAccessor ? undefinedType : getTypeOfSymbol(prop);
99449944
result.declarations = prop.declarations;
99459945
result.nameType = prop.nameType;
99469946
result.syntheticOrigin = prop;

tests/baselines/reference/objectSpreadNegative.errors.txt

+3-3
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ tests/cases/conformance/types/spread/objectSpreadNegative.ts(34,20): error TS269
1313
tests/cases/conformance/types/spread/objectSpreadNegative.ts(36,20): error TS2698: Spread types may only be created from object types.
1414
tests/cases/conformance/types/spread/objectSpreadNegative.ts(38,19): error TS2698: Spread types may only be created from object types.
1515
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.
16-
tests/cases/conformance/types/spread/objectSpreadNegative.ts(47,12): error TS2339: Property 'b' does not exist on type '{}'.
16+
tests/cases/conformance/types/spread/objectSpreadNegative.ts(47,1): error TS2322: Type '12' is not assignable to type 'undefined'.
1717
tests/cases/conformance/types/spread/objectSpreadNegative.ts(53,9): error TS2339: Property 'm' does not exist on type '{ p: number; }'.
1818
tests/cases/conformance/types/spread/objectSpreadNegative.ts(58,11): error TS2339: Property 'a' does not exist on type '{}'.
1919
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
9595
// write-only properties get skipped
9696
let setterOnly = { ...{ set b (bad: number) { } } };
9797
setterOnly.b = 12; // error, 'b' does not exist
98-
~
99-
!!! error TS2339: Property 'b' does not exist on type '{}'.
98+
~~~~~~~~~~~~
99+
!!! error TS2322: Type '12' is not assignable to type 'undefined'.
100100

101101
// methods are skipped because they aren't enumerable
102102
class C { p = 1; m() { } }

tests/baselines/reference/objectSpreadNegative.symbols

+2
Original file line numberDiff line numberDiff line change
@@ -132,7 +132,9 @@ let setterOnly = { ...{ set b (bad: number) { } } };
132132
>bad : Symbol(bad, Decl(objectSpreadNegative.ts, 45, 31))
133133

134134
setterOnly.b = 12; // error, 'b' does not exist
135+
>setterOnly.b : Symbol(b, Decl(objectSpreadNegative.ts, 45, 23))
135136
>setterOnly : Symbol(setterOnly, Decl(objectSpreadNegative.ts, 45, 3))
137+
>b : Symbol(b, Decl(objectSpreadNegative.ts, 45, 23))
136138

137139
// methods are skipped because they aren't enumerable
138140
class C { p = 1; m() { } }

tests/baselines/reference/objectSpreadNegative.types

+5-5
Original file line numberDiff line numberDiff line change
@@ -173,17 +173,17 @@ spreadFunc(); // error, no call signature
173173

174174
// write-only properties get skipped
175175
let setterOnly = { ...{ set b (bad: number) { } } };
176-
>setterOnly : {}
177-
>{ ...{ set b (bad: number) { } } } : {}
176+
>setterOnly : { b: undefined; }
177+
>{ ...{ set b (bad: number) { } } } : { b: undefined; }
178178
>{ set b (bad: number) { } } : { b: number; }
179179
>b : number
180180
>bad : number
181181

182182
setterOnly.b = 12; // error, 'b' does not exist
183183
>setterOnly.b = 12 : 12
184-
>setterOnly.b : any
185-
>setterOnly : {}
186-
>b : any
184+
>setterOnly.b : undefined
185+
>setterOnly : { b: undefined; }
186+
>b : undefined
187187
>12 : 12
188188

189189
// methods are skipped because they aren't enumerable
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
//// [objectSpreadSetonlyAccessor.ts]
2+
const o1: { foo: number, bar: undefined } = { foo: 1, ... { set bar(_v: number) { } } }
3+
const o2: { foo: undefined } = { foo: 1, ... { set foo(_v: number) { } } }
4+
5+
6+
//// [objectSpreadSetonlyAccessor.js]
7+
"use strict";
8+
const o1 = { foo: 1, ...{ set bar(_v) { } } };
9+
const o2 = { foo: 1, ...{ set foo(_v) { } } };
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
=== tests/cases/conformance/types/spread/objectSpreadSetonlyAccessor.ts ===
2+
const o1: { foo: number, bar: undefined } = { foo: 1, ... { set bar(_v: number) { } } }
3+
>o1 : Symbol(o1, Decl(objectSpreadSetonlyAccessor.ts, 0, 5))
4+
>foo : Symbol(foo, Decl(objectSpreadSetonlyAccessor.ts, 0, 11))
5+
>bar : Symbol(bar, Decl(objectSpreadSetonlyAccessor.ts, 0, 24))
6+
>foo : Symbol(foo, Decl(objectSpreadSetonlyAccessor.ts, 0, 45))
7+
>bar : Symbol(bar, Decl(objectSpreadSetonlyAccessor.ts, 0, 59))
8+
>_v : Symbol(_v, Decl(objectSpreadSetonlyAccessor.ts, 0, 68))
9+
10+
const o2: { foo: undefined } = { foo: 1, ... { set foo(_v: number) { } } }
11+
>o2 : Symbol(o2, Decl(objectSpreadSetonlyAccessor.ts, 1, 5))
12+
>foo : Symbol(foo, Decl(objectSpreadSetonlyAccessor.ts, 1, 11))
13+
>foo : Symbol(foo, Decl(objectSpreadSetonlyAccessor.ts, 1, 32))
14+
>foo : Symbol(foo, Decl(objectSpreadSetonlyAccessor.ts, 1, 46))
15+
>_v : Symbol(_v, Decl(objectSpreadSetonlyAccessor.ts, 1, 55))
16+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
=== tests/cases/conformance/types/spread/objectSpreadSetonlyAccessor.ts ===
2+
const o1: { foo: number, bar: undefined } = { foo: 1, ... { set bar(_v: number) { } } }
3+
>o1 : { foo: number; bar: undefined; }
4+
>foo : number
5+
>bar : undefined
6+
>{ foo: 1, ... { set bar(_v: number) { } } } : { bar: undefined; foo: number; }
7+
>foo : number
8+
>1 : 1
9+
>{ set bar(_v: number) { } } : { bar: number; }
10+
>bar : number
11+
>_v : number
12+
13+
const o2: { foo: undefined } = { foo: 1, ... { set foo(_v: number) { } } }
14+
>o2 : { foo: undefined; }
15+
>foo : undefined
16+
>{ foo: 1, ... { set foo(_v: number) { } } } : { foo: undefined; }
17+
>foo : number
18+
>1 : 1
19+
>{ set foo(_v: number) { } } : { foo: number; }
20+
>foo : number
21+
>_v : number
22+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// @strict: true
2+
// @target: esnext
3+
const o1: { foo: number, bar: undefined } = { foo: 1, ... { set bar(_v: number) { } } }
4+
const o2: { foo: undefined } = { foo: 1, ... { set foo(_v: number) { } } }

0 commit comments

Comments
 (0)