luau-syntax 0.732.0

Luau lexer, parser, AST, CST, and source utilities
Documentation
use super::*;

impl<'source, 'ast, 'name, 'names> Parser<'source, 'ast, 'name, 'names>
where
    'name: 'ast,
{
    pub(in crate::parser) fn parse_local(
        &mut self,
        location: Location,
        attributes: ParsedAttributes<'ast>,
    ) -> Result<ParsedStatement<'ast>> {
        let local_keyword = self.current_token_location();
        self.advance();
        self.parse_local_after_keyword(location, attributes, false, false, local_keyword)
    }

    pub(in crate::parser) fn parse_const(
        &mut self,
        location: Location,
        attributes: ParsedAttributes<'ast>,
    ) -> Result<ParsedStatement<'ast>> {
        let local_keyword = self.current_token_location();
        self.advance();
        self.parse_local_after_keyword(location, attributes, true, false, local_keyword)
    }

    pub(in crate::parser) fn parse_export_value_after_keyword(
        &mut self,
        location: Location,
        keyword: Location,
        attributes: ParsedAttributes<'ast>,
    ) -> Result<ParsedStatement<'ast>> {
        if !self.contexts.functions.is_empty() || self.contexts.recursion_counter != 1 {
            self.report_parse_error(self.error_at(
                location,
                "'export' may only be applied to top-level statements",
            ));
        }

        if self.has_module_return {
            self.report_parse_error(self.error_at(
                location,
                "Exporting values is not compatible with top-level return (export/return conflict)",
            ));
        }

        if !attributes.attributes.is_empty()
            && self.current.kind() != TokenKind::Reserved(R::Function)
        {
            self.report_parse_error(self.error_at(
                self.current_location(),
                format!(
                    "Expected 'function' after export declaration with attribute, but got {} instead",
                    self.current
                ),
            ));
        }

        match self.current {
            Token::Reserved(R::Local) => {
                if self.peek().kind() == TokenKind::Reserved(R::Function) {
                    let message_index = self.report_parse_error(self.error_at(
                        location,
                        "'export' must be followed by an identifier or 'function'; try removing 'local'",
                    ));
                    let statement = self.error_statement(location, message_index);
                    return Ok(self.parsed_statement_from_node(statement, None));
                }

                let local_keyword = self.current_token_location();
                self.advance();
                self.parse_local_after_keyword(
                    location,
                    ParsedAttributes::default(),
                    false,
                    true,
                    local_keyword,
                )
            }
            Token::Reserved(R::Function) => {
                self.parse_local_after_keyword(location, attributes, true, true, keyword)
            }
            Token::Ident(name) if name == "const" => {
                let const_keyword = self.current_token_location();
                self.advance();

                if self.current.kind() == TokenKind::Reserved(R::Function) {
                    let message_index = self.report_parse_error(self.error_at(
                        location,
                        "'export' must be followed by an identifier or 'function'",
                    ));
                    let statement = self.error_statement(location, message_index);
                    return Ok(self.parsed_statement_from_node(statement, None));
                }

                self.parse_local_after_keyword(
                    location,
                    ParsedAttributes::default(),
                    true,
                    true,
                    const_keyword,
                )
            }
            Token::Ident(name) if flags::DebugLuauUserDefinedClasses.get() && name == "class" => {
                self.advance();
                let class = self.parse_class_statement_after_keyword(location, true)?;
                let local = class.local;

                if self.declared_export_bindings.contains(&local.name) {
                    let message_index = self.report_parse_error(self.error_at(
                        local.location,
                        self.name_message(b"Duplicate exported class '", local.name, b"'"),
                    ));
                    let error = self.arena.alloc_statement_node(StatementError::new(
                        local.location,
                        false,
                        self.empty_expression_slice(),
                        self.arena.alloc_slice_fill_iter([class.statement]),
                        message_index,
                    ));
                    return Ok(self.parsed_statement_from_node(error, None));
                }

                self.declared_export_bindings
                    .insert(local.name, local.location);

                Ok(self.parsed_statement_from_node(class.statement, None))
            }
            _ => {
                let message_index = self.report_parse_error(self.error_at(
                    location,
                    "'export' must be followed by an identifier or 'function'",
                ));
                let statement = self.error_statement(location, message_index);
                Ok(self.parsed_statement_from_node(statement, None))
            }
        }
    }

    pub(in crate::parser) fn parse_local_after_keyword(
        &mut self,
        location: Location,
        attributes: ParsedAttributes<'ast>,
        is_const: bool,
        is_exported: bool,
        local_keyword: Location,
    ) -> Result<ParsedStatement<'ast>> {
        if self.current.kind() == TokenKind::Reserved(R::Function) {
            let mut match_location = self.current_token_location();
            let function_keyword = match_location.begin;
            self.advance();
            if match_location.begin.line == location.begin.line {
                match_location.begin.column = location.begin.column;
            }

            let name = self.parse_name("variable name");
            let name_location = name.location;
            let name = name.name;

            let attr_lists = attributes.attr_lists.clone();
            let parsed = self.parse_local_function_after_name(
                FunctionParseContext {
                    attributes,
                    location,
                    match_location,
                    function_keyword,
                    self_parameter: None,
                    debug_name: Some(name),
                },
                FunctionLocalBinding {
                    name,
                    location: name_location,
                    is_const,
                    is_exported,
                },
            )?;
            let statement_location = Location::new(location.begin, parsed.function.location.end);
            let local = parsed.local;

            if is_exported {
                if self.declared_export_bindings.contains(&local.name) {
                    let message_index = self.report_parse_error(self.error_at(
                        local.location,
                        self.name_message(b"Duplicate exported identifier '", local.name, b"'"),
                    ));
                    let statement = self.arena.alloc_statement_node(StatementLocalFunction::new(
                        statement_location,
                        false,
                        local,
                        parsed.function,
                        is_const,
                        if is_const && flags::LuauStoreConstKeywordBegin.get() {
                            local_keyword.begin
                        } else {
                            Position::missing()
                        },
                    ));
                    let error = self.arena.alloc_statement_node(StatementError::new(
                        local.location,
                        false,
                        self.empty_expression_slice(),
                        self.arena.alloc_slice_fill_iter([statement]),
                        message_index,
                    ));

                    return Ok(self.parsed_statement_from_node(error, None));
                }

                self.declared_export_bindings
                    .insert(local.name, local.location);
            }

            let statement = self.arena.alloc_statement_node(StatementLocalFunction::new(
                statement_location,
                false,
                local,
                parsed.function,
                is_const,
                if is_const && flags::LuauStoreConstKeywordBegin.get() {
                    local_keyword.begin
                } else {
                    Position::missing()
                },
            ));
            return Ok(self.parsed_statement_from_node(
                statement,
                if self.cst.enabled() {
                    Some(CstNode::StatLocalFunction(CstStatLocalFunction {
                        attr_lists,
                        local_keyword: local_keyword.begin,
                        function_keyword,
                    }))
                } else {
                    None
                },
            ));
        }

        if !attributes.attributes.is_empty() {
            let error_location = self.current_location();
            let error = self.located(format!(
                "Expected 'function' after local declaration with attribute, but got {} instead",
                self.current
            ));
            return Ok(self.report_statement_error(error_location, error));
        }

        let mut bindings = self.temp_bindings();
        let mut variable_commas = self.temp_positions();

        self.with_match_recovery_stop(MatchRecoveryStop::Equal, |parser| {
            parser
                .parse_binding_list_into(&mut bindings, false, &mut variable_commas, is_const)
                .map(|_| ())
        })?;

        let equals_sign_location = if self.current.kind() == TokenKind::Equal {
            Some(self.current_token_location())
        } else {
            None
        };
        let (values, value_commas) = if self.current.kind() == TokenKind::Equal {
            self.advance();
            let mut values = self.temp_expressions();
            let mut value_commas = self.temp_positions();
            self.parse_expression_list_into(&mut values, &mut value_commas)?;
            (Some(values), value_commas)
        } else {
            (None, self.temp_positions())
        };

        let values_slice = values
            .as_ref()
            .map(|values| values.as_slice())
            .unwrap_or(&[]);
        let const_underfilled = is_const && !values_slice.has_enough_values(bindings.len());

        let mut locals = self.temp_locals();
        for binding in bindings.iter() {
            locals.push_back(self.declare_local(
                binding.name,
                binding.name_location,
                binding.annotation,
                binding.is_const,
                is_exported,
            ));
        }
        let location_end = values_slice
            .last()
            .map(|value| value.location.end)
            .unwrap_or_else(|| self.previous_token_end_position());
        let location = Location::new(location.begin, location_end);

        let local_bindings = self.arena.alloc_slice_copy(locals.as_slice());
        let statement = self.arena.alloc_statement_node(StatementLocal::new(
            location,
            false,
            local_bindings,
            self.arena.alloc_slice_copy(values_slice),
            is_exported.then_some(local_keyword),
            equals_sign_location,
            is_const,
            is_exported,
        ));
        let statement = self.parsed_statement_from_node(statement, {
            if self.cst.enabled() {
                let mut variable_annotation_colons = variable_commas.nested();
                for binding in bindings.iter() {
                    variable_annotation_colons.push_back(binding.colon_position);
                }
                Some(CstNode::StatLocal(CstStatLocal {
                    declaration_keyword_position: local_keyword.begin,
                    variable_annotation_colons: self
                        .arena
                        .alloc_slice_copy(variable_annotation_colons.as_slice()),
                    variable_commas: self.arena.alloc_slice_copy(variable_commas.as_slice()),
                    value_commas: self.arena.alloc_slice_copy(value_commas.as_slice()),
                }))
            } else {
                None
            }
        });

        if const_underfilled {
            self.report_parse_error(
                self.error_at(location, "Missing initializer in const declaration"),
            );
        }

        if is_exported {
            for binding in local_bindings.iter() {
                if self.declared_export_bindings.contains(&binding.name) {
                    let message_index = self.report_parse_error(self.error_at(
                        binding.location,
                        self.name_message(b"Duplicate exported identifier '", binding.name, b"'"),
                    ));
                    let error = self.arena.alloc_statement_node(StatementError::new(
                        binding.location,
                        false,
                        self.empty_expression_slice(),
                        self.arena.alloc_slice_copy(&[statement.statement]),
                        message_index,
                    ));
                    return Ok(self.parsed_statement_from_node(error, None));
                }

                self.declared_export_bindings
                    .insert(binding.name, binding.location);
            }
        }

        Ok(statement)
    }
}