Skip to main content

luaur_compiler/records/
table_mutation_tracker_deprecated.rs

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}