mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-10 15:25:54 -06:00
Set-only accessors spread to undefined (#28213)
* 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
* Fix isSpreadableProperty oversimplification
This commit is contained in:
parent
60efb65931
commit
64ff195426
@ -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;
|
||||
|
||||
@ -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() { } }
|
||||
|
||||
@ -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() { } }
|
||||
|
||||
@ -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
|
||||
|
||||
9
tests/baselines/reference/objectSpreadSetonlyAccessor.js
Normal file
9
tests/baselines/reference/objectSpreadSetonlyAccessor.js
Normal file
@ -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) { } } };
|
||||
@ -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))
|
||||
|
||||
22
tests/baselines/reference/objectSpreadSetonlyAccessor.types
Normal file
22
tests/baselines/reference/objectSpreadSetonlyAccessor.types
Normal file
@ -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
|
||||
|
||||
@ -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) { } } }
|
||||
Loading…
x
Reference in New Issue
Block a user