main
rs 241 lines 8.46 KB
Raw
1 use rustc_hash::FxHashMap;
2
3 use react_compiler_diagnostics::{
4 CompilerDiagnostic, CompilerDiagnosticDetail, CompilerError, ErrorCategory,
5 };
6 use react_compiler_hir::environment::Environment;
7 use react_compiler_hir::visitors::{each_instruction_value_lvalue, each_pattern_operand};
8 use react_compiler_hir::{
9 FunctionId, HirFunction, Identifier, IdentifierId, InstructionValue, Place,
10 };
11
12 /// Variable reference kind: local, context, or destructure.
13 #[derive(Debug, Clone, Copy, PartialEq, Eq)]
14 enum VarRefKind {
15 Local,
16 Context,
17 Destructure,
18 }
19
20 impl std::fmt::Display for VarRefKind {
21 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
22 match self {
23 VarRefKind::Local => write!(f, "local"),
24 VarRefKind::Context => write!(f, "context"),
25 VarRefKind::Destructure => write!(f, "destructure"),
26 }
27 }
28 }
29
30 type IdentifierKinds = FxHashMap<IdentifierId, (Place, VarRefKind)>;
31
32 /// Validates that context variable lvalues are used consistently.
33 ///
34 /// Port of ValidateContextVariableLValues.ts
35 pub fn validate_context_variable_lvalues(
36 func: &HirFunction,
37 env: &mut Environment,
38 ) -> Result<(), CompilerDiagnostic> {
39 validate_context_variable_lvalues_with_errors(
40 func,
41 &env.functions,
42 &env.identifiers,
43 &mut env.errors,
44 )
45 }
46
47 /// Like [`validate_context_variable_lvalues`], but writes diagnostics into the
48 /// provided `errors` instead of `env.errors`. Useful when the caller wants to
49 /// discard the diagnostics (e.g. when lowering is incomplete).
50 pub fn validate_context_variable_lvalues_with_errors(
51 func: &HirFunction,
52 functions: &[HirFunction],
53 identifiers: &[Identifier],
54 errors: &mut CompilerError,
55 ) -> Result<(), CompilerDiagnostic> {
56 let mut identifier_kinds: IdentifierKinds = FxHashMap::default();
57 validate_context_variable_lvalues_impl(
58 func,
59 &mut identifier_kinds,
60 functions,
61 identifiers,
62 errors,
63 )
64 }
65
66 fn validate_context_variable_lvalues_impl(
67 func: &HirFunction,
68 identifier_kinds: &mut IdentifierKinds,
69 functions: &[HirFunction],
70 identifiers: &[Identifier],
71 errors: &mut CompilerError,
72 ) -> Result<(), CompilerDiagnostic> {
73 let mut inner_function_ids: Vec<FunctionId> = Vec::new();
74
75 for (_block_id, block) in &func.body.blocks {
76 for &instr_id in &block.instructions {
77 let instr = &func.instructions[instr_id.0 as usize];
78 let value = &instr.value;
79
80 match value {
81 InstructionValue::DeclareContext { lvalue, .. }
82 | InstructionValue::StoreContext { lvalue, .. } => {
83 visit(
84 identifier_kinds,
85 &lvalue.place,
86 VarRefKind::Context,
87 identifiers,
88 errors,
89 )?;
90 }
91 InstructionValue::LoadContext { place, .. } => {
92 visit(
93 identifier_kinds,
94 place,
95 VarRefKind::Context,
96 identifiers,
97 errors,
98 )?;
99 }
100 InstructionValue::StoreLocal { lvalue, .. }
101 | InstructionValue::DeclareLocal { lvalue, .. } => {
102 visit(
103 identifier_kinds,
104 &lvalue.place,
105 VarRefKind::Local,
106 identifiers,
107 errors,
108 )?;
109 }
110 InstructionValue::LoadLocal { place, .. } => {
111 visit(
112 identifier_kinds,
113 place,
114 VarRefKind::Local,
115 identifiers,
116 errors,
117 )?;
118 }
119 InstructionValue::PostfixUpdate { lvalue, .. }
120 | InstructionValue::PrefixUpdate { lvalue, .. } => {
121 visit(
122 identifier_kinds,
123 lvalue,
124 VarRefKind::Local,
125 identifiers,
126 errors,
127 )?;
128 }
129 InstructionValue::Destructure { lvalue, .. } => {
130 for place in each_pattern_operand(&lvalue.pattern) {
131 visit(
132 identifier_kinds,
133 &place,
134 VarRefKind::Destructure,
135 identifiers,
136 errors,
137 )?;
138 }
139 }
140 InstructionValue::FunctionExpression { lowered_func, .. }
141 | InstructionValue::ObjectMethod { lowered_func, .. } => {
142 inner_function_ids.push(lowered_func.func);
143 }
144 _ => {
145 for _ in each_instruction_value_lvalue(value) {
146 errors.push_diagnostic(
147 CompilerDiagnostic::new(
148 ErrorCategory::Todo,
149 "ValidateContextVariableLValues: unhandled instruction variant",
150 None,
151 )
152 .with_detail(
153 CompilerDiagnosticDetail::Error {
154 loc: value.loc().copied(),
155 message: None,
156 identifier_name: None,
157 },
158 ),
159 );
160 }
161 }
162 }
163 }
164 }
165
166 // Process inner functions after the block loop to avoid borrow conflicts
167 for func_id in inner_function_ids {
168 let inner_func = &functions[func_id.0 as usize];
169 validate_context_variable_lvalues_impl(
170 inner_func,
171 identifier_kinds,
172 functions,
173 identifiers,
174 errors,
175 )?;
176 }
177
178 Ok(())
179 }
180
181 /// Format a place like TS `printPlace()`: `<effect> <name>$<id>`
182 fn format_place(place: &Place, identifiers: &[Identifier]) -> String {
183 let id = place.identifier;
184 let ident = &identifiers[id.0 as usize];
185 let name = match &ident.name {
186 Some(n) => n.value().to_string(),
187 None => String::new(),
188 };
189 format!("{} {}${}", place.effect, name, id.0)
190 }
191
192 fn visit(
193 identifiers: &mut IdentifierKinds,
194 place: &Place,
195 kind: VarRefKind,
196 env_identifiers: &[Identifier],
197 errors: &mut CompilerError,
198 ) -> Result<(), CompilerDiagnostic> {
199 if let Some((prev_place, prev_kind)) = identifiers.get(&place.identifier) {
200 let was_context = *prev_kind == VarRefKind::Context;
201 let is_context = kind == VarRefKind::Context;
202 if was_context != is_context {
203 if *prev_kind == VarRefKind::Destructure || kind == VarRefKind::Destructure {
204 let loc = if kind == VarRefKind::Destructure {
205 place.loc
206 } else {
207 prev_place.loc
208 };
209 errors.push_diagnostic(
210 CompilerDiagnostic::new(
211 ErrorCategory::Todo,
212 "Support destructuring of context variables",
213 None,
214 )
215 .with_detail(CompilerDiagnosticDetail::Error {
216 loc,
217 message: None,
218 identifier_name: None,
219 }),
220 );
221 return Ok(());
222 }
223 let place_str = format_place(place, env_identifiers);
224 return Err(CompilerDiagnostic::new(
225 ErrorCategory::Invariant,
226 "Expected all references to a variable to be consistently local or context references",
227 Some(format!(
228 "Identifier {} is referenced as a {} variable, but was previously referenced as a {} variable",
229 place_str, kind, prev_kind
230 )),
231 )
232 .with_detail(CompilerDiagnosticDetail::Error {
233 loc: place.loc,
234 message: Some(format!("this is {}", prev_kind)),
235 identifier_name: None,
236 }));
237 }
238 }
239 identifiers.insert(place.identifier, (place.clone(), kind));
240 Ok(())
241 }