Skip to main content

luaur_ast/methods/
parser_parse_class_stat.rs

1use crate::records::ast_array::AstArray;
2use crate::records::ast_attr::AstAttr;
3use crate::records::ast_class_method::AstClassMethod;
4use crate::records::ast_class_property::AstClassProperty;
5use crate::records::ast_local::AstLocal;
6use crate::records::ast_name::AstName;
7use crate::records::ast_stat::AstStat;
8use crate::records::ast_stat_class::AstStatClass;
9use crate::records::ast_type::AstType;
10use crate::records::binding::Binding;
11use crate::records::lexeme::{Lexeme, Type};
12use crate::records::location::Location;
13use crate::records::name::Name;
14use crate::records::parser::Parser;
15use crate::records::position::Position;
16use crate::records::temp_vector::TempVector;
17use crate::type_aliases::ast_class_member::AstClassMember;
18use luaur_common::records::dense_hash_set::DenseHashSet;
19use luaur_common::records::variant::Variant2;
20use luaur_common::FFlag;
21
22const ALLOWED_METAMETHODS: &[&str] = &[
23    "__call",
24    "__concat",
25    "__unm",
26    "__add",
27    "__sub",
28    "__mul",
29    "__div",
30    "__mod",
31    "__pow",
32    "__tostring",
33    "__eq",
34    "__lt",
35    "__le",
36    "__iter",
37    "__len",
38    "__idiv",
39];
40
41const EXPLICITLY_DISALLOWED_METAMETHODS: &[&str] =
42    &["__index", "__newindex", "__mode", "__metatable", "__type"];
43
44impl Parser {
45    pub fn parse_class_stat(&mut self, start: &Location, exported: bool) -> *mut AstStat {
46        luaur_common::LUAU_ASSERT!(FFlag::DebugLuauUserDefinedClasses.get());
47        let name_opt = self.parse_name_opt("type name");
48
49        let name = name_opt.unwrap_or_else(|| Name {
50            name: self.name_error,
51            location: self.lexer.current().location,
52        });
53
54        let saved_locals = self.save_locals();
55
56        let binding = Binding::new(name, core::ptr::null_mut(), Position::default(), true);
57        let name_local = self.push_local(&binding);
58
59        let mut declarations = TempVector::new(&mut self.scratch_class_declarations);
60
61        let mut class_member_namespace: DenseHashSet<AstName> =
62            DenseHashSet::new(AstName::default());
63
64        while self.lexer.current().r#type != Type::ReservedEnd
65            && self.lexer.current().r#type != Type::Eof
66        {
67            let mut qualifier_location: Option<Location> = None;
68            if self.lexer.current().r#type == Type::Name
69                && unsafe {
70                    AstName::ast_name_c_char(self.lexer.current().data.name)
71                        .operator_eq_c_char(c"public".as_ptr())
72                }
73            {
74                qualifier_location = Some(self.lexer.current().location);
75                self.next_lexeme();
76            }
77
78            if qualifier_location.is_some() && self.lexer.current().r#type != Type::ReservedFunction
79            {
80                let prop_name_opt = self.parse_name_opt("class property name");
81                if prop_name_opt.is_none() {
82                    continue;
83                }
84                let prop_name = prop_name_opt.unwrap();
85
86                let mut prop_type: *mut AstType = core::ptr::null_mut();
87                let mut type_colon_location: Option<Location> = None;
88
89                if self.lexer.current().r#type == Type(':' as i32) {
90                    type_colon_location = Some(self.lexer.current().location);
91                    self.next_lexeme();
92                    prop_type = self.parse_type_bool(false);
93                }
94
95                unsafe {
96                    if !prop_name.name.value.is_null()
97                        && *prop_name.name.value == b'_' as core::ffi::c_char
98                        && *prop_name.name.value.add(1) == b'_' as core::ffi::c_char
99                    {
100                        self.report_location_c_char_item(
101                            prop_name.location,
102                            format_args!("Class properties cannot start with '__'"),
103                        );
104                    }
105                }
106
107                if class_member_namespace.contains(&prop_name.name) {
108                    unsafe {
109                        self.report_location_c_char_item(
110                            prop_name.location,
111                            format_args!(
112                                "Duplicate class member '{}'",
113                                core::ffi::CStr::from_ptr(prop_name.name.value).to_string_lossy()
114                            ),
115                        );
116                    }
117                } else {
118                    class_member_namespace.insert(prop_name.name);
119
120                    luaur_common::LUAU_ASSERT!(
121                        prop_type.is_null() == type_colon_location.is_none()
122                    );
123                    declarations.push_back(Variant2::V0(AstClassProperty {
124                        qualifier_location: qualifier_location.unwrap_or_default(),
125                        name: prop_name.name,
126                        name_location: prop_name.location,
127                        type_colon_location,
128                        ty: prop_type,
129                    }));
130                }
131            } else if self.lexer.current().r#type == Type::ReservedFunction {
132                let match_function = *self.lexer.current();
133                self.next_lexeme();
134
135                let name = self.parse_name("method name");
136
137                self.match_recovery_stop_on_token[Type::ReservedEnd.0 as usize] += 1;
138
139                let (body, _) = self.parse_function_body(
140                    false,
141                    &match_function,
142                    &name.name,
143                    None,
144                    &AstArray::default(),
145                    false,
146                );
147
148                self.match_recovery_stop_on_token[Type::ReservedEnd.0 as usize] -= 1;
149
150                unsafe {
151                    if (*body).args.size > 0 {
152                        let first_arg = *(*body).args.data;
153                        if (*first_arg).name.operator_eq_c_char(c"self".as_ptr())
154                            && !(*first_arg).annotation.is_null()
155                        {
156                            self.report_location_c_char_item(
157                                (*(*first_arg).annotation).base.location,
158                                format_args!("The 'self' parameter cannot have a type annotation"),
159                            );
160                        }
161                    }
162                }
163
164                let name_str = unsafe {
165                    core::ffi::CStr::from_ptr(name.name.value)
166                        .to_str()
167                        .unwrap_or("")
168                };
169                if name_str.starts_with("__") {
170                    if EXPLICITLY_DISALLOWED_METAMETHODS.contains(&name_str) {
171                        self.report_location_c_char_item(
172                            name.location,
173                            format_args!("Classes cannot define '{}' as a metamethod", name_str),
174                        );
175                    } else if !ALLOWED_METAMETHODS.contains(&name_str) {
176                        self.report_location_c_char_item(
177                            name.location,
178                            format_args!("Cannot use '{}' as a method name: names starting with '__' are reserved", name_str),
179                        );
180                    }
181                }
182
183                if class_member_namespace.contains(&name.name) {
184                    unsafe {
185                        self.report_location_c_char_item(
186                            name.location,
187                            format_args!(
188                                "Duplicate class member '{}'",
189                                core::ffi::CStr::from_ptr(name.name.value).to_string_lossy()
190                            ),
191                        );
192                    }
193                } else {
194                    class_member_namespace.insert(name.name);
195
196                    declarations.push_back(Variant2::V1(AstClassMethod {
197                        qualifier_location,
198                        keyword_location: match_function.location,
199                        function_name: name.name,
200                        name_location: name.location,
201                        function: body,
202                    }));
203                }
204            } else {
205                self.report_location_c_char_item(
206                    self.lexer.current().location,
207                    format_args!(
208                        "Only class properties and functions can be declared within a class"
209                    ),
210                );
211                self.next_lexeme();
212            }
213        }
214
215        let end = self.lexer.current().location;
216        self.expect_and_consume_type(Type::ReservedEnd, "class");
217        let location = Location::new(start.begin, end.end);
218
219        if self.recursion_counter > 1 {
220            unsafe {
221                self.report_location_c_char_item(
222                    (*name_local).location,
223                    format_args!(
224                        "Cannot declare class '{}' inside another statement or expression",
225                        core::ffi::CStr::from_ptr((*name_local).name.value).to_string_lossy()
226                    ),
227                );
228            }
229        }
230
231        let copied_declarations = self.copy_temp_vector_t(&declarations);
232        let cls = unsafe {
233            (*self.allocator).alloc(AstStatClass::new(
234                location,
235                name_local,
236                copied_declarations,
237                exported,
238            )) as *mut AstStat
239        };
240
241        let name_local_name = unsafe { (*name_local).name };
242        if self.classes_within_module.contains(&name_local_name) {
243            self.restore_locals(saved_locals);
244            let expressions = self.copy_initializer_list_t(&[]);
245            let statements = self.copy_initializer_list_t(&[cls]);
246            unsafe {
247                return self.report_stat_error(
248                    (*name_local).location,
249                    expressions,
250                    statements,
251                    format_args!(
252                        "A class named '{}' has already been declared in this module",
253                        core::ffi::CStr::from_ptr(name_local_name.value).to_string_lossy()
254                    ),
255                ) as *mut AstStat;
256            }
257        }
258        self.classes_within_module.insert(name_local_name);
259        cls
260    }
261}