luaur_ast/methods/
parser_parse_class_stat.rs1use 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}