gruel_cfg/
drop_names.rs

1//! Shared drop-glue utilities: reachability checks and synthesized symbol names.
2//!
3//! Both the drop-glue synthesizer (`gruel-compiler`) and the LLVM codegen
4//! (`gruel-codegen-llvm`) need to agree on:
5//! - whether a type requires a destructor call, and
6//! - the exact name of the synthesized `__gruel_drop_*` symbol for that type.
7//!
8//! Putting these here avoids duplicating the logic and keeps the two sites in
9//! sync automatically — if the names ever need to change, there is one place to
10//! edit.
11
12use gruel_air::{Type, TypeInternPool, TypeKind};
13
14/// Return `true` if `ty` requires a drop call when it goes out of scope.
15///
16/// Primitive scalars, unit-only enums, pointers, and unit/never are trivially droppable.
17/// A struct needs drop if it has a destructor or if any field needs drop.
18/// An array needs drop if its element type needs drop.
19/// A data enum needs drop if any variant has a field that needs drop.
20pub fn type_needs_drop(ty: Type, type_pool: &TypeInternPool) -> bool {
21    match ty.kind() {
22        TypeKind::Struct(id) => {
23            let def = type_pool.struct_def(id);
24            if def.destructor.is_some() {
25                return true;
26            }
27            def.fields.iter().any(|f| type_needs_drop(f.ty, type_pool))
28        }
29        TypeKind::Array(id) => {
30            let (elem, _) = type_pool.array_def(id);
31            type_needs_drop(elem, type_pool)
32        }
33        TypeKind::Enum(id) => {
34            let def = type_pool.enum_def(id);
35            def.variants
36                .iter()
37                .any(|v| v.fields.iter().any(|f| type_needs_drop(*f, type_pool)))
38        }
39        _ => false,
40    }
41}
42
43/// Return the name of the synthesized `__gruel_drop_*` function for `ty`,
44/// or `None` if the type is trivially droppable or is a built-in with a
45/// runtime-provided destructor (e.g. `String` → `__gruel_drop_String` lives
46/// in `gruel-runtime` and is called via a dedicated code path, not through
47/// a synthesized wrapper).
48pub fn drop_fn_name(ty: Type, type_pool: &TypeInternPool) -> Option<String> {
49    match ty.kind() {
50        TypeKind::Struct(id) => {
51            let def = type_pool.struct_def(id);
52            // Built-in types with runtime destructors are handled by the
53            // is_builtin_string / runtime-call path in codegen, not by a
54            // synthesized wrapper.
55            if def.is_builtin && def.destructor.is_some() {
56                return None;
57            }
58            if type_needs_drop(ty, type_pool) {
59                Some(format!("__gruel_drop_{}", def.name))
60            } else {
61                None
62            }
63        }
64        TypeKind::Array(id) => {
65            if type_needs_drop(ty, type_pool) {
66                let (elem, len) = type_pool.array_def(id);
67                Some(format!(
68                    "__gruel_drop_array_{}_{}",
69                    type_name_component(elem, type_pool),
70                    len
71                ))
72            } else {
73                None
74            }
75        }
76        TypeKind::Enum(id) => {
77            if type_needs_drop(ty, type_pool) {
78                let def = type_pool.enum_def(id);
79                Some(format!("__gruel_drop_{}", def.name))
80            } else {
81                None
82            }
83        }
84        _ => None,
85    }
86}
87
88/// A stable, human-readable name component for `ty`, used inside drop-glue
89/// symbol names (e.g. the `String_3` in `__gruel_drop_array_String_3`).
90pub fn type_name_component(ty: Type, type_pool: &TypeInternPool) -> String {
91    match ty.kind() {
92        TypeKind::I8 => "i8".to_owned(),
93        TypeKind::I16 => "i16".to_owned(),
94        TypeKind::I32 => "i32".to_owned(),
95        TypeKind::I64 => "i64".to_owned(),
96        TypeKind::U8 => "u8".to_owned(),
97        TypeKind::U16 => "u16".to_owned(),
98        TypeKind::U32 => "u32".to_owned(),
99        TypeKind::U64 => "u64".to_owned(),
100        TypeKind::Isize => "isize".to_owned(),
101        TypeKind::Usize => "usize".to_owned(),
102        TypeKind::F16 => "f16".to_owned(),
103        TypeKind::F32 => "f32".to_owned(),
104        TypeKind::F64 => "f64".to_owned(),
105        TypeKind::Bool => "bool".to_owned(),
106        TypeKind::Unit => "unit".to_owned(),
107        TypeKind::Never => "never".to_owned(),
108        TypeKind::Error => "error".to_owned(),
109        TypeKind::ComptimeType => "comptime_type".to_owned(),
110        TypeKind::ComptimeStr => "comptime_str".to_owned(),
111        TypeKind::ComptimeInt => "comptime_int".to_owned(),
112        TypeKind::Enum(id) => format!("enum{}", id.0),
113        TypeKind::Struct(id) => type_pool.struct_def(id).name.clone(),
114        TypeKind::Array(id) => {
115            let (elem, len) = type_pool.array_def(id);
116            format!("array_{}_{}", type_name_component(elem, type_pool), len)
117        }
118        TypeKind::Module(id) => format!("module{}", id.0),
119        TypeKind::PtrConst(id) => {
120            let pointee = type_pool.ptr_const_def(id);
121            format!("ptr_const_{}", type_name_component(pointee, type_pool))
122        }
123        TypeKind::PtrMut(id) => {
124            let pointee = type_pool.ptr_mut_def(id);
125            format!("ptr_mut_{}", type_name_component(pointee, type_pool))
126        }
127    }
128}