5
5
* LICENSE file in the root directory of this source tree.
6
6
*/
7
7
8
- import { CompilerError , CompilerErrorDetailOptions , ErrorSeverity } from '..' ;
8
+ import {
9
+ CompilerError ,
10
+ CompilerErrorDetailOptions ,
11
+ ErrorSeverity ,
12
+ SourceLocation ,
13
+ } from '..' ;
9
14
import {
10
15
CallExpression ,
11
16
Effect ,
@@ -28,14 +33,11 @@ import {
28
33
import { createTemporaryPlace , markInstructionIds } from '../HIR/HIRBuilder' ;
29
34
import { getOrInsertWith } from '../Utils/utils' ;
30
35
import { BuiltInFireId , DefaultNonmutatingHook } from '../HIR/ObjectShape' ;
36
+ import { eachInstructionOperand } from '../HIR/visitors' ;
37
+ import { printSourceLocationLine } from '../HIR/PrintHIR' ;
31
38
32
39
/*
33
40
* TODO(jmbrown):
34
- * In this stack:
35
- * - Assert no lingering fire calls
36
- * - Ensure a fired function is not called regularly elsewhere in the same effect
37
- *
38
- * Future:
39
41
* - rewrite dep arrays
40
42
* - traverse object methods
41
43
* - method calls
@@ -47,6 +49,9 @@ const CANNOT_COMPILE_FIRE = 'Cannot compile `fire`';
47
49
export function transformFire ( fn : HIRFunction ) : void {
48
50
const context = new Context ( fn . env ) ;
49
51
replaceFireFunctions ( fn , context ) ;
52
+ if ( ! context . hasErrors ( ) ) {
53
+ ensureNoMoreFireUses ( fn , context ) ;
54
+ }
50
55
context . throwIfErrorsFound ( ) ;
51
56
}
52
57
@@ -120,6 +125,11 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
120
125
}
121
126
rewriteInstrs . set ( loadUseEffectInstrId , newInstrs ) ;
122
127
}
128
+ ensureNoRemainingCalleeCaptures (
129
+ lambda . loweredFunc . func ,
130
+ context ,
131
+ capturedCallees ,
132
+ ) ;
123
133
}
124
134
}
125
135
} else if (
@@ -159,7 +169,10 @@ function replaceFireFunctions(fn: HIRFunction, context: Context): void {
159
169
}
160
170
161
171
const fireFunctionBinding =
162
- context . getOrGenerateFireFunctionBinding ( loadLocal . place ) ;
172
+ context . getOrGenerateFireFunctionBinding (
173
+ loadLocal . place ,
174
+ value . loc ,
175
+ ) ;
163
176
164
177
loadLocal . place = { ...fireFunctionBinding } ;
165
178
@@ -320,6 +333,69 @@ function visitFunctionExpressionAndPropagateFireDependencies(
320
333
return calleesCapturedByFnExpression ;
321
334
}
322
335
336
+ /*
337
+ * eachInstructionOperand is not sufficient for our cases because:
338
+ * 1. fire is a global, which will not appear
339
+ * 2. The HIR may be malformed, so can't rely on function deps and must
340
+ * traverse the whole function.
341
+ */
342
+ function * eachReachablePlace ( fn : HIRFunction ) : Iterable < Place > {
343
+ for ( const [ , block ] of fn . body . blocks ) {
344
+ for ( const instr of block . instructions ) {
345
+ if (
346
+ instr . value . kind === 'FunctionExpression' ||
347
+ instr . value . kind === 'ObjectMethod'
348
+ ) {
349
+ yield * eachReachablePlace ( instr . value . loweredFunc . func ) ;
350
+ } else {
351
+ yield * eachInstructionOperand ( instr ) ;
352
+ }
353
+ }
354
+ }
355
+ }
356
+
357
+ function ensureNoRemainingCalleeCaptures (
358
+ fn : HIRFunction ,
359
+ context : Context ,
360
+ capturedCallees : FireCalleesToFireFunctionBinding ,
361
+ ) : void {
362
+ for ( const place of eachReachablePlace ( fn ) ) {
363
+ const calleeInfo = capturedCallees . get ( place . identifier . id ) ;
364
+ if ( calleeInfo != null ) {
365
+ const calleeName =
366
+ calleeInfo . capturedCalleeIdentifier . name ?. kind === 'named'
367
+ ? calleeInfo . capturedCalleeIdentifier . name . value
368
+ : '<unknown>' ;
369
+ context . pushError ( {
370
+ loc : place . loc ,
371
+ description : `All uses of ${ calleeName } must be either used with a fire() call in \
372
+ this effect or not used with a fire() call at all. ${ calleeName } was used with fire() on line \
373
+ ${ printSourceLocationLine ( calleeInfo . fireLoc ) } in this effect`,
374
+ severity : ErrorSeverity . InvalidReact ,
375
+ reason : CANNOT_COMPILE_FIRE ,
376
+ suggestions : null ,
377
+ } ) ;
378
+ }
379
+ }
380
+ }
381
+
382
+ function ensureNoMoreFireUses ( fn : HIRFunction , context : Context ) : void {
383
+ for ( const place of eachReachablePlace ( fn ) ) {
384
+ if (
385
+ place . identifier . type . kind === 'Function' &&
386
+ place . identifier . type . shapeId === BuiltInFireId
387
+ ) {
388
+ context . pushError ( {
389
+ loc : place . identifier . loc ,
390
+ description : 'Cannot use `fire` outside of a useEffect function' ,
391
+ severity : ErrorSeverity . Invariant ,
392
+ reason : CANNOT_COMPILE_FIRE ,
393
+ suggestions : null ,
394
+ } ) ;
395
+ }
396
+ }
397
+ }
398
+
323
399
function makeLoadUseFireInstruction ( env : Environment ) : Instruction {
324
400
const useFirePlace = createTemporaryPlace ( env , GeneratedSource ) ;
325
401
useFirePlace . effect = Effect . Read ;
@@ -422,6 +498,7 @@ type FireCalleesToFireFunctionBinding = Map<
422
498
{
423
499
fireFunctionBinding : Place ;
424
500
capturedCalleeIdentifier : Identifier ;
501
+ fireLoc : SourceLocation ;
425
502
}
426
503
> ;
427
504
@@ -523,8 +600,10 @@ class Context {
523
600
getLoadLocalInstr ( id : IdentifierId ) : LoadLocal | undefined {
524
601
return this . #loadLocals. get ( id ) ;
525
602
}
526
-
527
- getOrGenerateFireFunctionBinding ( callee : Place ) : Place {
603
+ getOrGenerateFireFunctionBinding (
604
+ callee : Place ,
605
+ fireLoc : SourceLocation ,
606
+ ) : Place {
528
607
const fireFunctionBinding = getOrInsertWith (
529
608
this . #fireCalleesToFireFunctions,
530
609
callee . identifier . id ,
@@ -534,6 +613,7 @@ class Context {
534
613
this . #capturedCalleeIdentifierIds. set ( callee . identifier . id , {
535
614
fireFunctionBinding,
536
615
capturedCalleeIdentifier : callee . identifier ,
616
+ fireLoc,
537
617
} ) ;
538
618
539
619
return fireFunctionBinding ;
@@ -575,8 +655,12 @@ class Context {
575
655
return this . #loadGlobalInstructionIds. get ( id ) ;
576
656
}
577
657
658
+ hasErrors ( ) : boolean {
659
+ return this . #errors. hasErrors ( ) ;
660
+ }
661
+
578
662
throwIfErrorsFound ( ) : void {
579
- if ( this . #errors . hasErrors ( ) ) throw this . #errors;
663
+ if ( this . hasErrors ( ) ) throw this . #errors;
580
664
}
581
665
}
582
666
0 commit comments