1use crate::enums::table_constant_kind::TableConstantKind;
2use crate::records::variable::Variable;
3use luaur_ast::records::ast_expr::AstExpr;
4use luaur_ast::records::ast_expr_binary::AstExprBinary;
5use luaur_ast::records::ast_expr_call::AstExprCall;
6use luaur_ast::records::ast_expr_constant_bool::AstExprConstantBool;
7use luaur_ast::records::ast_expr_constant_integer::AstExprConstantInteger;
8use luaur_ast::records::ast_expr_constant_nil::AstExprConstantNil;
9use luaur_ast::records::ast_expr_constant_number::AstExprConstantNumber;
10use luaur_ast::records::ast_expr_constant_string::AstExprConstantString;
11use luaur_ast::records::ast_expr_function::AstExprFunction;
12use luaur_ast::records::ast_expr_global::AstExprGlobal;
13use luaur_ast::records::ast_expr_group::AstExprGroup;
14use luaur_ast::records::ast_expr_if_else::AstExprIfElse;
15use luaur_ast::records::ast_expr_index_expr::AstExprIndexExpr;
16use luaur_ast::records::ast_expr_index_name::AstExprIndexName;
17use luaur_ast::records::ast_expr_instantiate::AstExprInstantiate;
18use luaur_ast::records::ast_expr_interp_string::AstExprInterpString;
19use luaur_ast::records::ast_expr_local::AstExprLocal;
20use luaur_ast::records::ast_expr_table::AstExprTable;
21use luaur_ast::records::ast_expr_type_assertion::AstExprTypeAssertion;
22use luaur_ast::records::ast_expr_unary::AstExprUnary;
23use luaur_ast::records::ast_expr_varargs::AstExprVarargs;
24use luaur_ast::records::ast_stat_assign::AstStatAssign;
25use luaur_ast::records::ast_stat_compound_assign::AstStatCompoundAssign;
26use luaur_ast::records::ast_stat_for_in::AstStatForIn;
27use luaur_ast::records::ast_stat_function::AstStatFunction;
28use luaur_ast::records::ast_stat_local::AstStatLocal;
29use luaur_ast::records::ast_stat_return::AstStatReturn;
30use luaur_ast::records::ast_visitor::AstVisitor;
31use luaur_common::macros::luau_assert::LUAU_ASSERT;
32use luaur_common::records::dense_hash_map::DenseHashMap;
33use luaur_common::FFlag;
34
35#[derive(Debug)]
36pub struct TableMutationTrackerDeprecated<'a> {
37 pub(crate) constant_tables:
38 &'a mut DenseHashMap<*mut luaur_ast::records::ast_local::AstLocal, TableConstantKind>,
39 pub(crate) variables: &'a DenseHashMap<*mut luaur_ast::records::ast_local::AstLocal, Variable>,
40}
41
42impl<'a> TableMutationTrackerDeprecated<'a> {
43 pub fn table_mutation_tracker_deprecated(
44 constant_tables: &'a mut DenseHashMap<
45 *mut luaur_ast::records::ast_local::AstLocal,
46 TableConstantKind,
47 >,
48 variables: &'a DenseHashMap<*mut luaur_ast::records::ast_local::AstLocal, Variable>,
49 ) -> Self {
50 LUAU_ASSERT!(FFlag::LuauCompilePropagateTableProps2.get());
51 Self {
52 constant_tables,
53 variables,
54 }
55 }
56
57 pub fn is_non_table_constant(&self, node: *mut AstExpr) -> bool {
58 unsafe {
59 if let Some(expr) = unsafe {
60 luaur_ast::rtti::ast_node_as::<AstExprGroup>(
61 node as *mut luaur_ast::records::ast_node::AstNode,
62 )
63 .as_mut()
64 } {
65 return self.is_non_table_constant(expr.expr as *mut AstExpr);
66 }
67
68 if luaur_ast::rtti::ast_node_is::<AstExprConstantNil>(
69 node as *mut luaur_ast::records::ast_node::AstNode,
70 ) {
71 return true;
72 } else if luaur_ast::rtti::ast_node_is::<AstExprConstantBool>(
73 node as *mut luaur_ast::records::ast_node::AstNode,
74 ) {
75 return true;
76 } else if luaur_ast::rtti::ast_node_is::<AstExprConstantNumber>(
77 node as *mut luaur_ast::records::ast_node::AstNode,
78 ) {
79 return true;
80 } else if luaur_ast::rtti::ast_node_is::<AstExprConstantInteger>(
81 node as *mut luaur_ast::records::ast_node::AstNode,
82 ) {
83 return true;
84 } else if luaur_ast::rtti::ast_node_is::<AstExprConstantString>(
85 node as *mut luaur_ast::records::ast_node::AstNode,
86 ) {
87 return true;
88 } else if luaur_ast::rtti::ast_node_is::<AstExprLocal>(
89 node as *mut luaur_ast::records::ast_node::AstNode,
90 ) {
91 let expr = &*(node as *mut AstExprLocal);
92 if let Some(kind) = self.constant_tables.find(&expr.local) {
93 return *kind == TableConstantKind::ConstantOther;
94 }
95 return false;
96 } else if luaur_ast::rtti::ast_node_is::<AstExprGlobal>(
97 node as *mut luaur_ast::records::ast_node::AstNode,
98 ) {
99 return false;
100 } else if luaur_ast::rtti::ast_node_is::<AstExprVarargs>(
101 node as *mut luaur_ast::records::ast_node::AstNode,
102 ) {
103 return false;
104 } else if luaur_ast::rtti::ast_node_is::<AstExprCall>(
105 node as *mut luaur_ast::records::ast_node::AstNode,
106 ) {
107 return false;
108 } else if luaur_ast::rtti::ast_node_is::<AstExprIndexName>(
109 node as *mut luaur_ast::records::ast_node::AstNode,
110 ) {
111 let expr = &*(node as *mut AstExprIndexName);
112 let local = luaur_ast::rtti::ast_node_as::<AstExprLocal>(
113 expr.expr as *mut luaur_ast::records::ast_node::AstNode,
114 );
115 if local.is_null() {
116 return false;
117 }
118 let local_ref = &*local;
119 if let Some(kind) = self.constant_tables.find(&local_ref.local) {
120 return *kind == TableConstantKind::ConstantTable;
121 }
122 return false;
123 } else if luaur_ast::rtti::ast_node_is::<AstExprIndexExpr>(
124 node as *mut luaur_ast::records::ast_node::AstNode,
125 ) {
126 let expr = &*(node as *mut AstExprIndexExpr);
127 let local = luaur_ast::rtti::ast_node_as::<AstExprLocal>(
128 expr.expr as *mut luaur_ast::records::ast_node::AstNode,
129 );
130 if local.is_null() {
131 return false;
132 }
133 let local_ref = &*local;
134 if let Some(kind) = self.constant_tables.find(&local_ref.local) {
135 return *kind == TableConstantKind::ConstantTable
136 && self.is_non_table_constant(expr.index as *mut AstExpr);
137 }
138 return false;
139 } else if luaur_ast::rtti::ast_node_is::<AstExprFunction>(
140 node as *mut luaur_ast::records::ast_node::AstNode,
141 ) {
142 return false;
143 } else if luaur_ast::rtti::ast_node_is::<AstExprTable>(
144 node as *mut luaur_ast::records::ast_node::AstNode,
145 ) {
146 return false;
147 } else if luaur_ast::rtti::ast_node_is::<AstExprUnary>(
148 node as *mut luaur_ast::records::ast_node::AstNode,
149 ) {
150 let expr = &*(node as *mut AstExprUnary);
151 return self.is_non_table_constant(expr.expr as *mut AstExpr);
152 } else if luaur_ast::rtti::ast_node_is::<AstExprBinary>(
153 node as *mut luaur_ast::records::ast_node::AstNode,
154 ) {
155 let expr = &*(node as *mut AstExprBinary);
156 return self.is_non_table_constant(expr.left as *mut AstExpr)
157 && self.is_non_table_constant(expr.right as *mut AstExpr);
158 } else if luaur_ast::rtti::ast_node_is::<AstExprTypeAssertion>(
159 node as *mut luaur_ast::records::ast_node::AstNode,
160 ) {
161 let expr = &*(node as *mut AstExprTypeAssertion);
162 return self.is_non_table_constant(expr.expr as *mut AstExpr);
163 } else if luaur_ast::rtti::ast_node_is::<AstExprIfElse>(
164 node as *mut luaur_ast::records::ast_node::AstNode,
165 ) {
166 let expr = &*(node as *mut AstExprIfElse);
167 return self.is_non_table_constant(expr.condition as *mut AstExpr)
168 && self.is_non_table_constant(expr.true_expr as *mut AstExpr)
169 && self.is_non_table_constant(expr.false_expr as *mut AstExpr);
170 } else if luaur_ast::rtti::ast_node_is::<AstExprInterpString>(
171 node as *mut luaur_ast::records::ast_node::AstNode,
172 ) {
173 let expr = &*(node as *mut AstExprInterpString);
174 for i in 0..expr.expressions.size {
175 let expression = *expr.expressions.data.add(i);
176 if !self.is_non_table_constant(expression as *mut AstExpr) {
177 return false;
178 }
179 }
180 return true;
181 } else if luaur_ast::rtti::ast_node_is::<AstExprInstantiate>(
182 node as *mut luaur_ast::records::ast_node::AstNode,
183 ) {
184 let expr = &*(node as *mut AstExprInstantiate);
185 return self.is_non_table_constant(expr.expr as *mut AstExpr);
186 }
187
188 LUAU_ASSERT!(false);
189 }
190 false
191 }
192
193 pub fn is_constant_table_literal(&self, node: *mut AstExpr) -> bool {
194 unsafe {
195 if let Some(table) = unsafe {
196 luaur_ast::rtti::ast_node_as::<AstExprTable>(
197 node as *mut luaur_ast::records::ast_node::AstNode,
198 )
199 .as_mut()
200 } {
201 for i in 0..table.items.size {
202 let item = &*table.items.data.add(i);
203 if !item.key.is_null() {
204 if !self.is_non_table_constant(item.key as *mut AstExpr) {
205 return false;
206 }
207 }
208 if !self.is_non_table_constant(item.value as *mut AstExpr) {
209 return false;
210 }
211 }
212 return true;
213 }
214
215 if let Some(group) = unsafe {
216 luaur_ast::rtti::ast_node_as::<AstExprGroup>(
217 node as *mut luaur_ast::records::ast_node::AstNode,
218 )
219 .as_mut()
220 } {
221 return self.is_constant_table_literal(group.expr as *mut AstExpr);
222 }
223
224 if let Some(assert) = unsafe {
225 luaur_ast::rtti::ast_node_as::<AstExprTypeAssertion>(
226 node as *mut luaur_ast::records::ast_node::AstNode,
227 )
228 .as_mut()
229 } {
230 return self.is_constant_table_literal(assert.expr as *mut AstExpr);
231 }
232
233 if let Some(instantiate) = unsafe {
234 luaur_ast::rtti::ast_node_as::<AstExprInstantiate>(
235 node as *mut luaur_ast::records::ast_node::AstNode,
236 )
237 .as_mut()
238 } {
239 return self.is_constant_table_literal(instantiate.expr as *mut AstExpr);
240 }
241
242 false
243 }
244 }
245
246 pub fn could_be_table_reference(&self, node: *mut AstExpr) -> bool {
247 unsafe {
248 if let Some(expr) = unsafe {
249 luaur_ast::rtti::ast_node_as::<AstExprGroup>(
250 node as *mut luaur_ast::records::ast_node::AstNode,
251 )
252 .as_mut()
253 } {
254 return self.could_be_table_reference(expr.expr as *mut AstExpr);
255 } else if let Some(expr) = unsafe {
256 luaur_ast::rtti::ast_node_as::<AstExprTypeAssertion>(
257 node as *mut luaur_ast::records::ast_node::AstNode,
258 )
259 .as_mut()
260 } {
261 return self.could_be_table_reference(expr.expr as *mut AstExpr);
262 } else if let Some(expr) = unsafe {
263 luaur_ast::rtti::ast_node_as::<AstExprInstantiate>(
264 node as *mut luaur_ast::records::ast_node::AstNode,
265 )
266 .as_mut()
267 } {
268 return self.could_be_table_reference(expr.expr as *mut AstExpr);
269 } else if let Some(expr) = unsafe {
270 luaur_ast::rtti::ast_node_as::<AstExprIfElse>(
271 node as *mut luaur_ast::records::ast_node::AstNode,
272 )
273 .as_mut()
274 } {
275 return self.could_be_table_reference(expr.true_expr as *mut AstExpr)
276 || self.could_be_table_reference(expr.false_expr as *mut AstExpr);
277 } else if let Some(bin_expr) = unsafe {
278 luaur_ast::rtti::ast_node_as::<AstExprBinary>(
279 node as *mut luaur_ast::records::ast_node::AstNode,
280 )
281 .as_mut()
282 } {
283 if bin_expr.op == luaur_ast::records::ast_expr_binary::AstExprBinaryOp::And
284 || bin_expr.op == luaur_ast::records::ast_expr_binary::AstExprBinaryOp::Or
285 {
286 return self.could_be_table_reference(bin_expr.left as *mut AstExpr)
287 || self.could_be_table_reference(bin_expr.right as *mut AstExpr);
288 }
289 }
290
291 if luaur_ast::rtti::ast_node_is::<AstExprLocal>(
292 node as *mut luaur_ast::records::ast_node::AstNode,
293 ) {
294 return true;
295 }
296
297 false
298 }
299 }
300
301 pub fn observe_mutations(&mut self, node: *mut AstExpr, could_mutate_table: bool) {
302 unsafe {
303 if let Some(expr) = unsafe {
304 luaur_ast::rtti::ast_node_as::<AstExprGroup>(
305 node as *mut luaur_ast::records::ast_node::AstNode,
306 )
307 .as_mut()
308 } {
309 self.observe_mutations(expr.expr as *mut AstExpr, could_mutate_table);
310 } else if luaur_ast::rtti::ast_node_is::<AstExprConstantNil>(
311 node as *mut luaur_ast::records::ast_node::AstNode,
312 ) {
313 return;
314 } else if luaur_ast::rtti::ast_node_is::<AstExprConstantBool>(
315 node as *mut luaur_ast::records::ast_node::AstNode,
316 ) {
317 return;
318 } else if luaur_ast::rtti::ast_node_is::<AstExprConstantNumber>(
319 node as *mut luaur_ast::records::ast_node::AstNode,
320 ) {
321 return;
322 } else if luaur_ast::rtti::ast_node_is::<AstExprConstantInteger>(
323 node as *mut luaur_ast::records::ast_node::AstNode,
324 ) {
325 return;
326 } else if luaur_ast::rtti::ast_node_is::<AstExprConstantString>(
327 node as *mut luaur_ast::records::ast_node::AstNode,
328 ) {
329 return;
330 } else if let Some(expr) = unsafe {
331 luaur_ast::rtti::ast_node_as::<AstExprLocal>(
332 node as *mut luaur_ast::records::ast_node::AstNode,
333 )
334 .as_mut()
335 } {
336 let local = expr.local;
337 if could_mutate_table && self.constant_tables.contains_key(&local) {
338 *self.constant_tables.get_or_insert(local) = TableConstantKind::NotConstant;
339 }
340 } else if luaur_ast::rtti::ast_node_is::<AstExprGlobal>(
341 node as *mut luaur_ast::records::ast_node::AstNode,
342 ) {
343 return;
344 } else if luaur_ast::rtti::ast_node_is::<AstExprVarargs>(
345 node as *mut luaur_ast::records::ast_node::AstNode,
346 ) {
347 return;
348 } else if let Some(expr) = unsafe {
349 luaur_ast::rtti::ast_node_as::<AstExprCall>(
350 node as *mut luaur_ast::records::ast_node::AstNode,
351 )
352 .as_mut()
353 } {
354 self.observe_mutations(expr.func as *mut AstExpr, true);
355 for i in 0..expr.args.size {
356 let arg = *expr.args.data.add(i);
357 let could_mutate = self.could_be_table_reference(arg as *mut AstExpr);
358 self.observe_mutations(arg as *mut AstExpr, could_mutate);
359 }
360 } else if let Some(expr) = unsafe {
361 luaur_ast::rtti::ast_node_as::<AstExprIndexName>(
362 node as *mut luaur_ast::records::ast_node::AstNode,
363 )
364 .as_mut()
365 } {
366 self.observe_mutations(expr.expr as *mut AstExpr, could_mutate_table);
367 } else if let Some(expr) = unsafe {
368 luaur_ast::rtti::ast_node_as::<AstExprIndexExpr>(
369 node as *mut luaur_ast::records::ast_node::AstNode,
370 )
371 .as_mut()
372 } {
373 self.observe_mutations(expr.index as *mut AstExpr, false);
374 self.observe_mutations(expr.expr as *mut AstExpr, could_mutate_table);
375 } else if let Some(expr) = unsafe {
376 luaur_ast::rtti::ast_node_as::<AstExprFunction>(
377 node as *mut luaur_ast::records::ast_node::AstNode,
378 )
379 .as_mut()
380 } {
381 luaur_ast::visit::ast_stat_visit(
382 expr.body as *mut luaur_ast::records::ast_stat::AstStat,
383 self,
384 );
385 } else if let Some(expr) = unsafe {
386 luaur_ast::rtti::ast_node_as::<AstExprTable>(
387 node as *mut luaur_ast::records::ast_node::AstNode,
388 )
389 .as_mut()
390 } {
391 for i in 0..expr.items.size {
392 let item = &*expr.items.data.add(i);
393 if !item.key.is_null() {
394 self.observe_mutations(item.key as *mut AstExpr, false);
395 }
396 self.observe_mutations(
397 item.value as *mut AstExpr,
398 self.could_be_table_reference(item.value as *mut AstExpr),
399 );
400 }
401 } else if let Some(expr) = unsafe {
402 luaur_ast::rtti::ast_node_as::<AstExprUnary>(
403 node as *mut luaur_ast::records::ast_node::AstNode,
404 )
405 .as_mut()
406 } {
407 self.observe_mutations(expr.expr as *mut AstExpr, false);
408 } else if let Some(expr) = unsafe {
409 luaur_ast::rtti::ast_node_as::<AstExprBinary>(
410 node as *mut luaur_ast::records::ast_node::AstNode,
411 )
412 .as_mut()
413 } {
414 let short_circuiting = expr.op
415 == luaur_ast::records::ast_expr_binary::AstExprBinaryOp::And
416 || expr.op == luaur_ast::records::ast_expr_binary::AstExprBinaryOp::Or;
417 self.observe_mutations(expr.left as *mut AstExpr, short_circuiting);
418 self.observe_mutations(expr.right as *mut AstExpr, short_circuiting);
419 } else if let Some(expr) = unsafe {
420 luaur_ast::rtti::ast_node_as::<AstExprTypeAssertion>(
421 node as *mut luaur_ast::records::ast_node::AstNode,
422 )
423 .as_mut()
424 } {
425 self.observe_mutations(expr.expr as *mut AstExpr, could_mutate_table);
426 } else if let Some(expr) = unsafe {
427 luaur_ast::rtti::ast_node_as::<AstExprIfElse>(
428 node as *mut luaur_ast::records::ast_node::AstNode,
429 )
430 .as_mut()
431 } {
432 self.observe_mutations(expr.condition as *mut AstExpr, false);
433 self.observe_mutations(expr.true_expr as *mut AstExpr, could_mutate_table);
434 self.observe_mutations(expr.false_expr as *mut AstExpr, could_mutate_table);
435 } else if let Some(expr) = unsafe {
436 luaur_ast::rtti::ast_node_as::<AstExprInterpString>(
437 node as *mut luaur_ast::records::ast_node::AstNode,
438 )
439 .as_mut()
440 } {
441 for i in 0..expr.expressions.size {
442 let expression = *expr.expressions.data.add(i);
443 self.observe_mutations(expression as *mut AstExpr, false);
444 }
445 } else if let Some(expr) = unsafe {
446 luaur_ast::rtti::ast_node_as::<AstExprInstantiate>(
447 node as *mut luaur_ast::records::ast_node::AstNode,
448 )
449 .as_mut()
450 } {
451 self.observe_mutations(expr.expr as *mut AstExpr, could_mutate_table);
452 } else {
453 LUAU_ASSERT!(false);
454 }
455 }
456 }
457}
458
459impl<'a> AstVisitor for TableMutationTrackerDeprecated<'a> {
460 fn visit_expr(&mut self, node: *mut core::ffi::c_void) -> bool {
461 let node = node as *mut AstExpr;
462 self.observe_mutations(node, false);
463 false
464 }
465
466 fn visit_stat_local(&mut self, node: *mut core::ffi::c_void) -> bool {
467 unsafe {
468 let node = &*(node as *mut AstStatLocal);
469
470 for i in 0..node.vars.size.min(node.values.size) {
471 let local_ptr = *node.vars.data.add(i);
472 let rhs = *node.values.data.add(i);
473
474 let v = self.variables.find(&local_ptr);
475 LUAU_ASSERT!(v.is_some());
476 let v = v.unwrap();
477
478 if !v.written {
479 if self.is_constant_table_literal(rhs as *mut AstExpr) {
480 *self.constant_tables.get_or_insert(local_ptr) =
481 TableConstantKind::ConstantTable;
482 } else if self.is_non_table_constant(rhs as *mut AstExpr) {
483 *self.constant_tables.get_or_insert(local_ptr) =
484 TableConstantKind::ConstantOther;
485 }
486 }
487
488 if !self.constant_tables.contains_key(&local_ptr) {
489 self.observe_mutations(
490 rhs as *mut AstExpr,
491 self.could_be_table_reference(rhs as *mut AstExpr),
492 );
493 }
494 }
495
496 if node.vars.size < node.values.size {
497 for i in node.vars.size..node.values.size {
498 let rhs = *node.values.data.add(i);
499 self.observe_mutations(rhs as *mut AstExpr, false);
500 }
501 }
502
503 false
504 }
505 }
506
507 fn visit_stat_assign(&mut self, node: *mut core::ffi::c_void) -> bool {
508 unsafe {
509 let node = &*(node as *mut AstStatAssign);
510
511 for i in 0..node.vars.size.min(node.values.size) {
512 let rhs = *node.values.data.add(i);
513 self.observe_mutations(
514 rhs as *mut AstExpr,
515 self.could_be_table_reference(rhs as *mut AstExpr),
516 );
517 }
518
519 if node.values.size > node.vars.size {
520 for i in node.vars.size..node.values.size {
521 let rhs = *node.values.data.add(i);
522 self.observe_mutations(rhs as *mut AstExpr, false);
523 }
524 }
525
526 for i in 0..node.vars.size {
527 let lhs = *node.vars.data.add(i);
528 self.observe_mutations(lhs as *mut AstExpr, true);
529 }
530
531 false
532 }
533 }
534
535 fn visit_stat_compound_assign(&mut self, node: *mut core::ffi::c_void) -> bool {
536 unsafe {
537 let node = &*(node as *mut AstStatCompoundAssign);
538 let rhs = node.value as *mut AstExpr;
539 self.observe_mutations(rhs, self.could_be_table_reference(rhs));
540 self.observe_mutations(node.var as *mut AstExpr, true);
541 false
542 }
543 }
544
545 fn visit_stat_function(&mut self, node: *mut core::ffi::c_void) -> bool {
546 unsafe {
547 let node = &*(node as *mut AstStatFunction);
548 self.observe_mutations(node.func as *mut AstExpr, false);
549 self.observe_mutations(node.name as *mut AstExpr, true);
550 false
551 }
552 }
553
554 fn visit_stat_return(&mut self, node: *mut core::ffi::c_void) -> bool {
555 unsafe {
556 let node = &*(node as *mut AstStatReturn);
557 for i in 0..node.list.size {
558 let expr = *node.list.data.add(i);
559 self.observe_mutations(
560 expr as *mut AstExpr,
561 self.could_be_table_reference(expr as *mut AstExpr),
562 );
563 }
564 false
565 }
566 }
567
568 fn visit_stat_for_in(&mut self, node: *mut core::ffi::c_void) -> bool {
569 unsafe {
570 let node = &*(node as *mut AstStatForIn);
571
572 for i in 0..node.values.size {
573 let expr = *node.values.data.add(i);
574 self.observe_mutations(expr as *mut AstExpr, true);
575 }
576
577 luaur_ast::visit::ast_stat_visit(
578 node.body as *mut luaur_ast::records::ast_stat::AstStat,
579 self,
580 );
581 false
582 }
583 }
584}