Skip to content

Commit 3fa71c0

Browse files
committed
Make element accesses by template literals on types with template indexes work like you expect
1 parent 026ad21 commit 3fa71c0

6 files changed

+559
-1
lines changed

src/compiler/checker.ts

+29-1
Original file line numberDiff line numberDiff line change
@@ -21814,6 +21814,7 @@ namespace ts {
2181421814
(<BinaryExpression>parent.parent).operatorToken.kind === SyntaxKind.EqualsToken &&
2181521815
(<BinaryExpression>parent.parent).left === parent &&
2181621816
!isAssignmentTarget(parent.parent) &&
21817+
!isTemplateLiteral((<ElementAccessExpression>parent).argumentExpression) &&
2181721818
isTypeAssignableToKind(getTypeOfExpression((<ElementAccessExpression>parent).argumentExpression), TypeFlags.NumberLike);
2181821819
return isLengthPushOrUnshift || isElementAssignment;
2181921820
}
@@ -31094,6 +31095,33 @@ namespace ts {
3109431095
return getUnionType([type1, type2], UnionReduction.Subtype);
3109531096
}
3109631097

31098+
function isOrIsConstrainedToTemplate(type: Type | undefined): boolean {
31099+
if (!type) return false;
31100+
if (type.flags & TypeFlags.TemplateLiteral) {
31101+
return true;
31102+
}
31103+
if (type.flags & TypeFlags.UnionOrIntersection) {
31104+
return some((type as UnionOrIntersectionType).types, isOrIsConstrainedToTemplate);
31105+
}
31106+
if (type.flags & TypeFlags.InstantiableNonPrimitive) {
31107+
const constraint = getBaseConstraintOfType(type);
31108+
return constraint ? isOrIsConstrainedToTemplate(constraint) : false;
31109+
}
31110+
return false;
31111+
}
31112+
31113+
function isTemplateLiteralContext(node: Node): boolean {
31114+
const parent = node.parent;
31115+
switch (parent.kind) {
31116+
case SyntaxKind.ParenthesizedExpression:
31117+
return isTemplateLiteralContext(parent);
31118+
case SyntaxKind.ElementAccessExpression:
31119+
return (parent as ElementAccessExpression).argumentExpression === node &&
31120+
isOrIsConstrainedToTemplate(getIndexType(getTypeOfExpression((parent as ElementAccessExpression).expression)));
31121+
}
31122+
return false;
31123+
}
31124+
3109731125
function checkTemplateExpression(node: TemplateExpression): Type {
3109831126
const texts = [node.head.text];
3109931127
const types = [];
@@ -31105,7 +31133,7 @@ namespace ts {
3110531133
texts.push(span.literal.text);
3110631134
types.push(isTypeAssignableTo(type, templateConstraintType) ? type : stringType);
3110731135
}
31108-
return isConstContext(node) ? getTemplateLiteralType(texts, types) : stringType;
31136+
return isConstContext(node) || isTemplateLiteralContext(node) ? getTemplateLiteralType(texts, types) : stringType;
3110931137
}
3111031138

3111131139
function getContextNode(node: Expression): Node {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
tests/cases/conformance/types/members/typesMembersPatternLiteralIndexes.ts(8,9): error TS2322: Type 'boolean' is not assignable to type 'string'.
2+
tests/cases/conformance/types/members/typesMembersPatternLiteralIndexes.ts(12,9): error TS2322: Type '{ "my-prop": { whatever: string; }; "aria-disabled": string; }' is not assignable to type 'AriaProps'.
3+
Object literal may only specify known properties, and '"my-prop"' does not exist in type 'AriaProps'.
4+
tests/cases/conformance/types/members/typesMembersPatternLiteralIndexes.ts(23,9): error TS2322: Type 'boolean' is not assignable to type 'string'.
5+
6+
7+
==== tests/cases/conformance/types/members/typesMembersPatternLiteralIndexes.ts (3 errors) ====
8+
function f1() {
9+
interface AriaProps {
10+
[x: `aria-${string}`]: string;
11+
}
12+
13+
const x: AriaProps = {
14+
"my-prop": {"whatever": "yes"},
15+
"aria-disabled": false // error
16+
~~~~~~~~~~~~~~~
17+
!!! error TS2322: Type 'boolean' is not assignable to type 'string'.
18+
!!! related TS6501 tests/cases/conformance/types/members/typesMembersPatternLiteralIndexes.ts:3:9: The expected type comes from this index signature.
19+
};
20+
21+
const y: AriaProps = {
22+
"my-prop": {"whatever": "yes"}, // excess
23+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
24+
!!! error TS2322: Type '{ "my-prop": { whatever: string; }; "aria-disabled": string; }' is not assignable to type 'AriaProps'.
25+
!!! error TS2322: Object literal may only specify known properties, and '"my-prop"' does not exist in type 'AriaProps'.
26+
"aria-disabled": "false" // OK
27+
};
28+
29+
const z: AriaProps & { [x: string]: unknown } = {
30+
"my-prop": {"whatever": "yes"}, // OK
31+
"aria-disabled": "false" // OK
32+
};
33+
34+
const a: AriaProps & { [x: string]: unknown } = {
35+
"my-prop": {"whatever": "yes"}, // OK
36+
"aria-disabled": false // error
37+
~~~~~~~~~~~~~~~
38+
!!! error TS2322: Type 'boolean' is not assignable to type 'string'.
39+
!!! related TS6501 tests/cases/conformance/types/members/typesMembersPatternLiteralIndexes.ts:3:9: The expected type comes from this index signature.
40+
};
41+
}
42+
43+
function f2() {
44+
interface SlotProxy<T> {
45+
readonly [idx: number]: T;
46+
[idx: `getSlot${number}`]: () => this[number];
47+
[idx: `setSlot${number}`]: (obj: this[number]) => this;
48+
}
49+
50+
const obj = makeSlotProxy([{x: 12}, {y: 21}]);
51+
52+
const obj2 = obj.setSlot2({x: 12}).setSlot0({y: 12}).getSlot1();
53+
54+
function makeSlotProxy<T>(x: T[]): SlotProxy<T> {
55+
const result: SlotProxy<T> = {};
56+
for (let i = 0; i < x.length; i++) {
57+
Object.defineProperty(result, i, { get() { return x[i]; } });
58+
result[`getSlot${i}`] = () => x[i];
59+
result[`setSlot${i}`] = (arg) => (x[i] = arg, result);
60+
}
61+
return result;
62+
}
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
//// [typesMembersPatternLiteralIndexes.ts]
2+
function f1() {
3+
interface AriaProps {
4+
[x: `aria-${string}`]: string;
5+
}
6+
7+
const x: AriaProps = {
8+
"my-prop": {"whatever": "yes"},
9+
"aria-disabled": false // error
10+
};
11+
12+
const y: AriaProps = {
13+
"my-prop": {"whatever": "yes"}, // excess
14+
"aria-disabled": "false" // OK
15+
};
16+
17+
const z: AriaProps & { [x: string]: unknown } = {
18+
"my-prop": {"whatever": "yes"}, // OK
19+
"aria-disabled": "false" // OK
20+
};
21+
22+
const a: AriaProps & { [x: string]: unknown } = {
23+
"my-prop": {"whatever": "yes"}, // OK
24+
"aria-disabled": false // error
25+
};
26+
}
27+
28+
function f2() {
29+
interface SlotProxy<T> {
30+
readonly [idx: number]: T;
31+
[idx: `getSlot${number}`]: () => this[number];
32+
[idx: `setSlot${number}`]: (obj: this[number]) => this;
33+
}
34+
35+
const obj = makeSlotProxy([{x: 12}, {y: 21}]);
36+
37+
const obj2 = obj.setSlot2({x: 12}).setSlot0({y: 12}).getSlot1();
38+
39+
function makeSlotProxy<T>(x: T[]): SlotProxy<T> {
40+
const result: SlotProxy<T> = {};
41+
for (let i = 0; i < x.length; i++) {
42+
Object.defineProperty(result, i, { get() { return x[i]; } });
43+
result[`getSlot${i}`] = () => x[i];
44+
result[`setSlot${i}`] = (arg) => (x[i] = arg, result);
45+
}
46+
return result;
47+
}
48+
}
49+
50+
//// [typesMembersPatternLiteralIndexes.js]
51+
"use strict";
52+
function f1() {
53+
var x = {
54+
"my-prop": { "whatever": "yes" },
55+
"aria-disabled": false // error
56+
};
57+
var y = {
58+
"my-prop": { "whatever": "yes" },
59+
"aria-disabled": "false" // OK
60+
};
61+
var z = {
62+
"my-prop": { "whatever": "yes" },
63+
"aria-disabled": "false" // OK
64+
};
65+
var a = {
66+
"my-prop": { "whatever": "yes" },
67+
"aria-disabled": false // error
68+
};
69+
}
70+
function f2() {
71+
var obj = makeSlotProxy([{ x: 12 }, { y: 21 }]);
72+
var obj2 = obj.setSlot2({ x: 12 }).setSlot0({ y: 12 }).getSlot1();
73+
function makeSlotProxy(x) {
74+
var result = {};
75+
var _loop_1 = function (i) {
76+
Object.defineProperty(result, i, { get: function () { return x[i]; } });
77+
result["getSlot" + i] = function () { return x[i]; };
78+
result["setSlot" + i] = function (arg) { return (x[i] = arg, result); };
79+
};
80+
for (var i = 0; i < x.length; i++) {
81+
_loop_1(i);
82+
}
83+
return result;
84+
}
85+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
=== tests/cases/conformance/types/members/typesMembersPatternLiteralIndexes.ts ===
2+
function f1() {
3+
>f1 : Symbol(f1, Decl(typesMembersPatternLiteralIndexes.ts, 0, 0))
4+
5+
interface AriaProps {
6+
>AriaProps : Symbol(AriaProps, Decl(typesMembersPatternLiteralIndexes.ts, 0, 15))
7+
8+
[x: `aria-${string}`]: string;
9+
>x : Symbol(x, Decl(typesMembersPatternLiteralIndexes.ts, 2, 9))
10+
}
11+
12+
const x: AriaProps = {
13+
>x : Symbol(x, Decl(typesMembersPatternLiteralIndexes.ts, 5, 9))
14+
>AriaProps : Symbol(AriaProps, Decl(typesMembersPatternLiteralIndexes.ts, 0, 15))
15+
16+
"my-prop": {"whatever": "yes"},
17+
>"my-prop" : Symbol("my-prop", Decl(typesMembersPatternLiteralIndexes.ts, 5, 26))
18+
>"whatever" : Symbol("whatever", Decl(typesMembersPatternLiteralIndexes.ts, 6, 20))
19+
20+
"aria-disabled": false // error
21+
>"aria-disabled" : Symbol("aria-disabled", Decl(typesMembersPatternLiteralIndexes.ts, 6, 39))
22+
23+
};
24+
25+
const y: AriaProps = {
26+
>y : Symbol(y, Decl(typesMembersPatternLiteralIndexes.ts, 10, 9))
27+
>AriaProps : Symbol(AriaProps, Decl(typesMembersPatternLiteralIndexes.ts, 0, 15))
28+
29+
"my-prop": {"whatever": "yes"}, // excess
30+
>"my-prop" : Symbol("my-prop", Decl(typesMembersPatternLiteralIndexes.ts, 10, 26))
31+
>"whatever" : Symbol("whatever", Decl(typesMembersPatternLiteralIndexes.ts, 11, 20))
32+
33+
"aria-disabled": "false" // OK
34+
>"aria-disabled" : Symbol("aria-disabled", Decl(typesMembersPatternLiteralIndexes.ts, 11, 39))
35+
36+
};
37+
38+
const z: AriaProps & { [x: string]: unknown } = {
39+
>z : Symbol(z, Decl(typesMembersPatternLiteralIndexes.ts, 15, 9))
40+
>AriaProps : Symbol(AriaProps, Decl(typesMembersPatternLiteralIndexes.ts, 0, 15))
41+
>x : Symbol(x, Decl(typesMembersPatternLiteralIndexes.ts, 15, 28))
42+
43+
"my-prop": {"whatever": "yes"}, // OK
44+
>"my-prop" : Symbol("my-prop", Decl(typesMembersPatternLiteralIndexes.ts, 15, 53))
45+
>"whatever" : Symbol("whatever", Decl(typesMembersPatternLiteralIndexes.ts, 16, 20))
46+
47+
"aria-disabled": "false" // OK
48+
>"aria-disabled" : Symbol("aria-disabled", Decl(typesMembersPatternLiteralIndexes.ts, 16, 39))
49+
50+
};
51+
52+
const a: AriaProps & { [x: string]: unknown } = {
53+
>a : Symbol(a, Decl(typesMembersPatternLiteralIndexes.ts, 20, 9))
54+
>AriaProps : Symbol(AriaProps, Decl(typesMembersPatternLiteralIndexes.ts, 0, 15))
55+
>x : Symbol(x, Decl(typesMembersPatternLiteralIndexes.ts, 20, 28))
56+
57+
"my-prop": {"whatever": "yes"}, // OK
58+
>"my-prop" : Symbol("my-prop", Decl(typesMembersPatternLiteralIndexes.ts, 20, 53))
59+
>"whatever" : Symbol("whatever", Decl(typesMembersPatternLiteralIndexes.ts, 21, 20))
60+
61+
"aria-disabled": false // error
62+
>"aria-disabled" : Symbol("aria-disabled", Decl(typesMembersPatternLiteralIndexes.ts, 21, 39))
63+
64+
};
65+
}
66+
67+
function f2() {
68+
>f2 : Symbol(f2, Decl(typesMembersPatternLiteralIndexes.ts, 24, 1))
69+
70+
interface SlotProxy<T> {
71+
>SlotProxy : Symbol(SlotProxy, Decl(typesMembersPatternLiteralIndexes.ts, 26, 15))
72+
>T : Symbol(T, Decl(typesMembersPatternLiteralIndexes.ts, 27, 24))
73+
74+
readonly [idx: number]: T;
75+
>idx : Symbol(idx, Decl(typesMembersPatternLiteralIndexes.ts, 28, 18))
76+
>T : Symbol(T, Decl(typesMembersPatternLiteralIndexes.ts, 27, 24))
77+
78+
[idx: `getSlot${number}`]: () => this[number];
79+
>idx : Symbol(idx, Decl(typesMembersPatternLiteralIndexes.ts, 29, 9))
80+
81+
[idx: `setSlot${number}`]: (obj: this[number]) => this;
82+
>idx : Symbol(idx, Decl(typesMembersPatternLiteralIndexes.ts, 30, 9))
83+
>obj : Symbol(obj, Decl(typesMembersPatternLiteralIndexes.ts, 30, 36))
84+
}
85+
86+
const obj = makeSlotProxy([{x: 12}, {y: 21}]);
87+
>obj : Symbol(obj, Decl(typesMembersPatternLiteralIndexes.ts, 33, 9))
88+
>makeSlotProxy : Symbol(makeSlotProxy, Decl(typesMembersPatternLiteralIndexes.ts, 35, 68))
89+
>x : Symbol(x, Decl(typesMembersPatternLiteralIndexes.ts, 33, 32))
90+
>y : Symbol(y, Decl(typesMembersPatternLiteralIndexes.ts, 33, 41))
91+
92+
const obj2 = obj.setSlot2({x: 12}).setSlot0({y: 12}).getSlot1();
93+
>obj2 : Symbol(obj2, Decl(typesMembersPatternLiteralIndexes.ts, 35, 9))
94+
>obj : Symbol(obj, Decl(typesMembersPatternLiteralIndexes.ts, 33, 9))
95+
>x : Symbol(x, Decl(typesMembersPatternLiteralIndexes.ts, 35, 31))
96+
>y : Symbol(y, Decl(typesMembersPatternLiteralIndexes.ts, 35, 49))
97+
98+
function makeSlotProxy<T>(x: T[]): SlotProxy<T> {
99+
>makeSlotProxy : Symbol(makeSlotProxy, Decl(typesMembersPatternLiteralIndexes.ts, 35, 68))
100+
>T : Symbol(T, Decl(typesMembersPatternLiteralIndexes.ts, 37, 27))
101+
>x : Symbol(x, Decl(typesMembersPatternLiteralIndexes.ts, 37, 30))
102+
>T : Symbol(T, Decl(typesMembersPatternLiteralIndexes.ts, 37, 27))
103+
>SlotProxy : Symbol(SlotProxy, Decl(typesMembersPatternLiteralIndexes.ts, 26, 15))
104+
>T : Symbol(T, Decl(typesMembersPatternLiteralIndexes.ts, 37, 27))
105+
106+
const result: SlotProxy<T> = {};
107+
>result : Symbol(result, Decl(typesMembersPatternLiteralIndexes.ts, 38, 13))
108+
>SlotProxy : Symbol(SlotProxy, Decl(typesMembersPatternLiteralIndexes.ts, 26, 15))
109+
>T : Symbol(T, Decl(typesMembersPatternLiteralIndexes.ts, 37, 27))
110+
111+
for (let i = 0; i < x.length; i++) {
112+
>i : Symbol(i, Decl(typesMembersPatternLiteralIndexes.ts, 39, 16))
113+
>i : Symbol(i, Decl(typesMembersPatternLiteralIndexes.ts, 39, 16))
114+
>x.length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
115+
>x : Symbol(x, Decl(typesMembersPatternLiteralIndexes.ts, 37, 30))
116+
>length : Symbol(Array.length, Decl(lib.es5.d.ts, --, --))
117+
>i : Symbol(i, Decl(typesMembersPatternLiteralIndexes.ts, 39, 16))
118+
119+
Object.defineProperty(result, i, { get() { return x[i]; } });
120+
>Object.defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --))
121+
>Object : Symbol(Object, Decl(lib.es5.d.ts, --, --), Decl(lib.es5.d.ts, --, --))
122+
>defineProperty : Symbol(ObjectConstructor.defineProperty, Decl(lib.es5.d.ts, --, --))
123+
>result : Symbol(result, Decl(typesMembersPatternLiteralIndexes.ts, 38, 13))
124+
>i : Symbol(i, Decl(typesMembersPatternLiteralIndexes.ts, 39, 16))
125+
>get : Symbol(get, Decl(typesMembersPatternLiteralIndexes.ts, 40, 46))
126+
>x : Symbol(x, Decl(typesMembersPatternLiteralIndexes.ts, 37, 30))
127+
>i : Symbol(i, Decl(typesMembersPatternLiteralIndexes.ts, 39, 16))
128+
129+
result[`getSlot${i}`] = () => x[i];
130+
>result : Symbol(result, Decl(typesMembersPatternLiteralIndexes.ts, 38, 13))
131+
>i : Symbol(i, Decl(typesMembersPatternLiteralIndexes.ts, 39, 16))
132+
>x : Symbol(x, Decl(typesMembersPatternLiteralIndexes.ts, 37, 30))
133+
>i : Symbol(i, Decl(typesMembersPatternLiteralIndexes.ts, 39, 16))
134+
135+
result[`setSlot${i}`] = (arg) => (x[i] = arg, result);
136+
>result : Symbol(result, Decl(typesMembersPatternLiteralIndexes.ts, 38, 13))
137+
>i : Symbol(i, Decl(typesMembersPatternLiteralIndexes.ts, 39, 16))
138+
>arg : Symbol(arg, Decl(typesMembersPatternLiteralIndexes.ts, 42, 37))
139+
>x : Symbol(x, Decl(typesMembersPatternLiteralIndexes.ts, 37, 30))
140+
>i : Symbol(i, Decl(typesMembersPatternLiteralIndexes.ts, 39, 16))
141+
>arg : Symbol(arg, Decl(typesMembersPatternLiteralIndexes.ts, 42, 37))
142+
>result : Symbol(result, Decl(typesMembersPatternLiteralIndexes.ts, 38, 13))
143+
}
144+
return result;
145+
>result : Symbol(result, Decl(typesMembersPatternLiteralIndexes.ts, 38, 13))
146+
}
147+
}

0 commit comments

Comments
 (0)