Skip to content

Commit 2f6504c

Browse files
authored
Port JS specific code for this.xxx assignment declaration typing (#3680)
1 parent 6dff52f commit 2f6504c

5 files changed

Lines changed: 646 additions & 8 deletions

File tree

internal/checker/checker.go

Lines changed: 38 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17706,10 +17706,11 @@ func (c *Checker) getWidenedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Ty
1770617706
t = c.getTypeFromTypeNode(declaration.Type())
1770717707
break
1770817708
}
17709-
assignedType := c.getAssignmentDeclarationInitializerType(declaration)
17710-
// We ignore initial assignments of undefined to CommonJS exports when there are multiple assignment declarations
17711-
if ast.GetAssignmentDeclarationKind(declaration) != ast.JSDeclarationKindExportsProperty || i != 0 || len(symbol.Declarations) == 1 || assignedType.flags&TypeFlagsUndefined == 0 {
17712-
types = core.AppendIfUnique(types, assignedType)
17709+
if assignedType := c.getAssignmentDeclarationInitializerType(declaration); assignedType != nil {
17710+
// We ignore initial assignments of undefined to CommonJS exports when there are multiple assignment declarations
17711+
if ast.GetAssignmentDeclarationKind(declaration) != ast.JSDeclarationKindExportsProperty || i != 0 || len(symbol.Declarations) == 1 || assignedType.flags&TypeFlagsUndefined == 0 {
17712+
types = core.AppendIfUnique(types, assignedType)
17713+
}
1771317714
}
1771417715
}
1771517716
if kind == thisAssignmentDeclarationMethod && len(types) > 0 {
@@ -17718,7 +17719,10 @@ func (c *Checker) getWidenedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Ty
1771817719
}
1771917720
}
1772017721
if t == nil {
17721-
t = c.getUnionType(types)
17722+
t = c.anyType
17723+
if len(types) != 0 {
17724+
t = c.getUnionType(types)
17725+
}
1772217726
}
1772317727
}
1772417728
t = c.getWidenedType(t)
@@ -17733,16 +17737,42 @@ func (c *Checker) getWidenedTypeForAssignmentDeclaration(symbol *ast.Symbol) *Ty
1773317737

1773417738
func (c *Checker) getAssignmentDeclarationInitializerType(node *ast.Node) *Type {
1773517739
if ast.IsBinaryExpression(node) {
17740+
var t *Type
1773617741
switch ast.GetAssignmentDeclarationKind(node) {
1773717742
case ast.JSDeclarationKindModuleExports, ast.JSDeclarationKindExportsProperty:
17738-
return c.getRegularTypeOfLiteralType(c.checkExpressionCached(ast.GetRightMostAssignedExpression(node)))
17743+
t = c.getRegularTypeOfLiteralType(c.checkExpressionCached(ast.GetRightMostAssignedExpression(node)))
17744+
case ast.JSDeclarationKindThisProperty:
17745+
if c.containsSameNamedThisProperty(node.AsBinaryExpression().Left, node.AsBinaryExpression().Right) {
17746+
return nil
17747+
}
17748+
fallthrough
17749+
default:
17750+
t = c.checkExpressionForMutableLocation(node.AsBinaryExpression().Right, CheckModeNormal)
17751+
}
17752+
if c.isEmptyArrayLiteralType(t) {
17753+
c.reportImplicitAny(node, c.anyArrayType, WideningKindNormal)
17754+
return c.anyArrayType
1773917755
}
17740-
return c.checkExpressionForMutableLocation(node.AsBinaryExpression().Right, CheckModeNormal)
17756+
return t
1774117757
}
1774217758
if ast.IsCallExpression(node) {
1774317759
return c.getTypeFromPropertyDescriptor(node.Arguments()[2])
1774417760
}
17745-
return c.neverType
17761+
return nil
17762+
}
17763+
17764+
func (c *Checker) containsSameNamedThisProperty(thisProperty *ast.Node, expression *ast.Node) bool {
17765+
var visit func(node *ast.Node) bool
17766+
visit = func(node *ast.Node) bool {
17767+
if c.isMatchingReference(thisProperty, node) {
17768+
return true
17769+
}
17770+
if ast.IsFunctionLike(node) {
17771+
return false
17772+
}
17773+
return node.ForEachChild(visit)
17774+
}
17775+
return visit(expression)
1774617776
}
1774717777

1774817778
func (c *Checker) getTypeFromPropertyDescriptor(node *ast.Node) *Type {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
main.js(47,21): error TS2532: Object is possibly 'undefined'.
2+
main.js(55,9): error TS2322: Type 'string' is not assignable to type 'number'.
3+
main.js(64,9): error TS7008: Member 'bar' implicitly has an 'any[]' type.
4+
5+
6+
==== main.js (3 errors) ====
7+
class C1 {
8+
constructor() {
9+
this.foo = [3];
10+
this.foo = [this.foo[0] * 2];
11+
this.foo; // number[]
12+
}
13+
}
14+
15+
class C2 {
16+
constructor() {
17+
this.foo = [3];
18+
this.foo; // number[]
19+
}
20+
method1() {
21+
this.foo = [this.foo[0] * 2];
22+
}
23+
}
24+
25+
class C4 {
26+
constructor() {
27+
this.bar = [];
28+
this.bar.push("baz");
29+
this.bar; // string[]
30+
}
31+
}
32+
33+
// When this.xxx assignment declarations occur only in methods, the declared type of the
34+
// property is a union of the types of all assigned values, excluding values from self-referential
35+
// assignments, plus undefined. Local CFA removes the undefined when possible.
36+
37+
class C5 {
38+
method1() {
39+
this.foo; // number[] | undefined
40+
this.foo = [3]
41+
this.foo = [this.foo[0] * 2]
42+
this.foo; // number[]
43+
}
44+
}
45+
46+
class C6 {
47+
method1() {
48+
this.foo = [3];
49+
this.foo; // number[]
50+
}
51+
method2() {
52+
this.foo; // number[] | undefined
53+
this.foo = [this.foo[0] * 2]; // Error: Object is possibly undefined
54+
~~~~~~~~
55+
!!! error TS2532: Object is possibly 'undefined'.
56+
this.foo; // number[]
57+
}
58+
}
59+
60+
class C7 {
61+
method1() {
62+
this.foo = 0;
63+
this.foo = this.foo + "abc"; // Error, 'string' not assignable to 'number'
64+
~~~~~~~~
65+
!!! error TS2322: Type 'string' is not assignable to type 'number'.
66+
}
67+
}
68+
69+
// When this.xxx assigmment declarations occur only in methods, an assigned empty array
70+
// literal value is given type 'any[]'.
71+
72+
class C8 {
73+
method1() {
74+
this.bar = []; // Error: Implicit any[]
75+
~~~~~~~~~~~~~
76+
!!! error TS7008: Member 'bar' implicitly has an 'any[]' type.
77+
this.bar.push("baz");
78+
this.bar; // any[]
79+
}
80+
}
81+
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
//// [tests/cases/compiler/thisPropertyAssignmentTyping.ts] ////
2+
3+
=== main.js ===
4+
class C1 {
5+
>C1 : Symbol(C1, Decl(main.js, 0, 0))
6+
7+
constructor() {
8+
this.foo = [3];
9+
>this.foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
10+
>this : Symbol(C1, Decl(main.js, 0, 0))
11+
>foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
12+
13+
this.foo = [this.foo[0] * 2];
14+
>this.foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
15+
>this : Symbol(C1, Decl(main.js, 0, 0))
16+
>foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
17+
>this.foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
18+
>this : Symbol(C1, Decl(main.js, 0, 0))
19+
>foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
20+
21+
this.foo; // number[]
22+
>this.foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
23+
>this : Symbol(C1, Decl(main.js, 0, 0))
24+
>foo : Symbol(C1.foo, Decl(main.js, 1, 19), Decl(main.js, 2, 23))
25+
}
26+
}
27+
28+
class C2 {
29+
>C2 : Symbol(C2, Decl(main.js, 6, 1))
30+
31+
constructor() {
32+
this.foo = [3];
33+
>this.foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
34+
>this : Symbol(C2, Decl(main.js, 6, 1))
35+
>foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
36+
37+
this.foo; // number[]
38+
>this.foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
39+
>this : Symbol(C2, Decl(main.js, 6, 1))
40+
>foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
41+
}
42+
method1() {
43+
>method1 : Symbol(C2.method1, Decl(main.js, 12, 5))
44+
45+
this.foo = [this.foo[0] * 2];
46+
>this.foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
47+
>this : Symbol(C2, Decl(main.js, 6, 1))
48+
>foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
49+
>this.foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
50+
>this : Symbol(C2, Decl(main.js, 6, 1))
51+
>foo : Symbol(C2.foo, Decl(main.js, 9, 19), Decl(main.js, 13, 15))
52+
}
53+
}
54+
55+
class C4 {
56+
>C4 : Symbol(C4, Decl(main.js, 16, 1))
57+
58+
constructor() {
59+
this.bar = [];
60+
>this.bar : Symbol(C4.bar, Decl(main.js, 19, 19))
61+
>this : Symbol(C4, Decl(main.js, 16, 1))
62+
>bar : Symbol(C4.bar, Decl(main.js, 19, 19))
63+
64+
this.bar.push("baz");
65+
>this.bar.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
66+
>this.bar : Symbol(C4.bar, Decl(main.js, 19, 19))
67+
>this : Symbol(C4, Decl(main.js, 16, 1))
68+
>bar : Symbol(C4.bar, Decl(main.js, 19, 19))
69+
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
70+
71+
this.bar; // string[]
72+
>this.bar : Symbol(C4.bar, Decl(main.js, 19, 19))
73+
>this : Symbol(C4, Decl(main.js, 16, 1))
74+
>bar : Symbol(C4.bar, Decl(main.js, 19, 19))
75+
}
76+
}
77+
78+
// When this.xxx assignment declarations occur only in methods, the declared type of the
79+
// property is a union of the types of all assigned values, excluding values from self-referential
80+
// assignments, plus undefined. Local CFA removes the undefined when possible.
81+
82+
class C5 {
83+
>C5 : Symbol(C5, Decl(main.js, 24, 1))
84+
85+
method1() {
86+
>method1 : Symbol(C5.method1, Decl(main.js, 30, 10))
87+
88+
this.foo; // number[] | undefined
89+
>this.foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
90+
>this : Symbol(C5, Decl(main.js, 24, 1))
91+
>foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
92+
93+
this.foo = [3]
94+
>this.foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
95+
>this : Symbol(C5, Decl(main.js, 24, 1))
96+
>foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
97+
98+
this.foo = [this.foo[0] * 2]
99+
>this.foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
100+
>this : Symbol(C5, Decl(main.js, 24, 1))
101+
>foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
102+
>this.foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
103+
>this : Symbol(C5, Decl(main.js, 24, 1))
104+
>foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
105+
106+
this.foo; // number[]
107+
>this.foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
108+
>this : Symbol(C5, Decl(main.js, 24, 1))
109+
>foo : Symbol(C5.foo, Decl(main.js, 32, 17), Decl(main.js, 33, 22))
110+
}
111+
}
112+
113+
class C6 {
114+
>C6 : Symbol(C6, Decl(main.js, 37, 1))
115+
116+
method1() {
117+
>method1 : Symbol(C6.method1, Decl(main.js, 39, 10))
118+
119+
this.foo = [3];
120+
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
121+
>this : Symbol(C6, Decl(main.js, 37, 1))
122+
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
123+
124+
this.foo; // number[]
125+
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
126+
>this : Symbol(C6, Decl(main.js, 37, 1))
127+
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
128+
}
129+
method2() {
130+
>method2 : Symbol(C6.method2, Decl(main.js, 43, 5))
131+
132+
this.foo; // number[] | undefined
133+
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
134+
>this : Symbol(C6, Decl(main.js, 37, 1))
135+
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
136+
137+
this.foo = [this.foo[0] * 2]; // Error: Object is possibly undefined
138+
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
139+
>this : Symbol(C6, Decl(main.js, 37, 1))
140+
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
141+
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
142+
>this : Symbol(C6, Decl(main.js, 37, 1))
143+
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
144+
145+
this.foo; // number[]
146+
>this.foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
147+
>this : Symbol(C6, Decl(main.js, 37, 1))
148+
>foo : Symbol(C6.foo, Decl(main.js, 40, 15), Decl(main.js, 45, 17))
149+
}
150+
}
151+
152+
class C7 {
153+
>C7 : Symbol(C7, Decl(main.js, 49, 1))
154+
155+
method1() {
156+
>method1 : Symbol(C7.method1, Decl(main.js, 51, 10))
157+
158+
this.foo = 0;
159+
>this.foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))
160+
>this : Symbol(C7, Decl(main.js, 49, 1))
161+
>foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))
162+
163+
this.foo = this.foo + "abc"; // Error, 'string' not assignable to 'number'
164+
>this.foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))
165+
>this : Symbol(C7, Decl(main.js, 49, 1))
166+
>foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))
167+
>this.foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))
168+
>this : Symbol(C7, Decl(main.js, 49, 1))
169+
>foo : Symbol(C7.foo, Decl(main.js, 52, 15), Decl(main.js, 53, 21))
170+
}
171+
}
172+
173+
// When this.xxx assigmment declarations occur only in methods, an assigned empty array
174+
// literal value is given type 'any[]'.
175+
176+
class C8 {
177+
>C8 : Symbol(C8, Decl(main.js, 56, 1))
178+
179+
method1() {
180+
>method1 : Symbol(C8.method1, Decl(main.js, 61, 10))
181+
182+
this.bar = []; // Error: Implicit any[]
183+
>this.bar : Symbol(C8.bar, Decl(main.js, 62, 15))
184+
>this : Symbol(C8, Decl(main.js, 56, 1))
185+
>bar : Symbol(C8.bar, Decl(main.js, 62, 15))
186+
187+
this.bar.push("baz");
188+
>this.bar.push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
189+
>this.bar : Symbol(C8.bar, Decl(main.js, 62, 15))
190+
>this : Symbol(C8, Decl(main.js, 56, 1))
191+
>bar : Symbol(C8.bar, Decl(main.js, 62, 15))
192+
>push : Symbol(Array.push, Decl(lib.es5.d.ts, --, --))
193+
194+
this.bar; // any[]
195+
>this.bar : Symbol(C8.bar, Decl(main.js, 62, 15))
196+
>this : Symbol(C8, Decl(main.js, 56, 1))
197+
>bar : Symbol(C8.bar, Decl(main.js, 62, 15))
198+
}
199+
}
200+

0 commit comments

Comments
 (0)