use super::*;
use luau_common::DenseHashSet;
type ClassMemberNamespace<'ast> = DenseHashSet<AstName<'ast>, AstNameDenseHasher>;
impl<'source, 'ast, 'name, 'names> Parser<'source, 'ast, 'name, 'names>
where
'name: 'ast,
{
pub(in crate::parser) fn parse_class_statement_after_keyword(
&mut self,
location: Location,
exported: bool,
) -> Result<ParsedClassStatement<'ast>> {
let (name, name_location) = match self.current {
Token::Ident(name) => {
let name_location = self.current_token_location();
self.advance();
(name, name_location)
}
_ => {
let name_location = self.current_token_location();
self.report_parse_error(self.type_name_error());
(self.name_error, name_location)
}
};
let local = self.arena.alloc_local(Local::new(LocalInit {
name,
location: name_location,
shadow: None,
function_depth: self.contexts.functions.len(),
loop_depth: self.current_function().loop_depth,
annotation: None,
is_const: true,
is_exported: exported,
}));
let super_class = if matches!(self.current, Token::Ident(name) if name == "extends") {
self.advance();
Some(self.parse_class_reference_expression()?)
} else {
None
};
let mut members = self.temp_class_members();
let mut namespace = ClassMemberNamespace::new(AstName::empty_key());
while self.current.kind() != TokenKind::Eof
&& self.current.kind() != TokenKind::Reserved(R::End)
{
let before = self.current_token_location();
let qualifier_location = if matches!(self.current, Token::Ident(name) if name == "public")
{
let location = self.current_token_location();
self.advance();
Some(location)
} else {
None
};
let member = if let Some(qualifier_location) = qualifier_location {
if self.current.kind() == TokenKind::Reserved(R::Function) {
self.parse_class_method(&mut namespace, Some(qualifier_location))
} else {
self.parse_class_property(&mut namespace, qualifier_location)
}
} else {
match self.current {
Token::Reserved(R::Function) => self.parse_class_method(&mut namespace, None),
_ => {
self.report_parse_error(self.located(
"Only class properties and functions can be declared within a class",
));
self.advance();
continue;
}
}
};
match member {
Ok(Some(member)) => members.push_back(member),
Ok(None) => {}
Err(error) => {
self.report_parse_error(error);
}
}
if self.current_token_location() == before {
self.advance();
}
}
let end_location = self.current_token_location();
self.expect_and_consume_keyword(R::End, "class");
if self.contexts.recursion_counter > 1 {
self.report_parse_error(self.error_at(
local.location,
self.name_message(
b"Cannot declare class '",
local.name,
b"' inside another statement or expression",
),
));
}
let class_location = Location::new(location.begin, end_location.end);
let members = self.arena.alloc_slice_copy(members.as_slice());
let class_statement = self.arena.alloc_statement_node(StatementClass::new(
class_location,
false,
local,
super_class,
members,
exported,
));
if self.locals.classes_within_module.contains(&local.name) {
let message_index = self.report_parse_error(self.error_at(
local.location,
self.name_message(
b"A class named '",
local.name,
b"' has already been declared in this module",
),
));
let statement = self.arena.alloc_statement_node(StatementError::new(
local.location,
false,
self.empty_expression_slice(),
self.arena.alloc_slice_copy(&[class_statement]),
message_index,
));
return Ok(ParsedClassStatement { statement, local });
}
self.locals
.classes_within_module
.insert(local.name, Some(class_statement.as_class_unchecked()));
Ok(ParsedClassStatement {
statement: class_statement,
local,
})
}
fn parse_class_reference_expression(&mut self) -> Result<Expression<'ast>> {
let begin = self.current_token_location().begin;
let expression = self.parse_name_expression("class reference expression")?;
match self.current {
Token::Dot => {
let op_position = self.current_position();
self.advance();
let index =
self.parse_index_name(Some("class reference expression"), op_position)?;
Ok(self.arena.alloc_expression_index_name_direct(
Location::new(begin, index.location.end),
expression,
index.name,
index.location,
op_position,
IndexNameOp::Dot,
))
}
Token::LeftBracket => {
let open = self.current_location();
self.advance();
let index = self.parse_expression()?;
let end = self.current_token_location().end;
self.expect_match_token(Token::RightBracket, "]", "[", open, false);
Ok(self.alloc_expression(ExpressionInit::new(
Location::new(begin, end),
ExpressionKind::IndexExpr {
expr: expression,
index,
},
)))
}
_ => Ok(expression),
}
}
pub(in crate::parser) fn parse_class_property(
&mut self,
namespace: &mut ClassMemberNamespace<'ast>,
qualifier_location: Location,
) -> Result<Option<ClassMember<'ast>>> {
let Token::Ident(name) = self.current else {
self.report_parse_error(self.located(format!(
"Expected identifier when parsing class property name, got {}",
self.current
)));
return Ok(None);
};
let name_location = self.current_token_location();
self.advance();
let (type_colon, ty) = if self.current.kind() == TokenKind::Colon {
let colon = self.current_token_location();
self.advance();
let annotation = self.parse_type_annotation()?;
(Some(colon), Some(annotation))
} else {
(None, None)
};
if namespace.contains(&name) {
self.report_parse_error(self.error_at(
name_location,
self.name_message(b"Duplicate class member '", name, b"'"),
));
return Ok(None);
}
namespace.insert(name);
let name_bytes = name.bytes();
if name_bytes.starts_with(b"__") {
self.report_parse_error(
self.error_at(name_location, "Class properties cannot start with '__'"),
);
}
Ok(Some(ClassMember::Property {
qualifier_location,
name,
name_location,
type_colon_location: type_colon,
ty,
}))
}
pub(in crate::parser) fn parse_class_method(
&mut self,
namespace: &mut ClassMemberNamespace<'ast>,
qualifier_location: Option<Location>,
) -> Result<Option<ClassMember<'ast>>> {
let function_location = self.current_token_location();
let function_keyword = self.current_position();
self.advance();
let name = self.parse_name("method name");
let name_location = name.location;
let name = name.name;
let parsed = self.parse_function_after_name(FunctionParseContext {
attributes: ParsedAttributes::default(),
location: function_location,
match_location: function_location,
function_keyword,
self_parameter: None,
debug_name: Some(name),
})?;
let function = parsed.function;
if let Some(self_parameter) = function.args.first()
&& self_parameter.name == "self"
&& let Some(annotation) = self_parameter.annotation
{
self.report_parse_error(self.error_at(
annotation.location,
"The 'self' parameter cannot have a type annotation",
));
}
let name_bytes = name.bytes();
if name_bytes.starts_with(b"__") {
match name_bytes {
b"__index" | b"__newindex" | b"__mode" | b"__metatable" | b"__type" => {
self.report_parse_error(self.error_at(
name_location,
self.name_message(b"Classes cannot define '", name, b"' as a metamethod"),
));
}
b"__call" | b"__concat" | b"__unm" | b"__add" | b"__sub" | b"__mul" | b"__div"
| b"__mod" | b"__pow" | b"__tostring" | b"__eq" | b"__lt" | b"__le" | b"__iter"
| b"__len" | b"__idiv" => {}
_ => {
self.report_parse_error(self.error_at(
name_location,
self.name_message(
b"Cannot use '",
name,
b"' as a method name: names starting with '__' are reserved",
),
));
}
}
}
if namespace.contains(&name) {
self.report_parse_error(self.error_at(
name_location,
self.name_message(b"Duplicate class member '", name, b"'"),
));
return Ok(None);
}
namespace.insert(name);
Ok(Some(ClassMember::Method {
qualifier_location,
keyword_location: function_location,
function_name: name,
name_location,
function,
}))
}
}
pub(in crate::parser) struct ParsedClassStatement<'ast> {
pub(in crate::parser) statement: Statement<'ast>,
pub(in crate::parser) local: &'ast Local<'ast>,
}