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 { printIdentifier } from '../HIR/PrintHIR' ;
20+ import { eachInstructionValueOperand } from '../HIR/visitors' ;
21+ import { Iterable_some } from '../Utils/utils' ;
1822
1923/**
2024 * This pass supports the `fbt` translation system (https://facebook.github.io/fbt/)
@@ -48,24 +52,49 @@ export function memoizeFbtAndMacroOperandsInSameScope(
4852 ...Array . from ( FBT_TAGS ) . map ( ( tag ) : Macro => [ tag , [ ] ] ) ,
4953 ...( fn . env . config . customMacros ?? [ ] ) ,
5054 ] ) ;
51- const fbtValues : Set < IdentifierId > = new Set ( ) ;
55+ /**
56+ * Set of all identifiers that load fbt or other macro functions or their nested
57+ * properties, as well as values known to be the results of invoking macros
58+ */
59+ const macroTagsCalls : Set < IdentifierId > = new Set ( ) ;
60+ /**
61+ * Mapping of lvalue => list of operands for all expressions where either
62+ * the lvalue is a known fbt/macro call and/or the operands transitively
63+ * contain fbt/macro calls.
64+ *
65+ * This is the key data structure that powers the scope merging: we start
66+ * at the lvalues and merge operands into the lvalue's scope.
67+ */
68+ const macroValues : Map < Identifier , Array < Identifier > > = new Map ( ) ;
69+ // Tracks methods loaded from macros, like fbt.param or idx.foo
5270 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 ;
71+
72+ visit ( fn , fbtMacroTags , macroTagsCalls , macroMethods , macroValues ) ;
73+
74+ for ( const root of macroValues . keys ( ) ) {
75+ const scope = root . scope ;
76+ if ( scope == null ) {
77+ continue ;
78+ }
79+ // Merge the operands into the same scope if this is a known macro invocation
80+ if ( ! macroTagsCalls . has ( root . id ) ) {
81+ continue ;
5982 }
83+ mergeScopes ( root , scope , macroValues , macroTagsCalls ) ;
6084 }
61- return fbtValues ;
85+
86+ return macroTagsCalls ;
6287}
6388
6489export const FBT_TAGS : Set < string > = new Set ( [
6590 'fbt' ,
6691 'fbt:param' ,
92+ 'fbt:enum' ,
93+ 'fbt:plural' ,
6794 'fbs' ,
6895 'fbs:param' ,
96+ 'fbs:enum' ,
97+ 'fbs:plural' ,
6998] ) ;
7099export const SINGLE_CHILD_FBT_TAGS : Set < string > = new Set ( [
71100 'fbt:param' ,
@@ -75,10 +104,22 @@ export const SINGLE_CHILD_FBT_TAGS: Set<string> = new Set([
75104function visit (
76105 fn : HIRFunction ,
77106 fbtMacroTags : Set < Macro > ,
78- fbtValues : Set < IdentifierId > ,
107+ macroTagsCalls : Set < IdentifierId > ,
79108 macroMethods : Map < IdentifierId , Array < Array < MacroMethod > > > ,
109+ macroValues : Map < Identifier , Array < Identifier > > ,
80110) : void {
81111 for ( const [ , block ] of fn . body . blocks ) {
112+ for ( const phi of block . phis ) {
113+ const macroOperands : Array < Identifier > = [ ] ;
114+ for ( const operand of phi . operands . values ( ) ) {
115+ if ( macroValues . has ( operand . identifier ) ) {
116+ macroOperands . push ( operand . identifier ) ;
117+ }
118+ }
119+ if ( macroOperands . length !== 0 ) {
120+ macroValues . set ( phi . place . identifier , macroOperands ) ;
121+ }
122+ }
82123 for ( const instruction of block . instructions ) {
83124 const { lvalue, value} = instruction ;
84125 if ( lvalue === null ) {
@@ -93,13 +134,13 @@ function visit(
93134 * We don't distinguish between tag names and strings, so record
94135 * all `fbt` string literals in case they are used as a jsx tag.
95136 */
96- fbtValues . add ( lvalue . identifier . id ) ;
137+ macroTagsCalls . add ( lvalue . identifier . id ) ;
97138 } else if (
98139 value . kind === 'LoadGlobal' &&
99140 matchesExactTag ( value . binding . name , fbtMacroTags )
100141 ) {
101142 // Record references to `fbt` as a global
102- fbtValues . add ( lvalue . identifier . id ) ;
143+ macroTagsCalls . add ( lvalue . identifier . id ) ;
103144 } else if (
104145 value . kind === 'LoadGlobal' &&
105146 matchTagRoot ( value . binding . name , fbtMacroTags ) !== null
@@ -121,84 +162,66 @@ function visit(
121162 if ( method . length > 1 ) {
122163 newMethods . push ( method . slice ( 1 ) ) ;
123164 } else {
124- fbtValues . add ( lvalue . identifier . id ) ;
165+ macroTagsCalls . add ( lvalue . identifier . id ) ;
125166 }
126167 }
127168 }
128169 if ( newMethods . length > 0 ) {
129170 macroMethods . set ( lvalue . identifier . id , newMethods ) ;
130171 }
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- }
149172 } else if (
150- isFbtJsxExpression ( fbtMacroTags , fbtValues , value ) ||
151- isFbtJsxChild ( fbtValues , lvalue , value )
173+ value . kind === 'PropertyLoad' &&
174+ macroTagsCalls . has ( value . object . identifier . id )
152175 ) {
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 ;
176+ macroTagsCalls . add ( lvalue . identifier . id ) ;
177+ } else if (
178+ isFbtJsxExpression ( fbtMacroTags , macroTagsCalls , value ) ||
179+ isFbtJsxChild ( macroTagsCalls , lvalue , value ) ||
180+ isFbtCallExpression ( macroTagsCalls , value )
181+ ) {
182+ macroTagsCalls . add ( lvalue . identifier . id ) ;
183+ macroValues . set (
184+ lvalue . identifier ,
185+ Array . from (
186+ eachInstructionValueOperand ( value ) ,
187+ operand => operand . identifier ,
188+ ) ,
189+ ) ;
190+ } else if (
191+ Iterable_some ( eachInstructionValueOperand ( value ) , operand =>
192+ macroValues . has ( operand . identifier ) ,
193+ )
194+ ) {
195+ const macroOperands : Array < Identifier > = [ ] ;
196+ for ( const operand of eachInstructionValueOperand ( value ) ) {
197+ if ( macroValues . has ( operand . identifier ) ) {
198+ macroOperands . push ( operand . identifier ) ;
191199 }
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 ) ;
196200 }
201+ macroValues . set ( lvalue . identifier , macroOperands ) ;
197202 }
198203 }
199204 }
200205}
201206
207+ function mergeScopes (
208+ root : Identifier ,
209+ scope : ReactiveScope ,
210+ macroValues : Map < Identifier , Array < Identifier > > ,
211+ macroTagsCalls : Set < IdentifierId > ,
212+ ) : void {
213+ const operands = macroValues . get ( root ) ;
214+ if ( operands == null ) {
215+ return ;
216+ }
217+ for ( const operand of operands ) {
218+ operand . scope = scope ;
219+ expandFbtScopeRange ( scope . range , operand . mutableRange ) ;
220+ macroTagsCalls . add ( operand . id ) ;
221+ mergeScopes ( operand , scope , macroValues , macroTagsCalls ) ;
222+ }
223+ }
224+
202225function matchesExactTag ( s : string , tags : Set < Macro > ) : boolean {
203226 return Array . from ( tags ) . some ( macro =>
204227 typeof macro === 'string'
@@ -229,39 +252,40 @@ function matchTagRoot(
229252}
230253
231254function isFbtCallExpression (
232- fbtValues : Set < IdentifierId > ,
233- value : ReactiveValue ,
255+ macroTagsCalls : Set < IdentifierId > ,
256+ value : InstructionValue ,
234257) : boolean {
235258 return (
236259 ( value . kind === 'CallExpression' &&
237- fbtValues . has ( value . callee . identifier . id ) ) ||
238- ( value . kind === 'MethodCall' && fbtValues . has ( value . property . identifier . id ) )
260+ macroTagsCalls . has ( value . callee . identifier . id ) ) ||
261+ ( value . kind === 'MethodCall' &&
262+ macroTagsCalls . has ( value . property . identifier . id ) )
239263 ) ;
240264}
241265
242266function isFbtJsxExpression (
243267 fbtMacroTags : Set < Macro > ,
244- fbtValues : Set < IdentifierId > ,
245- value : ReactiveValue ,
268+ macroTagsCalls : Set < IdentifierId > ,
269+ value : InstructionValue ,
246270) : boolean {
247271 return (
248272 value . kind === 'JsxExpression' &&
249273 ( ( value . tag . kind === 'Identifier' &&
250- fbtValues . has ( value . tag . identifier . id ) ) ||
274+ macroTagsCalls . has ( value . tag . identifier . id ) ) ||
251275 ( value . tag . kind === 'BuiltinTag' &&
252276 matchesExactTag ( value . tag . name , fbtMacroTags ) ) )
253277 ) ;
254278}
255279
256280function isFbtJsxChild (
257- fbtValues : Set < IdentifierId > ,
281+ macroTagsCalls : Set < IdentifierId > ,
258282 lvalue : Place | null ,
259- value : ReactiveValue ,
283+ value : InstructionValue ,
260284) : boolean {
261285 return (
262286 ( value . kind === 'JsxExpression' || value . kind === 'JsxFragment' ) &&
263287 lvalue !== null &&
264- fbtValues . has ( lvalue . identifier . id )
288+ macroTagsCalls . has ( lvalue . identifier . id )
265289 ) ;
266290}
267291
0 commit comments