luau-syntax 0.732.0

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

#[derive(Clone, Copy)]
pub(in crate::parser) struct AttributeArgument<'ast> {
    expression: Expression<'ast>,
    list_location: Location,
}

pub(in crate::parser) struct AttributeArguments<'ast> {
    expressions: Vec<AttributeArgument<'ast>>,
    location: Location,
    cst: CstExprCall,
    present: bool,
}

#[derive(Default)]
pub(in crate::parser) struct ParsedAttributes<'ast> {
    pub attributes: Vec<&'ast Attribute<'ast>>,
    pub attr_lists: Vec<CstAttrList>,
}

impl<'source, 'ast, 'name, 'names> Parser<'source, 'ast, 'name, 'names>
where
    'name: 'ast,
{
    pub(in crate::parser) fn parse_attributes(&mut self) -> Result<ParsedAttributes<'ast>> {
        let mut attributes = self.temp_attributes();
        let mut attr_lists = Vec::new();

        while matches!(self.current, Token::Attribute(_) | Token::AttributeOpen) {
            if self.current.kind() == TokenKind::AttributeOpen {
                let bracket_location = self.current_location();
                let attribute_start = bracket_location.begin;
                self.advance();
                let mut comma_positions = Vec::new();
                if self.current.kind() == TokenKind::RightBracket {
                    let location =
                        Location::new(attribute_start, self.current_token_location().end);
                    self.report_parse_error(self.error_at(
                        Location::new(attribute_start, self.current_token_location().end),
                        "Attribute list cannot be empty",
                    ));
                    let attribute = self.arena.alloc_attribute(Attribute {
                        location,
                        kind: AttributeKind::Unknown,
                        args: self.arena.alloc_slice_copy(&[]),
                        name: AstName::empty_key(),
                    });
                    self.attach_attribute_cst(attribute, CstAttribute::Simple { has_at: false });
                    attributes.push_back(attribute);
                    attr_lists.push(CstAttrList {
                        at_bracket_position: attribute_start,
                        close_bracket_position: self.current_token_location().begin,
                        comma_positions,
                    });
                    self.advance();
                    continue;
                }

                loop {
                    self.parse_attribute_entry(&mut attributes, true)?;
                    if self.current.kind() != TokenKind::Comma {
                        break;
                    }
                    comma_positions.push(self.current_position());
                    self.advance();
                }
                let closing_bracket_found = self.expect_match_token(
                    Token::RightBracket,
                    "]",
                    "@[",
                    bracket_location,
                    false,
                );
                attr_lists.push(CstAttrList {
                    at_bracket_position: attribute_start,
                    close_bracket_position: if closing_bracket_found {
                        self.previous_token_location().begin
                    } else {
                        Position::missing()
                    },
                    comma_positions,
                });
            } else {
                self.parse_attribute_entry(&mut attributes, false)?;
            }
        }

        Ok(ParsedAttributes {
            attributes: attributes.as_slice().to_vec(),
            attr_lists,
        })
    }

    pub(in crate::parser) fn parse_attribute_entry(
        &mut self,
        attributes: &mut TempVector<&'ast Attribute<'ast>>,
        bracketed: bool,
    ) -> Result<()> {
        let error_location = self.current_token_location();
        let name_location = self.current_token_location();
        let name = match self.current {
            Token::Attribute(name) if !bracketed => {
                self.advance();
                name
            }
            Token::Ident(name) if bracketed => {
                self.advance();
                name
            }
            _ => {
                if bracketed {
                    self.report_parse_error(self.attribute_name_error());
                    self.name_error
                } else {
                    return Err(self.error_at(error_location, "Attribute name is missing"));
                }
            }
        };
        let arguments = if bracketed && self.current_starts_attribute_arguments() {
            self.parse_attribute_arguments()?
        } else {
            AttributeArguments {
                expressions: Vec::new(),
                location: name_location,
                cst: CstExprCall {
                    open_parens: None,
                    close_parens: None,
                    comma_positions: Vec::new(),
                    explicit_types: None,
                },
                present: false,
            }
        };
        for argument in &arguments.expressions {
            if !Self::is_attribute_literal(argument.expression) {
                self.report_parse_error(self.error_at(
                    argument.list_location,
                    "Only literals can be passed as arguments for attributes",
                ));
            }
        }
        let kind = self.validate_attribute(name, error_location, attributes.as_slice());
        let location_begin = name_location.begin;
        let location_end = arguments.location.end;
        let attribute_location = Location::new(location_begin, location_end);
        if kind == AttributeKind::Deprecated {
            self.validate_deprecated_attribute_arguments(name_location, &arguments.expressions);
        }
        let argument_cst = arguments.cst;
        let arguments_present = arguments.present;
        let argument_expressions = self.arena.alloc_slice_fill_iter(
            arguments
                .expressions
                .into_iter()
                .map(|argument| argument.expression),
        );
        let attribute = self.arena.alloc_attribute(Attribute {
            location: attribute_location,
            kind,
            args: argument_expressions,
            name,
        });
        let cst = if arguments_present {
            CstAttribute::Parametrized {
                open_paren_position: argument_cst.open_parens,
                close_paren_position: argument_cst.close_parens,
                argument_commas: argument_cst.comma_positions,
            }
        } else {
            CstAttribute::Simple { has_at: !bracketed }
        };
        self.attach_attribute_cst(attribute, cst);
        attributes.push_back(attribute);
        Ok(())
    }

    pub(in crate::parser) fn validate_attribute(
        &mut self,
        name: AstName<'ast>,
        location: Location,
        attributes: &[&'ast Attribute<'ast>],
    ) -> AttributeKind {
        let name_bytes = name.bytes();
        let kind = match name_bytes {
            b"" => {
                self.report_parse_error(self.error_at(location, "Attribute name is missing"));
                AttributeKind::Unknown
            }
            b"checked" => AttributeKind::Checked,
            b"native" => AttributeKind::Native,
            b"deprecated" => AttributeKind::Deprecated,
            b"debugnoinline" if flags::DebugLuauNoInline.get() => AttributeKind::DebugNoinline,
            _ => {
                self.report_parse_error(self.error_at(
                    location,
                    format!("Invalid attribute '@{}'", ascii_display(name_bytes)),
                ));
                AttributeKind::Unknown
            }
        };

        if kind != AttributeKind::Unknown
            && attributes.iter().any(|attribute| attribute.kind == kind)
        {
            self.report_parse_error(self.error_at(
                location,
                format!(
                    "Cannot duplicate attribute '@{}'",
                    ascii_display(name_bytes)
                ),
            ));
        }

        kind
    }

    pub(in crate::parser) fn current_starts_attribute_arguments(&self) -> bool {
        matches!(
            self.current,
            Token::LeftParen
                | Token::LeftBrace
                | Token::QuotedString { .. }
                | Token::RawString { .. }
        )
    }

    pub(in crate::parser) fn parse_attribute_arguments(
        &mut self,
    ) -> Result<AttributeArguments<'ast>> {
        let parsed = self.parse_call_list(self.current_line())?;
        let list_location = parsed.location;
        let arguments = parsed.arguments;
        Ok(AttributeArguments {
            expressions: arguments
                .iter()
                .copied()
                .map(|expression| AttributeArgument {
                    expression,
                    list_location,
                })
                .collect(),
            location: list_location,
            cst: parsed.cst,
            present: true,
        })
    }

    fn attach_attribute_cst(&mut self, attribute: &'ast Attribute<'ast>, cst: CstAttribute) {
        if self.cst.enabled() {
            self.cst.insert(attribute, CstNode::Attribute(cst));
        }
    }

    pub(in crate::parser) fn is_attribute_literal(expression: Expression<'_>) -> bool {
        match expression.kind() {
            ExpressionKind::Boolean(_)
            | ExpressionKind::Integer { .. }
            | ExpressionKind::Nil
            | ExpressionKind::Number { .. }
            | ExpressionKind::String { .. } => true,
            ExpressionKind::Table { items } => items.iter().all(|item| match item {
                TableItem::List { value } => Self::is_attribute_literal(*value),
                TableItem::Record { key: _, value } => Self::is_attribute_literal(*value),
                TableItem::General { key, value } => {
                    Self::is_attribute_literal(*key) && Self::is_attribute_literal(*value)
                }
            }),
            _ => false,
        }
    }

    pub(in crate::parser) fn validate_deprecated_attribute_arguments(
        &mut self,
        name_location: Location,
        arguments: &[AttributeArgument<'ast>],
    ) {
        if arguments.is_empty() {
            return;
        }
        if arguments.len() > 1 {
            self.report_parse_error(self.error_at(
                name_location,
                "@deprecated can be parametrized only by 1 argument",
            ));
            return;
        }

        let ExpressionKind::Table { items } = arguments[0].expression.kind() else {
            self.report_parse_error(self.error_at(
                arguments[0].expression.location,
                "Unknown argument type for @deprecated",
            ));
            return;
        };

        for item in items {
            let (key, value) = match item {
                TableItem::Record { key, value } => (key, value),
                TableItem::List { value } | TableItem::General { value, .. } => {
                    let error = self.error_at(
                        value.location(),
                        "Only constants keys 'use' and 'reason' are allowed for @deprecated attribute",
                    );
                    self.report_parse_error(error);
                    continue;
                }
            };
            let ExpressionKind::String {
                value: key_value, ..
            } = key.kind()
            else {
                unreachable!("record table keys are parsed as constant strings");
            };
            let key_name = key_value.as_bytes();
            if key_name != b"use" && key_name != b"reason" {
                let mut message = b"Unknown argument '".to_vec();
                message.extend_from_slice(key_name);
                message.extend_from_slice(
                    b"' for @deprecated. Only string constants for 'use' and 'reason' are allowed",
                );
                self.report_parse_error(ParseError::new_bytes(key.location(), message));
                continue;
            }
            if !matches!(value.kind(), ExpressionKind::String { .. }) {
                let mut message = b"Only constant string allowed as value for '".to_vec();
                message.extend_from_slice(key_name);
                message.extend_from_slice(b"'");
                self.report_parse_error(ParseError::new_bytes(value.location(), message));
            }
        }
    }
}