Skip to content

Commit 989a717

Browse files
authored
Definite assignment checking for expando properties (#27128)
1 parent e710645 commit 989a717

6 files changed

+724
-0
lines changed

src/compiler/checker.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14734,6 +14734,14 @@ namespace ts {
1473414734
// reference 'x.y.z', we may be at an assignment to 'x.y' or 'x'. In that case,
1473514735
// return the declared type.
1473614736
if (containsMatchingReference(reference, node)) {
14737+
// A matching dotted name might also be an expando property on a function *expression*,
14738+
// in which case we continue control flow analysis back to the function's declaration
14739+
if (isVariableDeclaration(node) && (isInJSFile(node) || isVarConst(node))) {
14740+
const init = getDeclaredExpandoInitializer(node);
14741+
if (init && (init.kind === SyntaxKind.FunctionExpression || init.kind === SyntaxKind.ArrowFunction)) {
14742+
return getTypeAtFlowNode(flow.antecedent);
14743+
}
14744+
}
1473714745
return declaredType;
1473814746
}
1473914747
// Assignment doesn't affect reference
@@ -18395,6 +18403,9 @@ namespace ts {
1839518403
}
1839618404
}
1839718405
}
18406+
else if (strictNullChecks && prop && prop.valueDeclaration && isPropertyAccessExpression(prop.valueDeclaration) && getAssignmentDeclarationPropertyAccessKind(prop.valueDeclaration)) {
18407+
assumeUninitialized = true;
18408+
}
1839818409
const flowType = getFlowTypeOfReference(node, propType, assumeUninitialized ? getOptionalType(propType) : propType);
1839918410
if (assumeUninitialized && !(getFalsyFlags(propType) & TypeFlags.Undefined) && getFalsyFlags(flowType) & TypeFlags.Undefined) {
1840018411
error(right, Diagnostics.Property_0_is_used_before_being_assigned, symbolToString(prop!)); // TODO: GH#18217
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
tests/cases/conformance/salsa/typeFromPropertyAssignment36.ts(11,7): error TS2565: Property 'q' is used before being assigned.
2+
tests/cases/conformance/salsa/typeFromPropertyAssignment36.ts(42,3): error TS2565: Property 'q' is used before being assigned.
3+
tests/cases/conformance/salsa/typeFromPropertyAssignment36.ts(64,3): error TS2565: Property 'expando' is used before being assigned.
4+
5+
6+
==== tests/cases/conformance/salsa/typeFromPropertyAssignment36.ts (3 errors) ====
7+
function f(b: boolean) {
8+
function d() {
9+
}
10+
d.e = 12
11+
d.e
12+
13+
if (b) {
14+
d.q = false
15+
}
16+
// error d.q might not be assigned
17+
d.q
18+
~
19+
!!! error TS2565: Property 'q' is used before being assigned.
20+
if (b) {
21+
d.q = false
22+
}
23+
else {
24+
d.q = true
25+
}
26+
d.q
27+
if (b) {
28+
d.r = 1
29+
}
30+
else {
31+
d.r = 2
32+
}
33+
d.r
34+
if (b) {
35+
d.s = 'hi'
36+
}
37+
return d
38+
}
39+
// OK to access possibly-unassigned properties outside the initialising scope
40+
var test = f(true).s
41+
42+
function d() {
43+
}
44+
d.e = 12
45+
d.e
46+
47+
if (!!false) {
48+
d.q = false
49+
}
50+
d.q
51+
~
52+
!!! error TS2565: Property 'q' is used before being assigned.
53+
if (!!false) {
54+
d.q = false
55+
}
56+
else {
57+
d.q = true
58+
}
59+
d.q
60+
if (!!false) {
61+
d.r = 1
62+
}
63+
else {
64+
d.r = 2
65+
}
66+
d.r
67+
68+
// test function expressions too
69+
const g = function() {
70+
}
71+
if (!!false) {
72+
g.expando = 1
73+
}
74+
g.expando // error
75+
~~~~~~~
76+
!!! error TS2565: Property 'expando' is used before being assigned.
77+
78+
if (!!false) {
79+
g.both = 'hi'
80+
}
81+
else {
82+
g.both = 0
83+
}
84+
g.both
85+
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
//// [typeFromPropertyAssignment36.ts]
2+
function f(b: boolean) {
3+
function d() {
4+
}
5+
d.e = 12
6+
d.e
7+
8+
if (b) {
9+
d.q = false
10+
}
11+
// error d.q might not be assigned
12+
d.q
13+
if (b) {
14+
d.q = false
15+
}
16+
else {
17+
d.q = true
18+
}
19+
d.q
20+
if (b) {
21+
d.r = 1
22+
}
23+
else {
24+
d.r = 2
25+
}
26+
d.r
27+
if (b) {
28+
d.s = 'hi'
29+
}
30+
return d
31+
}
32+
// OK to access possibly-unassigned properties outside the initialising scope
33+
var test = f(true).s
34+
35+
function d() {
36+
}
37+
d.e = 12
38+
d.e
39+
40+
if (!!false) {
41+
d.q = false
42+
}
43+
d.q
44+
if (!!false) {
45+
d.q = false
46+
}
47+
else {
48+
d.q = true
49+
}
50+
d.q
51+
if (!!false) {
52+
d.r = 1
53+
}
54+
else {
55+
d.r = 2
56+
}
57+
d.r
58+
59+
// test function expressions too
60+
const g = function() {
61+
}
62+
if (!!false) {
63+
g.expando = 1
64+
}
65+
g.expando // error
66+
67+
if (!!false) {
68+
g.both = 'hi'
69+
}
70+
else {
71+
g.both = 0
72+
}
73+
g.both
74+
75+
76+
//// [typeFromPropertyAssignment36.js]
77+
"use strict";
78+
function f(b) {
79+
function d() {
80+
}
81+
d.e = 12;
82+
d.e;
83+
if (b) {
84+
d.q = false;
85+
}
86+
// error d.q might not be assigned
87+
d.q;
88+
if (b) {
89+
d.q = false;
90+
}
91+
else {
92+
d.q = true;
93+
}
94+
d.q;
95+
if (b) {
96+
d.r = 1;
97+
}
98+
else {
99+
d.r = 2;
100+
}
101+
d.r;
102+
if (b) {
103+
d.s = 'hi';
104+
}
105+
return d;
106+
}
107+
// OK to access possibly-unassigned properties outside the initialising scope
108+
var test = f(true).s;
109+
function d() {
110+
}
111+
d.e = 12;
112+
d.e;
113+
if (!!false) {
114+
d.q = false;
115+
}
116+
d.q;
117+
if (!!false) {
118+
d.q = false;
119+
}
120+
else {
121+
d.q = true;
122+
}
123+
d.q;
124+
if (!!false) {
125+
d.r = 1;
126+
}
127+
else {
128+
d.r = 2;
129+
}
130+
d.r;
131+
// test function expressions too
132+
var g = function () {
133+
};
134+
if (!!false) {
135+
g.expando = 1;
136+
}
137+
g.expando; // error
138+
if (!!false) {
139+
g.both = 'hi';
140+
}
141+
else {
142+
g.both = 0;
143+
}
144+
g.both;

0 commit comments

Comments
 (0)