Skip to main content

luaur_ast/methods/
parser_parse_attribute.rs

1//! Node: `cxx:Method:Luau.Ast:Ast/src/Parser.cpp:994:parseAttribute`
2//!
3//! Faithful port of `Parser::parseAttribute` — parse one `@name` attribute, or
4//! a bracketed `@[ name(args), ... ]` list. Attribute arguments must be literal
5//! constants/tables; the name is validated against the known-attribute table.
6
7use crate::functions::is_constant_literal::is_constant_literal;
8use crate::functions::is_literal_table::is_literal_table;
9use crate::records::allocator::Allocator;
10use crate::records::ast_array::AstArray;
11use crate::records::ast_attr::{AstAttr, AstAttrType};
12use crate::records::ast_expr::AstExpr;
13use crate::records::ast_name::AstName;
14use crate::records::lexeme::Type;
15use crate::records::location::Location;
16use crate::records::match_lexeme::MatchLexeme;
17use crate::records::parser::Parser;
18use crate::records::temp_vector::TempVector;
19use luaur_common::LUAU_ASSERT;
20
21impl Parser {
22    pub fn parse_attribute(&mut self, attributes: &mut TempVector<'_, *mut AstAttr>) {
23        let empty: AstArray<*mut AstExpr> = AstArray {
24            data: core::ptr::null_mut(),
25            size: 0,
26        };
27
28        LUAU_ASSERT!(
29            self.lexer.current().r#type == Type::Attribute
30                || self.lexer.current().r#type == Type::AttributeOpen
31        );
32
33        if self.lexer.current().r#type == Type::Attribute {
34            let loc = self.lexer.current().location;
35            let name = unsafe { self.lexer.current().data.name };
36            let name_str = unsafe { core::ffi::CStr::from_ptr(name).to_string_lossy() };
37            let ty = self.validate_attribute(loc, &name_str, attributes, &empty);
38
39            self.next_lexeme();
40
41            let node = unsafe {
42                (*self.allocator).alloc(
43                    AstAttr::ast_attr_location_type_item_ast_array_ast_expr_ast_name(
44                        loc,
45                        ty.unwrap_or(AstAttrType::Unknown),
46                        empty,
47                        AstName { value: name },
48                    ),
49                )
50            };
51            attributes.push_back(node);
52        } else {
53            let open = *self.lexer.current();
54            self.next_lexeme();
55
56            if self.lexer.current().r#type != Type(b']' as i32) {
57                loop {
58                    let name = self.parse_name("attribute name");
59                    let name_loc = name.location;
60                    let attr_name = name.name.value;
61
62                    let ct = self.lexer.current().r#type;
63                    if ct == Type::RawString
64                        || ct == Type::QuotedString
65                        || ct == Type(b'{' as i32)
66                        || ct == Type(b'(' as i32)
67                    {
68                        let (args, args_location, _expr_location) =
69                            self.parse_call_list(core::ptr::null_mut());
70
71                        for i in 0..args.size {
72                            let arg = unsafe { *args.data.add(i) };
73                            if !is_constant_literal(arg) && !is_literal_table(arg) {
74                                self.report(
75                                    args_location,
76                                    format_args!(
77                                        "Only literals can be passed as arguments for attributes"
78                                    ),
79                                );
80                            }
81                        }
82
83                        let attr_name_str =
84                            unsafe { core::ffi::CStr::from_ptr(attr_name).to_string_lossy() };
85                        let ty =
86                            self.validate_attribute(name_loc, &attr_name_str, attributes, &args);
87
88                        let node = unsafe {
89                            (*self.allocator).alloc(
90                                AstAttr::ast_attr_location_type_item_ast_array_ast_expr_ast_name(
91                                    Location::new(name_loc.begin, args_location.end),
92                                    ty.unwrap_or(AstAttrType::Unknown),
93                                    args,
94                                    AstName { value: attr_name },
95                                ),
96                            )
97                        };
98                        attributes.push_back(node);
99                    } else {
100                        let attr_name_str =
101                            unsafe { core::ffi::CStr::from_ptr(attr_name).to_string_lossy() };
102                        let ty =
103                            self.validate_attribute(name_loc, &attr_name_str, attributes, &empty);
104                        let node = unsafe {
105                            (*self.allocator).alloc(
106                                AstAttr::ast_attr_location_type_item_ast_array_ast_expr_ast_name(
107                                    name_loc,
108                                    ty.unwrap_or(AstAttrType::Unknown),
109                                    empty,
110                                    AstName { value: attr_name },
111                                ),
112                            )
113                        };
114                        attributes.push_back(node);
115                    }
116
117                    if self.lexer.current().r#type == Type(b',' as i32) {
118                        self.next_lexeme();
119                    } else {
120                        break;
121                    }
122                }
123            } else {
124                let end_loc = self.lexer.current().location;
125                self.report(
126                    Location::new(open.location.begin, end_loc.end),
127                    format_args!("Attribute list cannot be empty"),
128                );
129
130                // autocomplete expects at least one unknown attribute.
131                let node = unsafe {
132                    (*self.allocator).alloc(
133                        AstAttr::ast_attr_location_type_item_ast_array_ast_expr_ast_name(
134                            Location::new(open.location.begin, end_loc.end),
135                            AstAttrType::Unknown,
136                            empty,
137                            self.name_error,
138                        ),
139                    )
140                };
141                attributes.push_back(node);
142            }
143
144            self.expect_match_and_consume(']', &MatchLexeme::new(&open), false);
145        }
146    }
147}