main
ts 134 lines 4.26 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 */
7
8 import {CompilerDiagnostic, CompilerError} from '..';
9 import {ErrorCategory} from '../CompilerError';
10 import {Environment} from '../HIR/Environment';
11 import {HIRFunction, IdentifierId, Place} from '../HIR';
12 import {printPlace} from '../HIR/PrintHIR';
13 import {eachInstructionValueLValue, eachPatternOperand} from '../HIR/visitors';
14
15 /**
16 * Validates that all store/load references to a given named identifier align with the
17 * "kind" of that variable (normal variable or context variable). For example, a context
18 * variable may not be loaded/stored with regular StoreLocal/LoadLocal/Destructure instructions.
19 */
20 export function validateContextVariableLValues(fn: HIRFunction): void {
21 const identifierKinds: IdentifierKinds = new Map();
22 validateContextVariableLValuesImpl(fn, identifierKinds, fn.env);
23 }
24
25 function validateContextVariableLValuesImpl(
26 fn: HIRFunction,
27 identifierKinds: IdentifierKinds,
28 env: Environment,
29 ): void {
30 for (const [, block] of fn.body.blocks) {
31 for (const instr of block.instructions) {
32 const {value} = instr;
33 switch (value.kind) {
34 case 'DeclareContext':
35 case 'StoreContext': {
36 visit(identifierKinds, value.lvalue.place, 'context', env);
37 break;
38 }
39 case 'LoadContext': {
40 visit(identifierKinds, value.place, 'context', env);
41 break;
42 }
43 case 'StoreLocal':
44 case 'DeclareLocal': {
45 visit(identifierKinds, value.lvalue.place, 'local', env);
46 break;
47 }
48 case 'LoadLocal': {
49 visit(identifierKinds, value.place, 'local', env);
50 break;
51 }
52 case 'PostfixUpdate':
53 case 'PrefixUpdate': {
54 visit(identifierKinds, value.lvalue, 'local', env);
55 break;
56 }
57 case 'Destructure': {
58 for (const lvalue of eachPatternOperand(value.lvalue.pattern)) {
59 visit(identifierKinds, lvalue, 'destructure', env);
60 }
61 break;
62 }
63 case 'ObjectMethod':
64 case 'FunctionExpression': {
65 validateContextVariableLValuesImpl(
66 value.loweredFunc.func,
67 identifierKinds,
68 env,
69 );
70 break;
71 }
72 default: {
73 for (const _ of eachInstructionValueLValue(value)) {
74 fn.env.recordError(
75 CompilerDiagnostic.create({
76 category: ErrorCategory.Todo,
77 reason:
78 'ValidateContextVariableLValues: unhandled instruction variant',
79 description: `Handle '${value.kind} lvalues`,
80 }).withDetails({
81 kind: 'error',
82 loc: value.loc,
83 message: null,
84 }),
85 );
86 }
87 }
88 }
89 }
90 }
91 }
92
93 type IdentifierKinds = Map<
94 IdentifierId,
95 {place: Place; kind: 'local' | 'context' | 'destructure'}
96 >;
97
98 function visit(
99 identifiers: IdentifierKinds,
100 place: Place,
101 kind: 'local' | 'context' | 'destructure',
102 env: Environment,
103 ): void {
104 const prev = identifiers.get(place.identifier.id);
105 if (prev !== undefined) {
106 const wasContext = prev.kind === 'context';
107 const isContext = kind === 'context';
108 if (wasContext !== isContext) {
109 if (prev.kind === 'destructure' || kind === 'destructure') {
110 env.recordError(
111 CompilerDiagnostic.create({
112 category: ErrorCategory.Todo,
113 reason: `Support destructuring of context variables`,
114 description: null,
115 }).withDetails({
116 kind: 'error',
117 loc: kind === 'destructure' ? place.loc : prev.place.loc,
118 message: null,
119 }),
120 );
121 return;
122 }
123
124 CompilerError.invariant(false, {
125 reason:
126 'Expected all references to a variable to be consistently local or context references',
127 description: `Identifier ${printPlace(place)} is referenced as a ${kind} variable, but was previously referenced as a ${prev.kind} variable`,
128 message: `this is ${prev.kind}`,
129 loc: place.loc,
130 });
131 }
132 }
133 identifiers.set(place.identifier.id, {place, kind});
134 }