Skip to content

Commit 32e60a9

Browse files
authored
Explicitly typed special assignments are context sensitive (#25619)
* Explicitly typed js assignments: context sensitive Explicitly typed special assignments should be context sensitive if they have an explicit type tag. Previously no special assignments were context sensitive because they are declarations, and in the common, untyped, case we inspect the right side of the assignment to get the type of the left side, and inspect the right side of the assignment to get the type of the left side, etc etc. Note that some special assignments still return `any` from checkExpression, so still don't get the right type. Fixes #25571 * Change prototype property handling+update bselines * Fix indentation in test * Update baselines
1 parent f500289 commit 32e60a9

7 files changed

+579
-22
lines changed

src/compiler/checker.ts

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15785,22 +15785,22 @@ namespace ts {
1578515785
}
1578615786

1578715787
// In an assignment expression, the right operand is contextually typed by the type of the left operand.
15788-
// Don't do this for special property assignments to avoid circularity.
15788+
// Don't do this for special property assignments unless there is a type tag on the assignment, to avoid circularity from checking the right operand.
1578915789
function isContextSensitiveAssignment(binaryExpression: BinaryExpression): boolean {
1579015790
const kind = getSpecialPropertyAssignmentKind(binaryExpression);
1579115791
switch (kind) {
1579215792
case SpecialPropertyAssignmentKind.None:
1579315793
return true;
1579415794
case SpecialPropertyAssignmentKind.Property:
15795-
// If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration.
15796-
// See `bindStaticPropertyAssignment` in `binder.ts`.
15797-
return !binaryExpression.left.symbol;
1579815795
case SpecialPropertyAssignmentKind.ExportsProperty:
15799-
case SpecialPropertyAssignmentKind.ModuleExports:
15796+
case SpecialPropertyAssignmentKind.Prototype:
1580015797
case SpecialPropertyAssignmentKind.PrototypeProperty:
15798+
// If `binaryExpression.left` was assigned a symbol, then this is a new declaration; otherwise it is an assignment to an existing declaration.
15799+
// See `bindStaticPropertyAssignment` in `binder.ts`.
15800+
return !binaryExpression.left.symbol || binaryExpression.left.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.left.symbol.valueDeclaration);
1580115801
case SpecialPropertyAssignmentKind.ThisProperty:
15802-
case SpecialPropertyAssignmentKind.Prototype:
15803-
return false;
15802+
case SpecialPropertyAssignmentKind.ModuleExports:
15803+
return !binaryExpression.symbol || binaryExpression.symbol.valueDeclaration && !!getJSDocTypeTag(binaryExpression.symbol.valueDeclaration);
1580415804
default:
1580515805
return Debug.assertNever(kind);
1580615806
}

tests/baselines/reference/conflictingCommonJSES2015Exports.types

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ export function abc(a, b, c) { return 5; }
77
>5 : 5
88

99
module.exports = { abc };
10-
>module.exports = { abc } : { [x: string]: any; abc: (a: any, b: any, c: any) => number; }
10+
>module.exports = { abc } : { abc: (a: any, b: any, c: any) => number; }
1111
>module.exports : any
1212
>module : any
1313
>exports : any
14-
>{ abc } : { [x: string]: any; abc: (a: any, b: any, c: any) => number; }
14+
>{ abc } : { abc: (a: any, b: any, c: any) => number; }
1515
>abc : (a: any, b: any, c: any) => number
1616

1717
=== tests/cases/conformance/salsa/use.js ===
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
tests/cases/conformance/salsa/mod.js(5,7): error TS7006: Parameter 'n' implicitly has an 'any' type.
2+
tests/cases/conformance/salsa/test.js(52,7): error TS7006: Parameter 'n' implicitly has an 'any' type.
3+
tests/cases/conformance/salsa/test.js(70,7): error TS7006: Parameter 'n' implicitly has an 'any' type.
4+
5+
6+
==== tests/cases/conformance/salsa/test.js (2 errors) ====
7+
/** @typedef {{
8+
status: 'done'
9+
m(n: number): void
10+
}} DoneStatus */
11+
12+
// property assignment
13+
var ns = {}
14+
/** @type {DoneStatus} */
15+
ns.x = {
16+
status: 'done',
17+
m(n) { }
18+
}
19+
20+
ns.x = {
21+
status: 'done',
22+
m(n) { }
23+
}
24+
ns.x
25+
26+
27+
// this-property assignment
28+
class Thing {
29+
constructor() {
30+
/** @type {DoneStatus} */
31+
this.s = {
32+
status: 'done',
33+
m(n) { }
34+
}
35+
}
36+
37+
fail() {
38+
this.s = {
39+
status: 'done',
40+
m(n) { }
41+
}
42+
}
43+
}
44+
45+
// exports-property assignment
46+
47+
/** @type {DoneStatus} */
48+
exports.x = {
49+
status: "done",
50+
m(n) { }
51+
}
52+
exports.x
53+
54+
/** @type {DoneStatus} contextual typing is allowed, but module.exports.y: any.
55+
Guess it doesn't check the type tag? */
56+
module.exports.y = {
57+
status: "done",
58+
m(n) { }
59+
~
60+
!!! error TS7006: Parameter 'n' implicitly has an 'any' type.
61+
}
62+
module.exports.y
63+
64+
// prototype-property assignment
65+
/** @type {DoneStatus} */
66+
Thing.prototype.x = {
67+
status: 'done',
68+
m(n) { }
69+
}
70+
Thing.prototype.x
71+
72+
// prototype assignment
73+
function F() {
74+
}
75+
/** @type {DoneStatus} */
76+
F.prototype = {
77+
status: "done",
78+
m(n) { }
79+
~
80+
!!! error TS7006: Parameter 'n' implicitly has an 'any' type.
81+
}
82+
83+
==== tests/cases/conformance/salsa/mod.js (1 errors) ====
84+
// module.exports assignment
85+
/** @type {{ status: 'done' }} */
86+
module.exports = {
87+
status: "done",
88+
m(n) { }
89+
~
90+
!!! error TS7006: Parameter 'n' implicitly has an 'any' type.
91+
}
92+
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
1+
=== tests/cases/conformance/salsa/test.js ===
2+
/** @typedef {{
3+
status: 'done'
4+
m(n: number): void
5+
}} DoneStatus */
6+
7+
// property assignment
8+
var ns = {}
9+
>ns : Symbol(ns, Decl(test.js, 6, 3))
10+
11+
/** @type {DoneStatus} */
12+
ns.x = {
13+
>ns.x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
14+
>ns : Symbol(ns, Decl(test.js, 6, 3))
15+
>x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
16+
17+
status: 'done',
18+
>status : Symbol(status, Decl(test.js, 8, 8))
19+
20+
m(n) { }
21+
>m : Symbol(m, Decl(test.js, 9, 19))
22+
>n : Symbol(n, Decl(test.js, 10, 6))
23+
}
24+
25+
ns.x = {
26+
>ns.x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
27+
>ns : Symbol(ns, Decl(test.js, 6, 3))
28+
>x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
29+
30+
status: 'done',
31+
>status : Symbol(status, Decl(test.js, 13, 8))
32+
33+
m(n) { }
34+
>m : Symbol(m, Decl(test.js, 14, 19))
35+
>n : Symbol(n, Decl(test.js, 15, 6))
36+
}
37+
ns.x
38+
>ns.x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
39+
>ns : Symbol(ns, Decl(test.js, 6, 3))
40+
>x : Symbol(ns.x, Decl(test.js, 6, 11), Decl(test.js, 11, 1))
41+
42+
43+
// this-property assignment
44+
class Thing {
45+
>Thing : Symbol(Thing, Decl(test.js, 17, 4))
46+
47+
constructor() {
48+
/** @type {DoneStatus} */
49+
this.s = {
50+
>this.s : Symbol(Thing.s, Decl(test.js, 22, 19), Decl(test.js, 30, 12))
51+
>this : Symbol(Thing, Decl(test.js, 17, 4))
52+
>s : Symbol(Thing.s, Decl(test.js, 22, 19), Decl(test.js, 30, 12))
53+
54+
status: 'done',
55+
>status : Symbol(status, Decl(test.js, 24, 18))
56+
57+
m(n) { }
58+
>m : Symbol(m, Decl(test.js, 25, 27))
59+
>n : Symbol(n, Decl(test.js, 26, 14))
60+
}
61+
}
62+
63+
fail() {
64+
>fail : Symbol(Thing.fail, Decl(test.js, 28, 5))
65+
66+
this.s = {
67+
>this.s : Symbol(Thing.s, Decl(test.js, 22, 19), Decl(test.js, 30, 12))
68+
>this : Symbol(Thing, Decl(test.js, 17, 4))
69+
>s : Symbol(Thing.s, Decl(test.js, 22, 19), Decl(test.js, 30, 12))
70+
71+
status: 'done',
72+
>status : Symbol(status, Decl(test.js, 31, 18))
73+
74+
m(n) { }
75+
>m : Symbol(m, Decl(test.js, 32, 27))
76+
>n : Symbol(n, Decl(test.js, 33, 14))
77+
}
78+
}
79+
}
80+
81+
// exports-property assignment
82+
83+
/** @type {DoneStatus} */
84+
exports.x = {
85+
>exports.x : Symbol(x, Decl(test.js, 36, 1))
86+
>exports : Symbol(x, Decl(test.js, 36, 1))
87+
>x : Symbol(x, Decl(test.js, 36, 1))
88+
89+
status: "done",
90+
>status : Symbol(status, Decl(test.js, 41, 13))
91+
92+
m(n) { }
93+
>m : Symbol(m, Decl(test.js, 42, 19))
94+
>n : Symbol(n, Decl(test.js, 43, 6))
95+
}
96+
exports.x
97+
>exports.x : Symbol(x, Decl(test.js, 36, 1))
98+
>exports : Symbol("tests/cases/conformance/salsa/test", Decl(test.js, 0, 0))
99+
>x : Symbol(x, Decl(test.js, 36, 1))
100+
101+
/** @type {DoneStatus} contextual typing is allowed, but module.exports.y: any.
102+
Guess it doesn't check the type tag? */
103+
module.exports.y = {
104+
>module.exports : Symbol(y, Decl(test.js, 45, 9))
105+
>module : Symbol(module)
106+
>y : Symbol(y, Decl(test.js, 45, 9))
107+
108+
status: "done",
109+
>status : Symbol(status, Decl(test.js, 49, 20))
110+
111+
m(n) { }
112+
>m : Symbol(m, Decl(test.js, 50, 19))
113+
>n : Symbol(n, Decl(test.js, 51, 6))
114+
}
115+
module.exports.y
116+
>module : Symbol(module)
117+
118+
// prototype-property assignment
119+
/** @type {DoneStatus} */
120+
Thing.prototype.x = {
121+
>Thing.prototype.x : Symbol(Thing.x, Decl(test.js, 53, 16))
122+
>Thing.prototype : Symbol(Thing.x, Decl(test.js, 53, 16))
123+
>Thing : Symbol(Thing, Decl(test.js, 17, 4))
124+
>prototype : Symbol(Thing.prototype)
125+
>x : Symbol(Thing.x, Decl(test.js, 53, 16))
126+
127+
status: 'done',
128+
>status : Symbol(status, Decl(test.js, 57, 21))
129+
130+
m(n) { }
131+
>m : Symbol(m, Decl(test.js, 58, 19))
132+
>n : Symbol(n, Decl(test.js, 59, 6))
133+
}
134+
Thing.prototype.x
135+
>Thing.prototype.x : Symbol(Thing.x, Decl(test.js, 53, 16))
136+
>Thing.prototype : Symbol(Thing.prototype)
137+
>Thing : Symbol(Thing, Decl(test.js, 17, 4))
138+
>prototype : Symbol(Thing.prototype)
139+
>x : Symbol(Thing.x, Decl(test.js, 53, 16))
140+
141+
// prototype assignment
142+
function F() {
143+
>F : Symbol(F, Decl(test.js, 61, 17), Decl(test.js, 65, 1))
144+
}
145+
/** @type {DoneStatus} */
146+
F.prototype = {
147+
>F.prototype : Symbol(F.prototype, Decl(test.js, 65, 1))
148+
>F : Symbol(F, Decl(test.js, 61, 17), Decl(test.js, 65, 1))
149+
>prototype : Symbol(F.prototype, Decl(test.js, 65, 1))
150+
151+
status: "done",
152+
>status : Symbol(status, Decl(test.js, 67, 15))
153+
154+
m(n) { }
155+
>m : Symbol(m, Decl(test.js, 68, 19))
156+
>n : Symbol(n, Decl(test.js, 69, 6))
157+
}
158+
159+
=== tests/cases/conformance/salsa/mod.js ===
160+
// module.exports assignment
161+
/** @type {{ status: 'done' }} */
162+
module.exports = {
163+
>module : Symbol(export=, Decl(mod.js, 0, 0))
164+
>exports : Symbol(export=, Decl(mod.js, 0, 0))
165+
166+
status: "done",
167+
>status : Symbol(status, Decl(mod.js, 2, 18))
168+
169+
m(n) { }
170+
>m : Symbol(m, Decl(mod.js, 3, 19))
171+
>n : Symbol(n, Decl(mod.js, 4, 6))
172+
}
173+

0 commit comments

Comments
 (0)