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));
}
}
}
}