77
88import {
99 HIRFunction ,
10+ Identifier ,
1011 IdentifierId ,
12+ InstructionValue ,
1113 makeInstructionId ,
1214 MutableRange ,
1315 Place ,
14- ReactiveValue ,
16+ ReactiveScope ,
1517} from '../HIR' ;
1618import { Macro , MacroMethod } from '../HIR/Environment' ;
17- import { eachReactiveValueOperand } from './visitors' ;
19+ import { eachInstructionValueOperand } from '../HIR/visitors' ;
20+ import { Iterable_some } from '../Utils/utils' ;
1821
1922/**
2023 * This pass supports the `fbt` translation system (https://facebook.github.io/fbt/)
@@ -48,24 +51,47 @@ export function memoizeFbtAndMacroOperandsInSameScope(
4851 ...Array . from ( FBT_TAGS ) . map ( ( tag ) : Macro => [ tag , [ ] ] ) ,
4952 ...( fn . env . config . customMacros ?? [ ] ) ,
5053 ] ) ;
51- const fbtValues : Set < IdentifierId > = new Set ( ) ;
54+ /**
55+ * Set of all identifiers that load fbt or other macro functions or their nested
56+ * properties, as well as values known to be the results of invoking macros
57+ */
58+ const macroTagsCalls : Set < IdentifierId > = new Set ( ) ;
59+ /**
60+ * Mapping of lvalue => list of operands for all expressions where either
61+ * the lvalue is a known fbt/macro call and/or the operands transitively
62+ * contain fbt/macro calls.
63+ *
64+ * This is the key data structure that powers the scope merging: we start
65+ * at the lvalues and merge operands into the lvalue's scope.
66+ */
67+ const macroValues : Map < Identifier , Array < Identifier > > = new Map ( ) ;
68+ // Tracks methods loaded from macros, like fbt.param or idx.foo
5269 const macroMethods = new Map < IdentifierId , Array < Array < MacroMethod > > > ( ) ;
53- while ( true ) {
54- let vsize = fbtValues . size ;
55- let msize = macroMethods . size ;
56- visit ( fn , fbtMacroTags , fbtValues , macroMethods ) ;
57- if ( vsize === fbtValues . size && msize === macroMethods . size ) {
58- break ;
70+
71+ visit ( fn , fbtMacroTags , macroTagsCalls , macroMethods , macroValues ) ;
72+
73+ for ( const root of macroValues . keys ( ) ) {
74+ const scope = root . scope ;
75+ if ( scope == null ) {
76+ continue ;
77+ }
78+ // Merge the operands into the same scope if this is a known macro invocation
79+ if ( ! macroTagsCalls . has ( root . id ) ) {
80+ continue ;
5981 }
82+ mergeScopes ( root , scope , macroValues , macroTagsCalls ) ;
6083 }
61- return fbtValues ;
84+
85+ return macroTagsCalls ;
6286}
6387
6488export const FBT_TAGS : Set < string > = new Set ( [
6589 'fbt' ,
6690 'fbt:param' ,
91+ 'fbt:enum' ,
6792 'fbs' ,
6893 'fbs:param' ,
94+ 'fbs:enum' ,
6995] ) ;
7096export const SINGLE_CHILD_FBT_TAGS : Set < string > = new Set ( [
7197 'fbt:param' ,
@@ -75,10 +101,22 @@ export const SINGLE_CHILD_FBT_TAGS: Set<string> = new Set([
75101function visit (
76102 fn : HIRFunction ,
77103 fbtMacroTags : Set < Macro > ,
78- fbtValues : Set < IdentifierId > ,
104+ macroTagsCalls : Set < IdentifierId > ,
79105 macroMethods : Map < IdentifierId , Array < Array < MacroMethod > > > ,
106+ macroValues : Map < Identifier , Array < Identifier > > ,
80107) : void {
81108 for ( const [ , block ] of fn . body . blocks ) {
109+ for ( const phi of block . phis ) {
110+ const fbtOperands : Array < Identifier > = [ ] ;
111+ for ( const operand of phi . operands . values ( ) ) {
112+ if ( macroValues . has ( operand . identifier ) ) {
113+ fbtOperands . push ( operand . identifier ) ;
114+ }
115+ }
116+ if ( fbtOperands . length !== 0 ) {
117+ macroValues . set ( phi . place . identifier , fbtOperands ) ;
118+ }
119+ }
82120 for ( const instruction of block . instructions ) {
83121 const { lvalue, value} = instruction ;
84122 if ( lvalue === null ) {
@@ -93,13 +131,13 @@ function visit(
93131 * We don't distinguish between tag names and strings, so record
94132 * all `fbt` string literals in case they are used as a jsx tag.
95133 */
96- fbtValues . add ( lvalue . identifier . id ) ;
134+ macroTagsCalls . add ( lvalue . identifier . id ) ;
97135 } else if (
98136 value . kind === 'LoadGlobal' &&
99137 matchesExactTag ( value . binding . name , fbtMacroTags )
100138 ) {
101139 // Record references to `fbt` as a global
102- fbtValues . add ( lvalue . identifier . id ) ;
140+ macroTagsCalls . add ( lvalue . identifier . id ) ;
103141 } else if (
104142 value . kind === 'LoadGlobal' &&
105143 matchTagRoot ( value . binding . name , fbtMacroTags ) !== null
@@ -121,84 +159,66 @@ function visit(
121159 if ( method . length > 1 ) {
122160 newMethods . push ( method . slice ( 1 ) ) ;
123161 } else {
124- fbtValues . add ( lvalue . identifier . id ) ;
162+ macroTagsCalls . add ( lvalue . identifier . id ) ;
125163 }
126164 }
127165 }
128166 if ( newMethods . length > 0 ) {
129167 macroMethods . set ( lvalue . identifier . id , newMethods ) ;
130168 }
131- } else if ( isFbtCallExpression ( fbtValues , value ) ) {
132- const fbtScope = lvalue . identifier . scope ;
133- if ( fbtScope === null ) {
134- continue ;
135- }
136-
137- /*
138- * if the JSX element's tag was `fbt`, mark all its operands
139- * to ensure that they end up in the same scope as the jsx element
140- * itself.
141- */
142- for ( const operand of eachReactiveValueOperand ( value ) ) {
143- operand . identifier . scope = fbtScope ;
144-
145- // Expand the jsx element's range to account for its operands
146- expandFbtScopeRange ( fbtScope . range , operand . identifier . mutableRange ) ;
147- fbtValues . add ( operand . identifier . id ) ;
148- }
149169 } else if (
150- isFbtJsxExpression ( fbtMacroTags , fbtValues , value ) ||
151- isFbtJsxChild ( fbtValues , lvalue , value )
170+ value . kind === 'PropertyLoad' &&
171+ macroTagsCalls . has ( value . object . identifier . id )
152172 ) {
153- const fbtScope = lvalue . identifier . scope ;
154- if ( fbtScope === null ) {
155- continue ;
156- }
157-
158- /*
159- * if the JSX element's tag was `fbt`, mark all its operands
160- * to ensure that they end up in the same scope as the jsx element
161- * itself.
162- */
163- for ( const operand of eachReactiveValueOperand ( value ) ) {
164- operand . identifier . scope = fbtScope ;
165-
166- // Expand the jsx element's range to account for its operands
167- expandFbtScopeRange ( fbtScope . range , operand . identifier . mutableRange ) ;
168-
169- /*
170- * NOTE: we add the operands as fbt values so that they are also
171- * grouped with this expression
172- */
173- fbtValues . add ( operand . identifier . id ) ;
174- }
175- } else if ( fbtValues . has ( lvalue . identifier . id ) ) {
176- const fbtScope = lvalue . identifier . scope ;
177- if ( fbtScope === null ) {
178- return ;
179- }
180-
181- for ( const operand of eachReactiveValueOperand ( value ) ) {
182- if (
183- operand . identifier . name !== null &&
184- operand . identifier . name . kind === 'named'
185- ) {
186- /*
187- * named identifiers were already locals, we only have to force temporaries
188- * into the same scope
189- */
190- continue ;
173+ macroTagsCalls . add ( lvalue . identifier . id ) ;
174+ } else if (
175+ isFbtJsxExpression ( fbtMacroTags , macroTagsCalls , value ) ||
176+ isFbtJsxChild ( macroTagsCalls , lvalue , value ) ||
177+ isFbtCallExpression ( macroTagsCalls , value )
178+ ) {
179+ macroTagsCalls . add ( lvalue . identifier . id ) ;
180+ macroValues . set (
181+ lvalue . identifier ,
182+ Array . from (
183+ eachInstructionValueOperand ( value ) ,
184+ operand => operand . identifier ,
185+ ) ,
186+ ) ;
187+ } else if (
188+ Iterable_some ( eachInstructionValueOperand ( value ) , operand =>
189+ macroValues . has ( operand . identifier ) ,
190+ )
191+ ) {
192+ const fbtOperands : Array < Identifier > = [ ] ;
193+ for ( const operand of eachInstructionValueOperand ( value ) ) {
194+ if ( macroValues . has ( operand . identifier ) ) {
195+ fbtOperands . push ( operand . identifier ) ;
191196 }
192- operand . identifier . scope = fbtScope ;
193-
194- // Expand the jsx element's range to account for its operands
195- expandFbtScopeRange ( fbtScope . range , operand . identifier . mutableRange ) ;
196197 }
198+ macroValues . set ( lvalue . identifier , fbtOperands ) ;
197199 }
198200 }
199201 }
200202}
201203
204+ function mergeScopes (
205+ root : Identifier ,
206+ scope : ReactiveScope ,
207+ macroValues : Map < Identifier , Array < Identifier > > ,
208+ macroTagsCalls : Set < IdentifierId > ,
209+ ) : void {
210+ const operands = macroValues . get ( root ) ;
211+ if ( operands == null ) {
212+ return ;
213+ }
214+ for ( const operand of operands ) {
215+ operand . scope = scope ;
216+ expandFbtScopeRange ( scope . range , operand . mutableRange ) ;
217+ macroTagsCalls . add ( operand . id ) ;
218+ mergeScopes ( operand , scope , macroValues , macroTagsCalls ) ;
219+ }
220+ }
221+
202222function matchesExactTag ( s : string , tags : Set < Macro > ) : boolean {
203223 return Array . from ( tags ) . some ( macro =>
204224 typeof macro === 'string'
@@ -229,39 +249,40 @@ function matchTagRoot(
229249}
230250
231251function isFbtCallExpression (
232- fbtValues : Set < IdentifierId > ,
233- value : ReactiveValue ,
252+ macroTagsCalls : Set < IdentifierId > ,
253+ value : InstructionValue ,
234254) : boolean {
235255 return (
236256 ( value . kind === 'CallExpression' &&
237- fbtValues . has ( value . callee . identifier . id ) ) ||
238- ( value . kind === 'MethodCall' && fbtValues . has ( value . property . identifier . id ) )
257+ macroTagsCalls . has ( value . callee . identifier . id ) ) ||
258+ ( value . kind === 'MethodCall' &&
259+ macroTagsCalls . has ( value . property . identifier . id ) )
239260 ) ;
240261}
241262
242263function isFbtJsxExpression (
243264 fbtMacroTags : Set < Macro > ,
244- fbtValues : Set < IdentifierId > ,
245- value : ReactiveValue ,
265+ macroTagsCalls : Set < IdentifierId > ,
266+ value : InstructionValue ,
246267) : boolean {
247268 return (
248269 value . kind === 'JsxExpression' &&
249270 ( ( value . tag . kind === 'Identifier' &&
250- fbtValues . has ( value . tag . identifier . id ) ) ||
271+ macroTagsCalls . has ( value . tag . identifier . id ) ) ||
251272 ( value . tag . kind === 'BuiltinTag' &&
252273 matchesExactTag ( value . tag . name , fbtMacroTags ) ) )
253274 ) ;
254275}
255276
256277function isFbtJsxChild (
257- fbtValues : Set < IdentifierId > ,
278+ macroTagsCalls : Set < IdentifierId > ,
258279 lvalue : Place | null ,
259- value : ReactiveValue ,
280+ value : InstructionValue ,
260281) : boolean {
261282 return (
262283 ( value . kind === 'JsxExpression' || value . kind === 'JsxFragment' ) &&
263284 lvalue !== null &&
264- fbtValues . has ( lvalue . identifier . id )
285+ macroTagsCalls . has ( lvalue . identifier . id )
265286 ) ;
266287}
267288
0 commit comments