@@ -9,14 +9,19 @@ import {CompilerError} from '..';
9
9
import {
10
10
BlockId ,
11
11
Effect ,
12
+ Environment ,
12
13
HIRFunction ,
13
14
Identifier ,
14
15
IdentifierId ,
16
+ Instruction ,
15
17
Place ,
16
18
computePostDominatorTree ,
19
+ evaluatesToStableTypeOrContainer ,
17
20
getHookKind ,
18
21
isStableType ,
22
+ isStableTypeContainer ,
19
23
isUseOperator ,
24
+ isUseRefType ,
20
25
} from '../HIR' ;
21
26
import { PostDominator } from '../HIR/Dominator' ;
22
27
import {
@@ -31,6 +36,103 @@ import {
31
36
import DisjointSet from '../Utils/DisjointSet' ;
32
37
import { assertExhaustive } from '../Utils/utils' ;
33
38
39
+ /**
40
+ * Side map to track and propagate sources of stability (i.e. hook calls such as
41
+ * `useRef()` and property reads such as `useState()[1]). Note that this
42
+ * requires forward data flow analysis since stability is not part of React
43
+ * Compiler's type system.
44
+ */
45
+ class StableSidemap {
46
+ map : Map < IdentifierId , { isStable : boolean } > = new Map ( ) ;
47
+ env : Environment ;
48
+
49
+ constructor ( env : Environment ) {
50
+ this . env = env ;
51
+ }
52
+
53
+ handleInstruction ( instr : Instruction ) : void {
54
+ const { value, lvalue} = instr ;
55
+
56
+ switch ( value . kind ) {
57
+ case 'CallExpression' :
58
+ case 'MethodCall' : {
59
+ /**
60
+ * Sources of stability are known hook calls
61
+ */
62
+ if ( evaluatesToStableTypeOrContainer ( this . env , instr ) ) {
63
+ if ( isStableType ( lvalue . identifier ) ) {
64
+ this . map . set ( lvalue . identifier . id , {
65
+ isStable : true ,
66
+ } ) ;
67
+ } else {
68
+ this . map . set ( lvalue . identifier . id , {
69
+ isStable : false ,
70
+ } ) ;
71
+ }
72
+ } else if (
73
+ this . env . config . enableTreatRefLikeIdentifiersAsRefs &&
74
+ isUseRefType ( lvalue . identifier )
75
+ ) {
76
+ this . map . set ( lvalue . identifier . id , {
77
+ isStable : true ,
78
+ } ) ;
79
+ }
80
+ break ;
81
+ }
82
+
83
+ case 'Destructure' :
84
+ case 'PropertyLoad' : {
85
+ /**
86
+ * PropertyLoads may from stable containers may also produce stable
87
+ * values. ComputedLoads are technically safe for now (as all stable
88
+ * containers have differently-typed elements), but are not handled as
89
+ * they should be rare anyways.
90
+ */
91
+ const source =
92
+ value . kind === 'Destructure'
93
+ ? value . value . identifier . id
94
+ : value . object . identifier . id ;
95
+ const entry = this . map . get ( source ) ;
96
+ if ( entry ) {
97
+ for ( const lvalue of eachInstructionLValue ( instr ) ) {
98
+ if ( isStableTypeContainer ( lvalue . identifier ) ) {
99
+ this . map . set ( lvalue . identifier . id , {
100
+ isStable : false ,
101
+ } ) ;
102
+ } else if ( isStableType ( lvalue . identifier ) ) {
103
+ this . map . set ( lvalue . identifier . id , {
104
+ isStable : true ,
105
+ } ) ;
106
+ }
107
+ }
108
+ }
109
+ break ;
110
+ }
111
+
112
+ case 'StoreLocal' : {
113
+ const entry = this . map . get ( value . value . identifier . id ) ;
114
+ if ( entry ) {
115
+ this . map . set ( lvalue . identifier . id , entry ) ;
116
+ this . map . set ( value . lvalue . place . identifier . id , entry ) ;
117
+ }
118
+ break ;
119
+ }
120
+
121
+ case 'LoadLocal' : {
122
+ const entry = this . map . get ( value . place . identifier . id ) ;
123
+ if ( entry ) {
124
+ this . map . set ( lvalue . identifier . id , entry ) ;
125
+ }
126
+ break ;
127
+ }
128
+ }
129
+ }
130
+
131
+ isStable ( id : IdentifierId ) : boolean {
132
+ const entry = this . map . get ( id ) ;
133
+ return entry != null ? entry . isStable : false ;
134
+ }
135
+ }
34
136
/*
35
137
* Infers which `Place`s are reactive, ie may *semantically* change
36
138
* over the course of the component/hook's lifetime. Places are reactive
@@ -111,6 +213,7 @@ import {assertExhaustive} from '../Utils/utils';
111
213
*/
112
214
export function inferReactivePlaces ( fn : HIRFunction ) : void {
113
215
const reactiveIdentifiers = new ReactivityMap ( findDisjointMutableValues ( fn ) ) ;
216
+ const stableIdentifierSources = new StableSidemap ( fn . env ) ;
114
217
for ( const param of fn . params ) {
115
218
const place = param . kind === 'Identifier' ? param : param . place ;
116
219
reactiveIdentifiers . markReactive ( place ) ;
@@ -184,6 +287,7 @@ export function inferReactivePlaces(fn: HIRFunction): void {
184
287
}
185
288
}
186
289
for ( const instruction of block . instructions ) {
290
+ stableIdentifierSources . handleInstruction ( instruction ) ;
187
291
const { value} = instruction ;
188
292
let hasReactiveInput = false ;
189
293
/*
@@ -218,7 +322,13 @@ export function inferReactivePlaces(fn: HIRFunction): void {
218
322
219
323
if ( hasReactiveInput ) {
220
324
for ( const lvalue of eachInstructionLValue ( instruction ) ) {
221
- if ( isStableType ( lvalue . identifier ) ) {
325
+ /**
326
+ * Note that it's not correct to mark all stable-typed identifiers
327
+ * as non-reactive, since ternaries and other value blocks can
328
+ * produce reactive identifiers typed as these.
329
+ * (e.g. `props.cond ? setState1 : setState2`)
330
+ */
331
+ if ( stableIdentifierSources . isStable ( lvalue . identifier . id ) ) {
222
332
continue ;
223
333
}
224
334
reactiveIdentifiers . markReactive ( lvalue ) ;
0 commit comments