Skip to main content

luaur_analysis/functions/
make_string_metatable.rs

1use crate::enums::solver_mode::SolverMode;
2use crate::enums::table_state::TableState;
3use crate::functions::assign_prop_documentation_symbols::assign_prop_documentation_symbols;
4use crate::functions::attach_magic_function::attach_magic_function;
5use crate::functions::get_mutable_type::get_mutable_type_id;
6use crate::functions::make_function_builtin_definitions_alt_b::make_function_type_arena_optional_type_id_initializer_list_type_id_initializer_list_type_pack_id_initializer_list_type_id_initializer_list_type_id_bool as make_function_poly;
7use crate::methods::magic_find_handle_old_solver::magic_find_handle_old_solver;
8use crate::methods::magic_format_handle_old_solver::magic_format_handle_old_solver;
9use crate::methods::magic_format_infer::magic_format_infer;
10use crate::methods::magic_format_type_check::magic_format_type_check;
11use crate::methods::magic_gmatch_handle_old_solver::magic_gmatch_handle_old_solver;
12use crate::methods::magic_match_handle_old_solver::magic_match_handle_old_solver;
13use crate::methods::magic_match_infer::magic_match_infer;
14use crate::records::builtin_types::BuiltinTypes;
15use crate::records::function_type::FunctionType;
16use crate::records::magic_find::MagicFind;
17use crate::records::magic_function::MagicFunction;
18use crate::records::magic_function_call_context::MagicFunctionCallContext;
19use crate::records::magic_gmatch::MagicGmatch;
20use crate::records::magic_refinement_context::MagicRefinementContext;
21use crate::records::property_type::Property;
22use crate::records::table_indexer::TableIndexer;
23use crate::records::table_type::TableType;
24use crate::records::type_arena::TypeArena;
25use crate::records::type_level::TypeLevel;
26use crate::records::type_pack::TypePack;
27use crate::records::union_type::UnionType;
28use crate::records::variadic_type_pack::VariadicTypePack;
29use crate::type_aliases::props_type::Props;
30use crate::type_aliases::type_id::TypeId;
31use crate::type_aliases::type_pack_id::TypePackId;
32use alloc::string::ToString;
33use alloc::sync::Arc;
34use alloc::vec;
35use alloc::vec::Vec;
36use core::ptr::NonNull;
37
38// MagicFunction::refine has a default no-op implementation in C++.
39fn noop_refine(_context: &MagicRefinementContext) {}
40
41// The `infer` logic for gmatch / find is translated as a method on the wrapper struct that
42// ignores `self`. Provide free-fn shims so the magic vtable can hold a function pointer.
43fn gmatch_infer_shim(context: &MagicFunctionCallContext) -> bool {
44    MagicGmatch {
45        base: MagicFunction {
46            handle_old_solver: magic_gmatch_handle_old_solver,
47            infer: gmatch_infer_shim,
48            refine: noop_refine,
49            type_check: |_| false,
50        },
51    }
52    .infer(context)
53}
54
55fn find_infer_shim(context: &MagicFunctionCallContext) -> bool {
56    MagicFind {
57        base: MagicFunction {
58            handle_old_solver: magic_find_handle_old_solver,
59            infer: find_infer_shim,
60            refine: noop_refine,
61            type_check: |_| false,
62        },
63        handle_old_solver: magic_find_handle_old_solver,
64        infer: find_infer_shim,
65    }
66    .infer(context)
67}
68
69fn make_format_magic() -> Arc<MagicFunction> {
70    Arc::new(MagicFunction {
71        handle_old_solver: magic_format_handle_old_solver,
72        infer: magic_format_infer,
73        refine: noop_refine,
74        type_check: magic_format_type_check,
75    })
76}
77
78fn make_gmatch_magic() -> Arc<MagicFunction> {
79    Arc::new(MagicFunction {
80        handle_old_solver: magic_gmatch_handle_old_solver,
81        infer: gmatch_infer_shim,
82        refine: noop_refine,
83        type_check: |_| false,
84    })
85}
86
87fn make_match_magic() -> Arc<MagicFunction> {
88    Arc::new(MagicFunction {
89        handle_old_solver: magic_match_handle_old_solver,
90        infer: magic_match_infer,
91        refine: noop_refine,
92        type_check: |_| false,
93    })
94}
95
96fn make_find_magic() -> Arc<MagicFunction> {
97    Arc::new(MagicFunction {
98        handle_old_solver: magic_find_handle_old_solver,
99        infer: find_infer_shim,
100        refine: noop_refine,
101        type_check: |_| false,
102    })
103}
104
105fn read_prop(ty: TypeId) -> Property {
106    Property::rw_type_id(ty)
107}
108
109pub fn make_string_metatable(mut builtin_types: NonNull<BuiltinTypes>, mode: SolverMode) -> TypeId {
110    // C++ `NotNull<TypeArena> arena{builtinTypes->arena.get()}` — a mutable handle into the Box's contents.
111    let arena: &mut TypeArena =
112        unsafe { &mut *(&mut *builtin_types.as_mut().arena as *mut TypeArena) };
113    let builtin_types = unsafe { builtin_types.as_ref() };
114
115    let nil_type = builtin_types.nilType;
116    let number_type = builtin_types.numberType;
117    let boolean_type = builtin_types.booleanType;
118    let string_type = builtin_types.stringType;
119
120    let optional_number = arena.add_type(UnionType {
121        options: vec![nil_type, number_type],
122    });
123    let optional_string = arena.add_type(UnionType {
124        options: vec![nil_type, string_type],
125    });
126    let optional_boolean = arena.add_type(UnionType {
127        options: vec![nil_type, boolean_type],
128    });
129
130    let one_string_pack = arena.add_type_pack_initializer_list_type_id(&[string_type]);
131    let any_type_pack = builtin_types.anyTypePack;
132
133    let variadic_tail_pack: TypePackId = if mode == SolverMode::New {
134        builtin_types.unknownTypePack
135    } else {
136        any_type_pack
137    };
138    let empty_pack = arena.add_type_pack_initializer_list_type_id(&[]);
139    let string_variadic_list = arena.add_type_pack_t(VariadicTypePack {
140        ty: string_type,
141        hidden: false,
142    });
143    let number_variadic_list = arena.add_type_pack_t(VariadicTypePack {
144        ty: number_type,
145        hidden: false,
146    });
147
148    let mut format_ftv = FunctionType::function_type_new(
149        arena.add_type_pack_t(TypePack {
150            head: vec![string_type],
151            tail: Some(variadic_tail_pack),
152        }),
153        one_string_pack,
154        None,
155        false,
156    );
157    format_ftv.is_checked_function = true;
158    let format_fn = arena.add_type(format_ftv);
159    attach_magic_function(format_fn, make_format_magic());
160
161    let string_to_string_type = make_function_poly(
162        arena,
163        None,
164        Vec::new(),
165        Vec::new(),
166        vec![string_type],
167        vec![string_type],
168        /* checked */ true,
169    );
170
171    let repl_table = arena.add_type(TableType {
172        props: Props::default(),
173        indexer: Some(TableIndexer {
174            index_type: string_type,
175            index_result_type: string_type,
176            is_read_only: false,
177        }),
178        state: TableState::Generic,
179        level: TypeLevel::default(),
180        scope: core::ptr::null_mut(),
181        name: None,
182        synthetic_name: None,
183        instantiated_type_params: Vec::new(),
184        instantiated_type_pack_params: Vec::new(),
185        definition_module_name: Default::default(),
186        definition_location: Default::default(),
187        bound_to: None,
188        tags: Default::default(),
189        remaining_props: 0,
190    });
191    let repl_fn = make_function_poly(
192        arena,
193        None,
194        Vec::new(),
195        Vec::new(),
196        vec![string_type],
197        vec![string_type],
198        /* checked */ false,
199    );
200    let repl_arg_type = arena.add_type(UnionType {
201        options: vec![string_type, repl_table, repl_fn],
202    });
203    let gsub_func = make_function_poly(
204        arena,
205        Some(string_type),
206        Vec::new(),
207        Vec::new(),
208        vec![string_type, repl_arg_type, optional_number],
209        vec![string_type, number_type],
210        /* checked */ false,
211    );
212
213    let gmatch_iter_ret = arena.add_type(FunctionType::function_type_new(
214        empty_pack,
215        string_variadic_list,
216        None,
217        false,
218    ));
219    let gmatch_func = make_function_poly(
220        arena,
221        Some(string_type),
222        Vec::new(),
223        Vec::new(),
224        vec![string_type],
225        vec![gmatch_iter_ret],
226        /* checked */ true,
227    );
228    attach_magic_function(gmatch_func, make_gmatch_magic());
229
230    let mut match_func_ty = FunctionType::function_type_new(
231        arena.add_type_pack_initializer_list_type_id(&[string_type, string_type, optional_number]),
232        arena.add_type_pack_t(VariadicTypePack {
233            ty: string_type,
234            hidden: false,
235        }),
236        None,
237        false,
238    );
239    match_func_ty.is_checked_function = true;
240    let match_func = arena.add_type(match_func_ty);
241    attach_magic_function(match_func, make_match_magic());
242
243    let mut find_func_ty = FunctionType::function_type_new(
244        arena.add_type_pack_initializer_list_type_id(&[
245            string_type,
246            string_type,
247            optional_number,
248            optional_boolean,
249        ]),
250        arena.add_type_pack_t(TypePack {
251            head: vec![optional_number, optional_number],
252            tail: Some(string_variadic_list),
253        }),
254        None,
255        false,
256    );
257    find_func_ty.is_checked_function = true;
258    let find_func = arena.add_type(find_func_ty);
259    attach_magic_function(find_func, make_find_magic());
260
261    // string.byte : string -> number? -> number? -> ...number
262    let mut string_dot_byte = FunctionType::function_type_new(
263        arena.add_type_pack_initializer_list_type_id(&[
264            string_type,
265            optional_number,
266            optional_number,
267        ]),
268        number_variadic_list,
269        None,
270        false,
271    );
272    string_dot_byte.is_checked_function = true;
273
274    // string.char : .... number -> string
275    let mut string_dot_char = FunctionType::function_type_new(
276        number_variadic_list,
277        arena.add_type_pack_initializer_list_type_id(&[string_type]),
278        None,
279        false,
280    );
281    string_dot_char.is_checked_function = true;
282
283    // string.unpack : string -> string -> number? -> ...any
284    let mut string_dot_unpack = FunctionType::function_type_new(
285        arena.add_type_pack_t(TypePack {
286            head: vec![string_type, string_type, optional_number],
287            tail: None,
288        }),
289        variadic_tail_pack,
290        None,
291        false,
292    );
293    string_dot_unpack.is_checked_function = true;
294
295    let byte_fn = arena.add_type(string_dot_byte);
296    let char_fn = arena.add_type(string_dot_char);
297    let len_fn = make_function_poly(
298        arena,
299        Some(string_type),
300        Vec::new(),
301        Vec::new(),
302        Vec::new(),
303        vec![number_type],
304        /* checked */ true,
305    );
306    let rep_fn = make_function_poly(
307        arena,
308        Some(string_type),
309        Vec::new(),
310        Vec::new(),
311        vec![number_type],
312        vec![string_type],
313        /* checked */ true,
314    );
315    let sub_fn = make_function_poly(
316        arena,
317        Some(string_type),
318        Vec::new(),
319        Vec::new(),
320        vec![number_type, optional_number],
321        vec![string_type],
322        /* checked */ true,
323    );
324    let split_ret_table = arena.add_type(TableType {
325        props: Props::default(),
326        indexer: Some(TableIndexer {
327            index_type: number_type,
328            index_result_type: string_type,
329            is_read_only: false,
330        }),
331        state: TableState::Sealed,
332        level: TypeLevel::default(),
333        scope: core::ptr::null_mut(),
334        name: None,
335        synthetic_name: None,
336        instantiated_type_params: Vec::new(),
337        instantiated_type_pack_params: Vec::new(),
338        definition_module_name: Default::default(),
339        definition_location: Default::default(),
340        bound_to: None,
341        tags: Default::default(),
342        remaining_props: 0,
343    });
344    let split_fn = make_function_poly(
345        arena,
346        Some(string_type),
347        Vec::new(),
348        Vec::new(),
349        vec![optional_string],
350        vec![split_ret_table],
351        /* checked */ true,
352    );
353    let pack_arg_pack = arena.add_type_pack_t(TypePack {
354        head: vec![string_type],
355        tail: Some(variadic_tail_pack),
356    });
357    let pack_fn = arena.add_type(FunctionType::function_type_new(
358        pack_arg_pack,
359        one_string_pack,
360        None,
361        false,
362    ));
363    let packsize_fn = make_function_poly(
364        arena,
365        Some(string_type),
366        Vec::new(),
367        Vec::new(),
368        Vec::new(),
369        vec![number_type],
370        /* checked */ true,
371    );
372    let unpack_fn = arena.add_type(string_dot_unpack);
373
374    let mut string_lib = Props::default();
375    string_lib.insert("byte".to_string(), read_prop(byte_fn));
376    string_lib.insert("char".to_string(), read_prop(char_fn));
377    string_lib.insert("find".to_string(), read_prop(find_func));
378    string_lib.insert("format".to_string(), read_prop(format_fn)); // FIXME
379    string_lib.insert("gmatch".to_string(), read_prop(gmatch_func));
380    string_lib.insert("gsub".to_string(), read_prop(gsub_func));
381    string_lib.insert("len".to_string(), read_prop(len_fn));
382    string_lib.insert("lower".to_string(), read_prop(string_to_string_type));
383    string_lib.insert("match".to_string(), read_prop(match_func));
384    string_lib.insert("rep".to_string(), read_prop(rep_fn));
385    string_lib.insert("reverse".to_string(), read_prop(string_to_string_type));
386    string_lib.insert("sub".to_string(), read_prop(sub_fn));
387    string_lib.insert("upper".to_string(), read_prop(string_to_string_type));
388    string_lib.insert("split".to_string(), read_prop(split_fn));
389    string_lib.insert("pack".to_string(), read_prop(pack_fn));
390    string_lib.insert("packsize".to_string(), read_prop(packsize_fn));
391    string_lib.insert("unpack".to_string(), read_prop(unpack_fn));
392
393    assign_prop_documentation_symbols(&mut string_lib, "@luau/global/string");
394
395    let table_type = arena.add_type(TableType {
396        props: string_lib,
397        indexer: None,
398        state: TableState::Sealed,
399        level: TypeLevel::default(),
400        scope: core::ptr::null_mut(),
401        name: None,
402        synthetic_name: None,
403        instantiated_type_params: Vec::new(),
404        instantiated_type_pack_params: Vec::new(),
405        definition_module_name: Default::default(),
406        definition_location: Default::default(),
407        bound_to: None,
408        tags: Default::default(),
409        remaining_props: 0,
410    });
411
412    let ttv = unsafe { get_mutable_type_id::<TableType>(table_type) };
413    if !ttv.is_null() {
414        unsafe {
415            (*ttv).name = Some("typeof(string)".to_string());
416        }
417    }
418
419    let mut index_props = Props::default();
420    index_props.insert("__index".to_string(), Property::rw_type_id(table_type));
421    arena.add_type(TableType {
422        props: index_props,
423        indexer: None,
424        state: TableState::Sealed,
425        level: TypeLevel::default(),
426        scope: core::ptr::null_mut(),
427        name: None,
428        synthetic_name: None,
429        instantiated_type_params: Vec::new(),
430        instantiated_type_pack_params: Vec::new(),
431        definition_module_name: Default::default(),
432        definition_location: Default::default(),
433        bound_to: None,
434        tags: Default::default(),
435        remaining_props: 0,
436    })
437}