Skip to content

Commit 11f715c

Browse files
authored
Merge pull request #15036 from Microsoft/remove-readonly-from-spread-properties
Remove readonly from spread properties
2 parents ee52477 + 0ffe24c commit 11f715c

File tree

8 files changed

+105
-13
lines changed

8 files changed

+105
-13
lines changed

src/compiler/checker.ts

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5501,7 +5501,7 @@ namespace ts {
55015501
prop.checkFlags = templateReadonly || modifiersProp && isReadonlySymbol(modifiersProp) ? CheckFlags.Readonly : 0;
55025502
prop.type = propType;
55035503
if (propertySymbol) {
5504-
prop.mappedTypeOrigin = propertySymbol;
5504+
prop.syntheticOrigin = propertySymbol;
55055505
}
55065506
members.set(propName, prop);
55075507
}
@@ -7450,7 +7450,7 @@ namespace ts {
74507450
skippedPrivateMembers.set(rightProp.name, true);
74517451
}
74527452
else if (!isClassMethod(rightProp) && !isSetterWithoutGetter) {
7453-
members.set(rightProp.name, rightProp);
7453+
members.set(rightProp.name, getNonReadonlySymbol(rightProp));
74547454
}
74557455
}
74567456
for (const leftProp of getPropertiesOfType(left)) {
@@ -7466,7 +7466,6 @@ namespace ts {
74667466
const declarations: Declaration[] = concatenate(leftProp.declarations, rightProp.declarations);
74677467
const flags = SymbolFlags.Property | (leftProp.flags & SymbolFlags.Optional);
74687468
const result = createSymbol(flags, leftProp.name);
7469-
result.checkFlags = isReadonlySymbol(leftProp) || isReadonlySymbol(rightProp) ? CheckFlags.Readonly : 0;
74707469
result.type = getUnionType([getTypeOfSymbol(leftProp), getTypeWithFacts(rightType, TypeFacts.NEUndefined)]);
74717470
result.leftSpread = leftProp;
74727471
result.rightSpread = rightProp;
@@ -7475,12 +7474,24 @@ namespace ts {
74757474
}
74767475
}
74777476
else {
7478-
members.set(leftProp.name, leftProp);
7477+
members.set(leftProp.name, getNonReadonlySymbol(leftProp));
74797478
}
74807479
}
74817480
return createAnonymousType(undefined, members, emptyArray, emptyArray, stringIndexInfo, numberIndexInfo);
74827481
}
74837482

7483+
function getNonReadonlySymbol(prop: Symbol) {
7484+
if (!isReadonlySymbol(prop)) {
7485+
return prop;
7486+
}
7487+
const flags = SymbolFlags.Property | (prop.flags & SymbolFlags.Optional);
7488+
const result = createSymbol(flags, prop.name);
7489+
result.type = getTypeOfSymbol(prop);
7490+
result.declarations = prop.declarations;
7491+
result.syntheticOrigin = prop;
7492+
return result;
7493+
}
7494+
74847495
function isClassMethod(prop: Symbol) {
74857496
return prop.flags & SymbolFlags.Method && find(prop.declarations, decl => isClassLike(decl.parent));
74867497
}
@@ -22144,10 +22155,10 @@ namespace ts {
2214422155
else if (symbol.flags & SymbolFlags.Transient) {
2214522156
if ((symbol as SymbolLinks).leftSpread) {
2214622157
const links = symbol as SymbolLinks;
22147-
return [links.leftSpread, links.rightSpread];
22158+
return [...getRootSymbols(links.leftSpread), ...getRootSymbols(links.rightSpread)];
2214822159
}
22149-
if ((symbol as SymbolLinks).mappedTypeOrigin) {
22150-
return getRootSymbols((symbol as SymbolLinks).mappedTypeOrigin);
22160+
if ((symbol as SymbolLinks).syntheticOrigin) {
22161+
return getRootSymbols((symbol as SymbolLinks).syntheticOrigin);
2215122162
}
2215222163

2215322164
let target: Symbol;

src/compiler/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2852,7 +2852,7 @@ namespace ts {
28522852
containingType?: UnionOrIntersectionType; // Containing union or intersection type for synthetic property
28532853
leftSpread?: Symbol; // Left source for synthetic spread property
28542854
rightSpread?: Symbol; // Right source for synthetic spread property
2855-
mappedTypeOrigin?: Symbol; // For a property on a mapped type, points back to the orignal 'T' from 'keyof T'.
2855+
syntheticOrigin?: Symbol; // For a property on a mapped or spread type, points back to the original property
28562856
isDiscriminantProperty?: boolean; // True if discriminant synthetic property
28572857
resolvedExports?: SymbolTable; // Resolved exports of module
28582858
exportsChecked?: boolean; // True if exports of external module have been checked

tests/baselines/reference/objectSpread.types

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,7 @@ let getter: { a: number, c: number } =
211211
>c : number
212212

213213
{ ...op, c: 7 }
214-
>{ ...op, c: 7 } : { c: number; readonly a: number; }
214+
>{ ...op, c: 7 } : { c: number; a: number; }
215215
>op : { readonly a: number; }
216216
>c : number
217217
>7 : 7
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
//// [spreadTypeRemovesReadonly.ts]
2+
interface ReadonlyData {
3+
readonly value: string;
4+
}
5+
6+
const data: ReadonlyData = { value: 'foo' };
7+
const clone = { ...data };
8+
clone.value = 'bar';
9+
10+
11+
//// [spreadTypeRemovesReadonly.js]
12+
var __assign = (this && this.__assign) || Object.assign || function(t) {
13+
for (var s, i = 1, n = arguments.length; i < n; i++) {
14+
s = arguments[i];
15+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
16+
t[p] = s[p];
17+
}
18+
return t;
19+
};
20+
var data = { value: 'foo' };
21+
var clone = __assign({}, data);
22+
clone.value = 'bar';
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
=== tests/cases/compiler/spreadTypeRemovesReadonly.ts ===
2+
interface ReadonlyData {
3+
>ReadonlyData : Symbol(ReadonlyData, Decl(spreadTypeRemovesReadonly.ts, 0, 0))
4+
5+
readonly value: string;
6+
>value : Symbol(ReadonlyData.value, Decl(spreadTypeRemovesReadonly.ts, 0, 24))
7+
}
8+
9+
const data: ReadonlyData = { value: 'foo' };
10+
>data : Symbol(data, Decl(spreadTypeRemovesReadonly.ts, 4, 5))
11+
>ReadonlyData : Symbol(ReadonlyData, Decl(spreadTypeRemovesReadonly.ts, 0, 0))
12+
>value : Symbol(value, Decl(spreadTypeRemovesReadonly.ts, 4, 28))
13+
14+
const clone = { ...data };
15+
>clone : Symbol(clone, Decl(spreadTypeRemovesReadonly.ts, 5, 5))
16+
>data : Symbol(data, Decl(spreadTypeRemovesReadonly.ts, 4, 5))
17+
18+
clone.value = 'bar';
19+
>clone.value : Symbol(value, Decl(spreadTypeRemovesReadonly.ts, 0, 24))
20+
>clone : Symbol(clone, Decl(spreadTypeRemovesReadonly.ts, 5, 5))
21+
>value : Symbol(value, Decl(spreadTypeRemovesReadonly.ts, 0, 24))
22+
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
=== tests/cases/compiler/spreadTypeRemovesReadonly.ts ===
2+
interface ReadonlyData {
3+
>ReadonlyData : ReadonlyData
4+
5+
readonly value: string;
6+
>value : string
7+
}
8+
9+
const data: ReadonlyData = { value: 'foo' };
10+
>data : ReadonlyData
11+
>ReadonlyData : ReadonlyData
12+
>{ value: 'foo' } : { value: string; }
13+
>value : string
14+
>'foo' : "foo"
15+
16+
const clone = { ...data };
17+
>clone : { value: string; }
18+
>{ ...data } : { value: string; }
19+
>data : ReadonlyData
20+
21+
clone.value = 'bar';
22+
>clone.value = 'bar' : "bar"
23+
>clone.value : string
24+
>clone : { value: string; }
25+
>value : string
26+
>'bar' : "bar"
27+
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
interface ReadonlyData {
2+
readonly value: string;
3+
}
4+
5+
const data: ReadonlyData = { value: 'foo' };
6+
const clone = { ...data };
7+
clone.value = 'bar';
Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
/// <reference path='fourslash.ts'/>
22

3-
////interface A1 { [|{| "isWriteAccess": true, "isDefinition": true |}a|]: string };
3+
////interface A1 { readonly [|{| "isWriteAccess": true, "isDefinition": true |}a|]: string };
44
////interface A2 { [|{| "isWriteAccess": true, "isDefinition": true |}a|]?: number };
55
////let a1: A1;
66
////let a2: A2;
77
////let a12 = { ...a1, ...a2 };
88
////a12.[|a|];
9+
////a1.[|a|];
910
const ranges = test.ranges();
10-
const [r0, r1, r2] = ranges;
11+
const [r0, r1, r2, r3] = ranges;
1112

1213
// members of spread types only refer to themselves and the resulting property
13-
verify.referenceGroups(r0, [{ definition: "(property) A1.a: string", ranges: [r0, r2] }]);
14+
verify.referenceGroups(r0, [{ definition: "(property) A1.a: string", ranges: [r0, r2, r3] }]);
1415
verify.referenceGroups(r1, [{ definition: "(property) A2.a: number", ranges: [r1, r2] }]);
1516

1617
// but the resulting property refers to everything
1718
verify.referenceGroups(r2, [
18-
{ definition: "(property) A1.a: string", ranges: [r0] },
19+
{ definition: "(property) A1.a: string", ranges: [r0, r3] },
1920
{ definition: "(property) A2.a: number", ranges: [r1] },
2021
{ definition: "(property) a: string | number", ranges: [r2] }
2122
]);
23+
24+
verify.referenceGroups(r3, [{ definition: "(property) A1.a: string", ranges: [r0, r2, r3] }]);

0 commit comments

Comments
 (0)