luaur_ast/methods/
parser_parse_export_value.rs1use 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}