luau-syntax 0.732.0

Luau lexer, parser, AST, CST, and source utilities
Documentation
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>,
}