Skip to content

Commit 3a2f7c0

Browse files
authored
Allow intersections of readonlys to be assignable to a readonly intersection (#28218)
* Allow intersections of readonlys to be assignable to a readonly intersection * Add real motivating react example to test suite
1 parent 0ef844f commit 3a2f7c0

9 files changed

+221
-1
lines changed

src/compiler/checker.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12036,13 +12036,15 @@ namespace ts {
1203612036
return Ternary.True;
1203712037
}
1203812038
// A source type T is related to a target type { [P in keyof T]: X } if T[P] is related to X.
12039-
if (!isGenericMappedType(source) && getConstraintTypeFromMappedType(target) === getIndexType(source)) {
12039+
if (!isGenericMappedType(source) && isRelatedTo(getConstraintTypeFromMappedType(target), getIndexType(source))) {
1204012040
const indexedAccessType = getIndexedAccessType(source, getTypeParameterFromMappedType(target));
1204112041
const templateType = getTemplateTypeFromMappedType(target);
1204212042
if (result = isRelatedTo(indexedAccessType, templateType, reportErrors)) {
1204312043
return result;
1204412044
}
1204512045
}
12046+
originalErrorInfo = errorInfo;
12047+
errorInfo = saveErrorInfo;
1204612048
}
1204712049
}
1204812050

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
//// [homomorphicMappedTypeIntersectionAssignability.ts]
2+
function f<TType>(
3+
a: { weak?: string } & Readonly<TType> & { name: "ok" },
4+
b: Readonly<TType & { name: string }>,
5+
c: Readonly<TType> & { name: string }) {
6+
c = a; // Works
7+
b = a; // Should also work
8+
}
9+
10+
11+
//// [homomorphicMappedTypeIntersectionAssignability.js]
12+
"use strict";
13+
function f(a, b, c) {
14+
c = a; // Works
15+
b = a; // Should also work
16+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
=== tests/cases/compiler/homomorphicMappedTypeIntersectionAssignability.ts ===
2+
function f<TType>(
3+
>f : Symbol(f, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 0, 0))
4+
>TType : Symbol(TType, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 0, 11))
5+
6+
a: { weak?: string } & Readonly<TType> & { name: "ok" },
7+
>a : Symbol(a, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 0, 18))
8+
>weak : Symbol(weak, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 1, 8))
9+
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
10+
>TType : Symbol(TType, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 0, 11))
11+
>name : Symbol(name, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 1, 46))
12+
13+
b: Readonly<TType & { name: string }>,
14+
>b : Symbol(b, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 1, 60))
15+
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
16+
>TType : Symbol(TType, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 0, 11))
17+
>name : Symbol(name, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 2, 25))
18+
19+
c: Readonly<TType> & { name: string }) {
20+
>c : Symbol(c, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 2, 42))
21+
>Readonly : Symbol(Readonly, Decl(lib.es5.d.ts, --, --))
22+
>TType : Symbol(TType, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 0, 11))
23+
>name : Symbol(name, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 3, 26))
24+
25+
c = a; // Works
26+
>c : Symbol(c, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 2, 42))
27+
>a : Symbol(a, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 0, 18))
28+
29+
b = a; // Should also work
30+
>b : Symbol(b, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 1, 60))
31+
>a : Symbol(a, Decl(homomorphicMappedTypeIntersectionAssignability.ts, 0, 18))
32+
}
33+
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
=== tests/cases/compiler/homomorphicMappedTypeIntersectionAssignability.ts ===
2+
function f<TType>(
3+
>f : <TType>(a: { weak?: string | undefined; } & Readonly<TType> & { name: "ok"; }, b: Readonly<TType & { name: string; }>, c: Readonly<TType> & { name: string; }) => void
4+
5+
a: { weak?: string } & Readonly<TType> & { name: "ok" },
6+
>a : { weak?: string | undefined; } & Readonly<TType> & { name: "ok"; }
7+
>weak : string | undefined
8+
>name : "ok"
9+
10+
b: Readonly<TType & { name: string }>,
11+
>b : Readonly<TType & { name: string; }>
12+
>name : string
13+
14+
c: Readonly<TType> & { name: string }) {
15+
>c : Readonly<TType> & { name: string; }
16+
>name : string
17+
18+
c = a; // Works
19+
>c = a : { weak?: string | undefined; } & Readonly<TType> & { name: "ok"; }
20+
>c : Readonly<TType> & { name: string; }
21+
>a : { weak?: string | undefined; } & Readonly<TType> & { name: "ok"; }
22+
23+
b = a; // Should also work
24+
>b = a : { weak?: string | undefined; } & Readonly<TType> & { name: "ok"; }
25+
>b : Readonly<TType & { name: string; }>
26+
>a : { weak?: string | undefined; } & Readonly<TType> & { name: "ok"; }
27+
}
28+
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
//// [reactReadonlyHOCAssignabilityReal.tsx]
2+
/// <reference path="/.lib/react16.d.ts" />
3+
import * as React from "react";
4+
5+
function myHigherOrderComponent<P>(Inner: React.ComponentClass<P & {name: string}>): React.ComponentClass<P> {
6+
return class OuterComponent extends React.Component<P> {
7+
render() {
8+
return <Inner {...this.props} name="Matt"/>;
9+
}
10+
};
11+
}
12+
13+
//// [reactReadonlyHOCAssignabilityReal.js]
14+
"use strict";
15+
var __extends = (this && this.__extends) || (function () {
16+
var extendStatics = function (d, b) {
17+
extendStatics = Object.setPrototypeOf ||
18+
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
19+
function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
20+
return extendStatics(d, b);
21+
};
22+
return function (d, b) {
23+
extendStatics(d, b);
24+
function __() { this.constructor = d; }
25+
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
26+
};
27+
})();
28+
var __assign = (this && this.__assign) || function () {
29+
__assign = Object.assign || function(t) {
30+
for (var s, i = 1, n = arguments.length; i < n; i++) {
31+
s = arguments[i];
32+
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
33+
t[p] = s[p];
34+
}
35+
return t;
36+
};
37+
return __assign.apply(this, arguments);
38+
};
39+
exports.__esModule = true;
40+
/// <reference path="react16.d.ts" />
41+
var React = require("react");
42+
function myHigherOrderComponent(Inner) {
43+
return /** @class */ (function (_super) {
44+
__extends(OuterComponent, _super);
45+
function OuterComponent() {
46+
return _super !== null && _super.apply(this, arguments) || this;
47+
}
48+
OuterComponent.prototype.render = function () {
49+
return React.createElement(Inner, __assign({}, this.props, { name: "Matt" }));
50+
};
51+
return OuterComponent;
52+
}(React.Component));
53+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
=== tests/cases/compiler/reactReadonlyHOCAssignabilityReal.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
import * as React from "react";
4+
>React : Symbol(React, Decl(reactReadonlyHOCAssignabilityReal.tsx, 1, 6))
5+
6+
function myHigherOrderComponent<P>(Inner: React.ComponentClass<P & {name: string}>): React.ComponentClass<P> {
7+
>myHigherOrderComponent : Symbol(myHigherOrderComponent, Decl(reactReadonlyHOCAssignabilityReal.tsx, 1, 31))
8+
>P : Symbol(P, Decl(reactReadonlyHOCAssignabilityReal.tsx, 3, 32))
9+
>Inner : Symbol(Inner, Decl(reactReadonlyHOCAssignabilityReal.tsx, 3, 35))
10+
>React : Symbol(React, Decl(reactReadonlyHOCAssignabilityReal.tsx, 1, 6))
11+
>ComponentClass : Symbol(React.ComponentClass, Decl(react16.d.ts, 421, 9))
12+
>P : Symbol(P, Decl(reactReadonlyHOCAssignabilityReal.tsx, 3, 32))
13+
>name : Symbol(name, Decl(reactReadonlyHOCAssignabilityReal.tsx, 3, 68))
14+
>React : Symbol(React, Decl(reactReadonlyHOCAssignabilityReal.tsx, 1, 6))
15+
>ComponentClass : Symbol(React.ComponentClass, Decl(react16.d.ts, 421, 9))
16+
>P : Symbol(P, Decl(reactReadonlyHOCAssignabilityReal.tsx, 3, 32))
17+
18+
return class OuterComponent extends React.Component<P> {
19+
>OuterComponent : Symbol(OuterComponent, Decl(reactReadonlyHOCAssignabilityReal.tsx, 4, 10))
20+
>React.Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
21+
>React : Symbol(React, Decl(reactReadonlyHOCAssignabilityReal.tsx, 1, 6))
22+
>Component : Symbol(React.Component, Decl(react16.d.ts, 345, 54), Decl(react16.d.ts, 349, 94))
23+
>P : Symbol(P, Decl(reactReadonlyHOCAssignabilityReal.tsx, 3, 32))
24+
25+
render() {
26+
>render : Symbol(OuterComponent.render, Decl(reactReadonlyHOCAssignabilityReal.tsx, 4, 60))
27+
28+
return <Inner {...this.props} name="Matt"/>;
29+
>Inner : Symbol(Inner, Decl(reactReadonlyHOCAssignabilityReal.tsx, 3, 35))
30+
>this.props : Symbol(React.Component.props, Decl(react16.d.ts, 367, 32))
31+
>this : Symbol(OuterComponent, Decl(reactReadonlyHOCAssignabilityReal.tsx, 4, 10))
32+
>props : Symbol(React.Component.props, Decl(react16.d.ts, 367, 32))
33+
>name : Symbol(name, Decl(reactReadonlyHOCAssignabilityReal.tsx, 6, 41))
34+
}
35+
};
36+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
=== tests/cases/compiler/reactReadonlyHOCAssignabilityReal.tsx ===
2+
/// <reference path="react16.d.ts" />
3+
import * as React from "react";
4+
>React : typeof React
5+
6+
function myHigherOrderComponent<P>(Inner: React.ComponentClass<P & {name: string}>): React.ComponentClass<P> {
7+
>myHigherOrderComponent : <P>(Inner: React.ComponentClass<P & { name: string; }, any>) => React.ComponentClass<P, any>
8+
>Inner : React.ComponentClass<P & { name: string; }, any>
9+
>React : any
10+
>name : string
11+
>React : any
12+
13+
return class OuterComponent extends React.Component<P> {
14+
>class OuterComponent extends React.Component<P> { render() { return <Inner {...this.props} name="Matt"/>; } } : typeof OuterComponent
15+
>OuterComponent : typeof OuterComponent
16+
>React.Component : React.Component<P, {}, any>
17+
>React : typeof React
18+
>Component : typeof React.Component
19+
20+
render() {
21+
>render : () => JSX.Element
22+
23+
return <Inner {...this.props} name="Matt"/>;
24+
><Inner {...this.props} name="Matt"/> : JSX.Element
25+
>Inner : React.ComponentClass<P & { name: string; }, any>
26+
>this.props : Readonly<{ children?: React.ReactNode; }> & Readonly<P>
27+
>this : this
28+
>props : Readonly<{ children?: React.ReactNode; }> & Readonly<P>
29+
>name : "Matt"
30+
}
31+
};
32+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// @strict: true
2+
function f<TType>(
3+
a: { weak?: string } & Readonly<TType> & { name: "ok" },
4+
b: Readonly<TType & { name: string }>,
5+
c: Readonly<TType> & { name: string }) {
6+
c = a; // Works
7+
b = a; // Should also work
8+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
// @strict: true
2+
// @jsx: react
3+
/// <reference path="/.lib/react16.d.ts" />
4+
import * as React from "react";
5+
6+
function myHigherOrderComponent<P>(Inner: React.ComponentClass<P & {name: string}>): React.ComponentClass<P> {
7+
return class OuterComponent extends React.Component<P> {
8+
render() {
9+
return <Inner {...this.props} name="Matt"/>;
10+
}
11+
};
12+
}

0 commit comments

Comments
 (0)