Skip to main content

luaur_ast/methods/
parser_parse_export_value.rs

1use crate::records::ast_array::AstArray;
2use crate::records::ast_attr::AstAttr;
3use crate::records::ast_local::AstLocal;
4use crate::records::ast_name::AstName;
5use crate::records::ast_stat::AstStat;
6use crate::records::ast_stat_local::AstStatLocal;
7use crate::records::cst_stat_local::CstStatLocal;
8use crate::records::lexeme::Type;
9use crate::records::location::Location;
10use crate::records::name::Name;
11use crate::records::parser::Parser;
12use crate::records::position::Position;
13use luaur_common::macros::luau_assert::LUAU_ASSERT;
14
15impl Parser {
16    fn check_duplicate_export_value(&mut self, name: AstName, location: Location) -> bool {
17        if self.declared_export_bindings.find(&name).is_some() {
18            return false;
19        }
20
21        *self.declared_export_bindings.get_or_insert(name) = location;
22        true
23    }
24
25    fn export_local_stat_value(
26        &mut self,
27        stat: *mut AstStat,
28        keyword_position: Position,
29    ) -> *mut AstStat {
30        let local_stat = unsafe {
31            crate::rtti::ast_node_as::<AstStatLocal>(stat as *mut crate::records::ast_node::AstNode)
32        };
33        if !local_stat.is_null() {
34            unsafe {
35                (*local_stat).is_exported = true;
36            }
37
38            let vars = unsafe { (*local_stat).vars };
39            for i in 0..vars.size {
40                let local = unsafe { *vars.data.add(i) };
41                if !self.check_duplicate_export_value(unsafe { (*local).name }, unsafe {
42                    (*local).location
43                }) {
44                    let stats = self.copy_initializer_list_t(&[stat as *mut AstStat]);
45                    return self.report_stat_error(
46                        unsafe { (*local).location },
47                        AstArray {
48                            data: core::ptr::null_mut(),
49                            size: 0,
50                        },
51                        stats,
52                        format_args!("Duplicate exported identifier '{}'", unsafe {
53                            core::ffi::CStr::from_ptr((*local).name.value).to_string_lossy()
54                        }),
55                    ) as *mut AstStat;
56                }
57
58                unsafe {
59                    (*local).is_exported = true;
60                }
61            }
62
63            if self.options.store_cst_data {
64                let cst_stat_local = unsafe {
65                    let cst_node = self
66                        .cst_node_map
67                        .find(&(stat as *mut crate::records::ast_node::AstNode));
68                    if let Some(cst_node_ptr) = cst_node {
69                        crate::rtti::cst_node_as::<CstStatLocal>(*cst_node_ptr)
70                    } else {
71                        core::ptr::null_mut()
72                    }
73                };
74                LUAU_ASSERT!(!cst_stat_local.is_null());
75                if !cst_stat_local.is_null() {
76                    unsafe {
77                        (*cst_stat_local).declaration_keyword_position = keyword_position;
78                    }
79                }
80            }
81        } else {
82            LUAU_ASSERT!(
83                false,
84                "Expected export local/const to parse as AstStatLocal"
85            );
86        }
87
88        stat
89    }
90
91    pub fn parse_export_value(
92        &mut self,
93        start: &Location,
94        keyword_position: Position,
95        attributes: &AstArray<*mut AstAttr>,
96    ) -> *mut AstStat {
97        if self.function_stack.len() != 1 || self.recursion_counter != 1 {
98            self.report_location_c_char_item(
99                *start,
100                format_args!("'export' may only be applied to top-level statements"),
101            );
102        }
103
104        if self.has_module_return {
105            self.report_location_c_char_item(
106                *start,
107                format_args!("Exporting values is not compatible with top-level return (export/return conflict)"),
108            );
109        }
110
111        if attributes.size != 0 && self.lexer.current().r#type != Type::ReservedFunction {
112            self.report_location_c_char_item(
113                self.lexer.current().location,
114                format_args!(
115                    "Expected 'function' after export declaration with attribute, but got {} instead",
116                    self.lexer.current().to_string()
117                ),
118            );
119        }
120
121        if self.lexer.current().r#type == Type::ReservedLocal {
122            let local_keyword_position = self.lexer.current().location.begin;
123
124            if self.lexer.lookahead().r#type == Type::ReservedFunction {
125                return self.report_stat_error(
126                    *start,
127                    AstArray { data: core::ptr::null_mut(), size: 0 },
128                    AstArray { data: core::ptr::null_mut(), size: 0 },
129                    format_args!("'export' must be followed by an identifier or 'function'; try removing 'local'"),
130                ) as *mut AstStat;
131            }
132
133            let stat = self.parse_local(
134                *start,
135                keyword_position,
136                &AstArray {
137                    data: core::ptr::null_mut(),
138                    size: 0,
139                },
140                false,
141            );
142            return self.export_local_stat_value(stat, local_keyword_position);
143        } else if self.lexer.current().r#type == Type::ReservedFunction {
144            let func_stat = self.parse_local(*start, keyword_position, attributes, true);
145            if !crate::rtti::ast_node_is::<
146                crate::records::ast_stat_local_function::AstStatLocalFunction,
147            >(func_stat as *mut crate::records::ast_node::AstNode)
148            {
149                return func_stat;
150            }
151
152            let func = unsafe {
153                func_stat as *mut crate::records::ast_stat_local_function::AstStatLocalFunction
154            };
155            let name = unsafe { (*func).name };
156            if !self
157                .check_duplicate_export_value(unsafe { (*name).name }, unsafe { (*name).location })
158            {
159                let stats = self.copy_initializer_list_t(&[func_stat as *mut AstStat]);
160                return self.report_stat_error(
161                    unsafe { (*name).location },
162                    AstArray {
163                        data: core::ptr::null_mut(),
164                        size: 0,
165                    },
166                    stats,
167                    format_args!("Duplicate exported identifier '{}'", unsafe {
168                        core::ffi::CStr::from_ptr((*name).name.value).to_string_lossy()
169                    }),
170                ) as *mut AstStat;
171            }
172
173            unsafe {
174                (*name).is_exported = true;
175                (*name).is_const = true;
176            }
177            func_stat
178        } else if self.lexer.current().r#type == Type::Name
179            && unsafe { AstName::ast_name_c_char(self.lexer.current().data.name) }
180                .operator_eq_c_char(b"const\0".as_ptr() as *const core::ffi::c_char)
181        {
182            let const_keyword_position = self.lexer.current().location.begin;
183            self.next_lexeme();
184
185            if self.lexer.current().r#type == Type::ReservedFunction {
186                return self.report_stat_error(
187                    *start,
188                    AstArray {
189                        data: core::ptr::null_mut(),
190                        size: 0,
191                    },
192                    AstArray {
193                        data: core::ptr::null_mut(),
194                        size: 0,
195                    },
196                    format_args!("'export' must be followed by an identifier or 'function'"),
197                ) as *mut AstStat;
198            }
199
200            let stat = self.parse_local(
201                *start,
202                const_keyword_position,
203                &AstArray {
204                    data: core::ptr::null_mut(),
205                    size: 0,
206                },
207                true,
208            );
209            return self.export_local_stat_value(stat, const_keyword_position);
210        } else if luaur_common::FFlag::DebugLuauUserDefinedClasses.get()
211            && self.lexer.current().r#type == Type::Name
212            && unsafe { AstName::ast_name_c_char(self.lexer.current().data.name) }
213                .operator_eq_c_char(b"class\0".as_ptr() as *const core::ffi::c_char)
214        {
215            self.next_lexeme();
216            let stat = self.parse_class_stat(start, true);
217            let class_stat = unsafe { stat as *mut crate::records::ast_stat_class::AstStatClass };
218            if !class_stat.is_null() {
219                let name = unsafe { (*class_stat).name };
220                if !self.check_duplicate_export_value(unsafe { (*name).name }, unsafe {
221                    (*name).location
222                }) {
223                    let stats = self.copy_initializer_list_t(&[stat as *mut AstStat]);
224                    return self.report_stat_error(
225                        unsafe { (*name).location },
226                        AstArray {
227                            data: core::ptr::null_mut(),
228                            size: 0,
229                        },
230                        stats,
231                        format_args!("Duplicate exported class '{}'", unsafe {
232                            core::ffi::CStr::from_ptr((*name).name.value).to_string_lossy()
233                        }),
234                    ) as *mut AstStat;
235                }
236
237                unsafe {
238                    (*name).is_exported = true;
239                }
240            }
241            stat
242        } else {
243            self.report_stat_error(
244                *start,
245                AstArray {
246                    data: core::ptr::null_mut(),
247                    size: 0,
248                },
249                AstArray {
250                    data: core::ptr::null_mut(),
251                    size: 0,
252                },
253                format_args!("'export' must be followed by an identifier or 'function'"),
254            ) as *mut AstStat
255        }
256    }
257}