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,49 @@ 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' ,
92+ 'fbt:plural' ,
6793 'fbs' ,
6894 'fbs:param' ,
95+ 'fbs:enum' ,
96+ 'fbs:plural' ,
6997] ) ;
7098export const SINGLE_CHILD_FBT_TAGS : Set < string > = new Set ( [
7199 'fbt:param' ,
@@ -75,10 +103,22 @@ export const SINGLE_CHILD_FBT_TAGS: Set<string> = new Set([
75103function visit (
76104 fn : HIRFunction ,
77105 fbtMacroTags : Set < Macro > ,
78- fbtValues : Set < IdentifierId > ,
106+ macroTagsCalls : Set < IdentifierId > ,
79107 macroMethods : Map < IdentifierId , Array < Array < MacroMethod > > > ,
108+ macroValues : Map < Identifier , Array < Identifier > > ,
80109) : void {
81110 for ( const [ , block ] of fn . body . blocks ) {
111+ for ( const phi of block . phis ) {
112+ const macroOperands : Array < Identifier > = [ ] ;
113+ for ( const operand of phi . operands . values ( ) ) {
114+ if ( macroValues . has ( operand . identifier ) ) {
115+ macroOperands . push ( operand . identifier ) ;
116+ }
117+ }
118+ if ( macroOperands . length !== 0 ) {
119+ macroValues . set ( phi . place . identifier , macroOperands ) ;
120+ }
121+ }
82122 for ( const instruction of block . instructions ) {
83123 const { lvalue, value} = instruction ;
84124 if ( lvalue === null ) {
@@ -93,13 +133,13 @@ function visit(
93133 * We don't distinguish between tag names and strings, so record
94134 * all `fbt` string literals in case they are used as a jsx tag.
95135 */
96- fbtValues . add ( lvalue . identifier . id ) ;
136+ macroTagsCalls . add ( lvalue . identifier . id ) ;
97137 } else if (
98138 value . kind === 'LoadGlobal' &&
99139 matchesExactTag ( value . binding . name , fbtMacroTags )
100140 ) {
101141 // Record references to `fbt` as a global
102- fbtValues . add ( lvalue . identifier . id ) ;
142+ macroTagsCalls . add ( lvalue . identifier . id ) ;
103143 } else if (
104144 value . kind === 'LoadGlobal' &&
105145 matchTagRoot ( value . binding . name , fbtMacroTags ) !== null
@@ -121,84 +161,66 @@ function visit(
121161 if ( method . length > 1 ) {
122162 newMethods . push ( method . slice ( 1 ) ) ;
123163 } else {
124- fbtValues . add ( lvalue . identifier . id ) ;
164+ macroTagsCalls . add ( lvalue . identifier . id ) ;
125165 }
126166 }
127167 }
128168 if ( newMethods . length > 0 ) {
129169 macroMethods . set ( lvalue . identifier . id , newMethods ) ;
130170 }
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- }
149171 } else if (
150- isFbtJsxExpression ( fbtMacroTags , fbtValues , value ) ||
151- isFbtJsxChild ( fbtValues , lvalue , value )
172+ value . kind === 'PropertyLoad' &&
173+ macroTagsCalls . has ( value . object . identifier . id )
152174 ) {
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 ;
175+ macroTagsCalls . add ( lvalue . identifier . id ) ;
176+ } else if (
177+ isFbtJsxExpression ( fbtMacroTags , macroTagsCalls , value ) ||
178+ isFbtJsxChild ( macroTagsCalls , lvalue , value ) ||
179+ isFbtCallExpression ( macroTagsCalls , value )
180+ ) {
181+ macroTagsCalls . add ( lvalue . identifier . id ) ;
182+ macroValues . set (
183+ lvalue . identifier ,
184+ Array . from (
185+ eachInstructionValueOperand ( value ) ,
186+ operand => operand . identifier ,
187+ ) ,
188+ ) ;
189+ } else if (
190+ Iterable_some ( eachInstructionValueOperand ( value ) , operand =>
191+ macroValues . has ( operand . identifier ) ,
192+ )
193+ ) {
194+ const macroOperands : Array < Identifier > = [ ] ;
195+ for ( const operand of eachInstructionValueOperand ( value ) ) {
196+ if ( macroValues . has ( operand . identifier ) ) {
197+ macroOperands . push ( operand . identifier ) ;
191198 }
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 ) ;
196199 }
200+ macroValues . set ( lvalue . identifier , macroOperands ) ;
197201 }
198202 }
199203 }
200204}
201205
206+ function mergeScopes (
207+ root : Identifier ,
208+ scope : ReactiveScope ,
209+ macroValues : Map < Identifier , Array < Identifier > > ,
210+ macroTagsCalls : Set < IdentifierId > ,
211+ ) : void {
212+ const operands = macroValues . get ( root ) ;
213+ if ( operands == null ) {
214+ return ;
215+ }
216+ for ( const operand of operands ) {
217+ operand . scope = scope ;
218+ expandFbtScopeRange ( scope . range , operand . mutableRange ) ;
219+ macroTagsCalls . add ( operand . id ) ;
220+ mergeScopes ( operand , scope , macroValues , macroTagsCalls ) ;
221+ }
222+ }
223+
202224function matchesExactTag ( s : string , tags : Set < Macro > ) : boolean {
203225 return Array . from ( tags ) . some ( macro =>
204226 typeof macro === 'string'
@@ -229,39 +251,40 @@ function matchTagRoot(
229251}
230252
231253function isFbtCallExpression (
232- fbtValues : Set < IdentifierId > ,
233- value : ReactiveValue ,
254+ macroTagsCalls : Set < IdentifierId > ,
255+ value : InstructionValue ,
234256) : boolean {
235257 return (
236258 ( value . kind === 'CallExpression' &&
237- fbtValues . has ( value . callee . identifier . id ) ) ||
238- ( value . kind === 'MethodCall' && fbtValues . has ( value . property . identifier . id ) )
259+ macroTagsCalls . has ( value . callee . identifier . id ) ) ||
260+ ( value . kind === 'MethodCall' &&
261+ macroTagsCalls . has ( value . property . identifier . id ) )
239262 ) ;
240263}
241264
242265function isFbtJsxExpression (
243266 fbtMacroTags : Set < Macro > ,
244- fbtValues : Set < IdentifierId > ,
245- value : ReactiveValue ,
267+ macroTagsCalls : Set < IdentifierId > ,
268+ value : InstructionValue ,
246269) : boolean {
247270 return (
248271 value . kind === 'JsxExpression' &&
249272 ( ( value . tag . kind === 'Identifier' &&
250- fbtValues . has ( value . tag . identifier . id ) ) ||
273+ macroTagsCalls . has ( value . tag . identifier . id ) ) ||
251274 ( value . tag . kind === 'BuiltinTag' &&
252275 matchesExactTag ( value . tag . name , fbtMacroTags ) ) )
253276 ) ;
254277}
255278
256279function isFbtJsxChild (
257- fbtValues : Set < IdentifierId > ,
280+ macroTagsCalls : Set < IdentifierId > ,
258281 lvalue : Place | null ,
259- value : ReactiveValue ,
282+ value : InstructionValue ,
260283) : boolean {
261284 return (
262285 ( value . kind === 'JsxExpression' || value . kind === 'JsxFragment' ) &&
263286 lvalue !== null &&
264- fbtValues . has ( lvalue . identifier . id )
287+ macroTagsCalls . has ( lvalue . identifier . id )
265288 ) ;
266289}
267290
0 commit comments