Skip to main content

gruel_air/sema/
lang_items.rs

1//! Lang-item registry (ADR-0079).
2//!
3//! Stores the resolved interface/enum IDs for the compiler-recognized
4//! lang items (`drop`, `clone`, `handle`, `op_eq`, `op_cmp`,
5//! `ordering`). Populated from `@lang("…")` directives on prelude
6//! declarations during `resolve_declarations`. Every compiler-side
7//! behavior that historically matched on a hardcoded type name (the
8//! `Drop`/`Clone`/`Eq`/`Ord` strings) consults this registry instead.
9//!
10//! ADR-0080 retired the `copy` lang item: posture is declared on the
11//! type (`copy struct`/`copy enum`) and queried via `@ownership(T)`,
12//! never dispatched, so it no longer needs an interface binding.
13
14use gruel_builtins::{LangEnumItem, LangFnItem, LangInterfaceItem, LangItemKind};
15use gruel_rir::InstData;
16use gruel_util::{CompileError, CompileResult, ErrorKind, Span};
17use lasso::Spur;
18
19use super::Sema;
20use super::file_paths::is_prelude_path;
21use crate::types::{EnumId, InterfaceId};
22
23/// Lang items resolved against the prelude. Each option is `Some` once
24/// the prelude tags the corresponding declaration with `@lang("…")`.
25/// Missing entries surface lazily — every call site that needs a lang
26/// item logs through `Sema::lang_items()` and tolerates an absent entry
27/// in the same way it tolerated a missing interface name pre-ADR-0079.
28#[derive(Debug, Default, Clone)]
29pub struct LangItems {
30    pub(crate) drop: Option<InterfaceId>,
31    pub(crate) clone: Option<InterfaceId>,
32    pub(crate) handle: Option<InterfaceId>,
33    pub(crate) op_eq: Option<InterfaceId>,
34    pub(crate) op_cmp: Option<InterfaceId>,
35    pub(crate) ordering: Option<EnumId>,
36    /// `@lang("vec")` — the prelude `Vec(comptime T: type) -> type`
37    /// function whose instantiations are recognized as the canonical
38    /// owned-buffer vector (ADR-0082).
39    pub(crate) vec_fn: Option<Spur>,
40}
41
42impl LangItems {
43    pub fn drop(&self) -> Option<InterfaceId> {
44        self.drop
45    }
46    pub fn clone(&self) -> Option<InterfaceId> {
47        self.clone
48    }
49    pub fn handle(&self) -> Option<InterfaceId> {
50        self.handle
51    }
52    pub fn op_eq(&self) -> Option<InterfaceId> {
53        self.op_eq
54    }
55    pub fn op_cmp(&self) -> Option<InterfaceId> {
56        self.op_cmp
57    }
58    pub fn ordering(&self) -> Option<EnumId> {
59        self.ordering
60    }
61    pub fn vec_fn(&self) -> Option<Spur> {
62        self.vec_fn
63    }
64}
65
66impl<'a> Sema<'a> {
67    /// Public read access to the lang-item registry.
68    pub fn lang_items(&self) -> &LangItems {
69        &self.lang_items
70    }
71
72    /// ADR-0082: reverse lookup — given an element type, return the
73    /// `StructId` of the `@lang("vec")` instance for that element.
74    /// Returns `None` if `populate_vec_instance` has not yet been
75    /// called for `elem_ty` (or the lang-item Vec is unbound).
76    pub(crate) fn vec_instance_for_elem(
77        &self,
78        elem_ty: crate::types::Type,
79    ) -> Option<crate::types::StructId> {
80        self.vec_instance_registry
81            .iter()
82            .find_map(|(sid, t)| if *t == elem_ty { Some(*sid) } else { None })
83    }
84
85    /// ADR-0082: ensure the prelude `@lang("vec")` function's
86    /// instantiation for `elem_ty` has been evaluated and its
87    /// `StructId` registered. Idempotent — early-returns if the
88    /// element type is already represented in the registry. Errors
89    /// are swallowed: the legacy `TypeKind::Vec(_)` path stays valid,
90    /// so a failed prelude evaluation only means the registry stays
91    /// empty for that element type. Phase 3's bridge consumers fall
92    /// back to the legacy path when the registry has no entry.
93    ///
94    /// Uses the rich comptime interpreter (`evaluate_type_ctor_body`)
95    /// because the prelude `Vec` body is allowed to wrap its struct
96    /// literal in a `comptime if` (ADR-0084 follow-up: thread-safety
97    /// markers per element-type classification). The simpler
98    /// `try_evaluate_const_with_subst` path doesn't handle
99    /// `InstData::Branch`, so it can't see through the conditional to
100    /// the struct underneath.
101    pub(crate) fn populate_vec_instance(&mut self, elem_ty: crate::types::Type) {
102        // Skip if any existing entry already maps to this element.
103        if self.vec_instance_registry.values().any(|t| *t == elem_ty) {
104            return;
105        }
106        let Some(vec_fn_sym) = self.lang_items.vec_fn() else {
107            return;
108        };
109        let Some(fn_info) = self.functions.get(&vec_fn_sym).copied() else {
110            return;
111        };
112        let param_names = self.param_arena.names(fn_info.params).to_vec();
113        if param_names.is_empty() {
114            return;
115        }
116        let mut type_subst: rustc_hash::FxHashMap<lasso::Spur, crate::types::Type> =
117            rustc_hash::FxHashMap::default();
118        type_subst.insert(param_names[0], elem_ty);
119        let value_subst: rustc_hash::FxHashMap<lasso::Spur, super::ConstValue> =
120            rustc_hash::FxHashMap::default();
121
122        // Build a stub `AnalysisContext` so we can drive the rich
123        // evaluator without an enclosing analysis. Mirrors the stub
124        // built by `evaluate_comptime_top_level` for Phase 2.5
125        // const-initializer evaluation.
126        let empty_params: Vec<crate::sema::context::ParamInfo> = Vec::new();
127        let empty_resolved: rustc_hash::FxHashMap<gruel_rir::InstRef, crate::types::Type> =
128            rustc_hash::FxHashMap::default();
129        let stub = crate::sema::context::AnalysisContext {
130            locals: rustc_hash::FxHashMap::default(),
131            params: &empty_params,
132            next_slot: 0,
133            loop_depth: 0,
134            forbid_break: None,
135            checked_depth: 0,
136            used_locals: rustc_hash::FxHashSet::default(),
137            return_type: crate::types::Type::UNIT,
138            scope_stack: Vec::new(),
139            resolved_types: &empty_resolved,
140            moved_vars: rustc_hash::FxHashMap::default(),
141            warnings: Vec::new(),
142            local_string_table: rustc_hash::FxHashMap::default(),
143            local_strings: Vec::new(),
144            local_bytes: Vec::new(),
145            comptime_type_vars: rustc_hash::FxHashMap::default(),
146            comptime_value_vars: rustc_hash::FxHashMap::default(),
147            referenced_functions: rustc_hash::FxHashSet::default(),
148            referenced_methods: rustc_hash::FxHashSet::default(),
149            borrow_arg_skip_move: None,
150            uninit_handles: rustc_hash::FxHashMap::default(),
151            unroll_arm_bindings: rustc_hash::FxHashMap::default(),
152        };
153
154        // Evaluation may push diagnostics into `pending_anon_eval_errors`
155        // when the body has a sema-level problem (e.g. an empty struct
156        // body). Snapshot the buffer length and rewind on failure so we
157        // don't surface the prelude's evaluation errors to user code —
158        // the legacy `TypeKind::Vec(_)` path is the safety net.
159        let pending_before = self.pending_anon_eval_errors.len();
160        let saved_ctor = self.comptime_ctor_fn.replace(vec_fn_sym);
161        // Use the function's declaration span as a fallback diagnostic
162        // anchor; the rich evaluator only consults this on unrecoverable
163        // failures, which we discard.
164        let span = self
165            .functions
166            .get(&vec_fn_sym)
167            .map(|f| self.rir.get(f.body).span)
168            .unwrap_or(gruel_util::Span::new(0, 0));
169        let _ = self.evaluate_type_ctor_body(fn_info.body, &type_subst, &value_subst, &stub, span);
170        self.comptime_ctor_fn = saved_ctor;
171        self.pending_anon_eval_errors.truncate(pending_before);
172    }
173
174    /// ADR-0079: walk every interface and enum declaration in the RIR,
175    /// resolve `@lang("…")` directives against the closed lang-item set,
176    /// and record the binding on `self.lang_items`.
177    ///
178    /// Errors:
179    /// - `@lang(...)` outside a prelude file (`InvalidLangItem`).
180    /// - Wrong arg shape (zero, multiple, or non-string) at the
181    ///   directive site.
182    /// - Unknown lang-item name.
183    /// - Lang-item kind mismatched with declaration kind (e.g.
184    ///   `@lang("ordering")` on an interface).
185    /// - Two declarations both claim the same lang item.
186    pub(crate) fn populate_lang_items(&mut self) -> CompileResult<()> {
187        // Collect raw bindings first; we look up interface/enum IDs from
188        // already-populated maps and only mutate `self.lang_items` after
189        // all directives validate.
190        struct Pending {
191            kind: PendingKind,
192            site: Span,
193        }
194        enum PendingKind {
195            Interface(LangInterfaceItem, InterfaceId),
196            Enum(LangEnumItem, EnumId),
197            Fn(LangFnItem, Spur),
198        }
199        let mut pending: Vec<Pending> = Vec::new();
200
201        for (_, inst) in self.rir.iter() {
202            let (name, directives_start, directives_len, kind) = match &inst.data {
203                InstData::InterfaceDecl {
204                    name,
205                    directives_start,
206                    directives_len,
207                    ..
208                } => (
209                    *name,
210                    *directives_start,
211                    *directives_len,
212                    DeclKind::Interface,
213                ),
214                InstData::EnumDecl {
215                    name,
216                    directives_start,
217                    directives_len,
218                    ..
219                } => (*name, *directives_start, *directives_len, DeclKind::Enum),
220                InstData::FnDecl {
221                    name,
222                    directives_start,
223                    directives_len,
224                    ..
225                } => (*name, *directives_start, *directives_len, DeclKind::Fn),
226                _ => continue,
227            };
228            if directives_len == 0 {
229                continue;
230            }
231            // Use the host inst's span for privilege/diagnostic
232            // reporting. RIR directive storage drops the directive's
233            // file_id (its `span` carries `(start, len)` only) so the
234            // host span is the reliable file-of-origin signal.
235            let host_span = inst.span;
236            let directives = self.rir.get_directives(directives_start, directives_len);
237            for d in &directives {
238                if self.interner.resolve(&d.name) != "lang" {
239                    continue;
240                }
241                // Privilege gate: `@lang(...)` is only valid inside the
242                // prelude.
243                if !self.is_directive_in_prelude(host_span) {
244                    return Err(CompileError::new(
245                        ErrorKind::InvalidLangItem {
246                            reason: "`@lang(...)` is only valid in the prelude (under `prelude/`)"
247                                .to_string(),
248                        },
249                        host_span,
250                    ));
251                }
252                if d.args.len() != 1 {
253                    return Err(CompileError::new(
254                        ErrorKind::InvalidLangItem {
255                            reason: format!(
256                                "`@lang(...)` expects one string argument, got {}",
257                                d.args.len()
258                            ),
259                        },
260                        host_span,
261                    ));
262                }
263                let lang_name = self.interner.resolve(&d.args[0]).to_string();
264                let lang_kind = match LangItemKind::from_str(&lang_name) {
265                    Some(k) => k,
266                    None => {
267                        let known = gruel_builtins::all_lang_item_names().join(", ");
268                        return Err(CompileError::new(
269                            ErrorKind::InvalidLangItem {
270                                reason: format!(
271                                    "unknown lang item `{}`; known: {}",
272                                    lang_name, known
273                                ),
274                            },
275                            host_span,
276                        ));
277                    }
278                };
279                match (kind, lang_kind) {
280                    (DeclKind::Interface, LangItemKind::Interface(item)) => {
281                        let id = match self.interfaces.get(&name) {
282                            Some(&id) => id,
283                            None => continue,
284                        };
285                        pending.push(Pending {
286                            kind: PendingKind::Interface(item, id),
287                            site: host_span,
288                        });
289                    }
290                    (DeclKind::Enum, LangItemKind::Enum(item)) => {
291                        let id = match self.enums.get(&name) {
292                            Some(&id) => id,
293                            None => continue,
294                        };
295                        pending.push(Pending {
296                            kind: PendingKind::Enum(item, id),
297                            site: host_span,
298                        });
299                    }
300                    (DeclKind::Fn, LangItemKind::Fn(item)) => {
301                        // ADR-0082: bind the function's name `Spur` so
302                        // `Sema::lang_items().vec_fn()` returns it. We
303                        // bind unconditionally — `self.functions` isn't
304                        // populated until `resolve_remaining_declarations`,
305                        // which runs *after* `populate_lang_items`.
306                        // Function-existence checks happen lazily at use
307                        // sites (e.g. `populate_vec_instance`'s
308                        // `self.functions.get(&vec_fn_sym)` lookup), by
309                        // which time the map is populated.
310                        pending.push(Pending {
311                            kind: PendingKind::Fn(item, name),
312                            site: host_span,
313                        });
314                    }
315                    (DeclKind::Interface, LangItemKind::Enum(_))
316                    | (DeclKind::Interface, LangItemKind::Fn(_)) => {
317                        return Err(CompileError::new(
318                            ErrorKind::InvalidLangItem {
319                                reason: format!(
320                                    "lang item `{}` cannot appear on an interface declaration",
321                                    lang_name
322                                ),
323                            },
324                            host_span,
325                        ));
326                    }
327                    (DeclKind::Enum, LangItemKind::Interface(_))
328                    | (DeclKind::Enum, LangItemKind::Fn(_)) => {
329                        return Err(CompileError::new(
330                            ErrorKind::InvalidLangItem {
331                                reason: format!(
332                                    "lang item `{}` cannot appear on an enum declaration",
333                                    lang_name
334                                ),
335                            },
336                            host_span,
337                        ));
338                    }
339                    (DeclKind::Fn, LangItemKind::Interface(_))
340                    | (DeclKind::Fn, LangItemKind::Enum(_)) => {
341                        return Err(CompileError::new(
342                            ErrorKind::InvalidLangItem {
343                                reason: format!(
344                                    "lang item `{}` cannot appear on a function declaration",
345                                    lang_name
346                                ),
347                            },
348                            host_span,
349                        ));
350                    }
351                }
352            }
353        }
354
355        for p in pending {
356            match p.kind {
357                PendingKind::Interface(item, id) => {
358                    let slot: &mut Option<InterfaceId> = match item {
359                        LangInterfaceItem::Drop => &mut self.lang_items.drop,
360                        LangInterfaceItem::Clone => &mut self.lang_items.clone,
361                        LangInterfaceItem::Handle => &mut self.lang_items.handle,
362                        LangInterfaceItem::OpEq => &mut self.lang_items.op_eq,
363                        LangInterfaceItem::OpCmp => &mut self.lang_items.op_cmp,
364                    };
365                    if let Some(_existing) = *slot {
366                        return Err(CompileError::new(
367                            ErrorKind::InvalidLangItem {
368                                reason: format!("lang item `{}` is bound twice", item.name()),
369                            },
370                            p.site,
371                        ));
372                    }
373                    *slot = Some(id);
374                }
375                PendingKind::Enum(item, id) => {
376                    let slot: &mut Option<EnumId> = match item {
377                        LangEnumItem::Ordering => &mut self.lang_items.ordering,
378                    };
379                    if let Some(_existing) = *slot {
380                        return Err(CompileError::new(
381                            ErrorKind::InvalidLangItem {
382                                reason: format!("lang item `{}` is bound twice", item.name()),
383                            },
384                            p.site,
385                        ));
386                    }
387                    *slot = Some(id);
388                }
389                PendingKind::Fn(item, sym) => {
390                    let slot: &mut Option<Spur> = match item {
391                        LangFnItem::Vec => &mut self.lang_items.vec_fn,
392                    };
393                    if let Some(_existing) = *slot {
394                        return Err(CompileError::new(
395                            ErrorKind::InvalidLangItem {
396                                reason: format!("lang item `{}` is bound twice", item.name()),
397                            },
398                            p.site,
399                        ));
400                    }
401                    *slot = Some(sym);
402                }
403            }
404        }
405        Ok(())
406    }
407
408    /// Return true iff the directive's source file is part of the
409    /// prelude (top-level `prelude/` directory). Recognizes:
410    ///
411    /// - `FileId::PRELUDE` itself (the prelude root).
412    /// - File IDs in the high reserved band (0xFFFF_F000 ..=
413    ///   FileId::PRELUDE) that the prelude loader assigns to submodules
414    ///   counting down from PRELUDE. Test fixtures inline submodules
415    ///   without registering paths, but the file IDs they use sit in
416    ///   this band.
417    /// - Any registered path that satisfies `is_prelude_path`.
418    fn is_directive_in_prelude(&self, span: Span) -> bool {
419        let id = span.file_id.index();
420        if id >= 0xFFFF_F000 {
421            return true;
422        }
423        match self.file_paths.get(&span.file_id) {
424            Some(path) => is_prelude_path(path),
425            // Without a registered path, default to denying — user
426            // files always have a path; absence implies a virtual or
427            // ungated source we don't trust to use `@lang(...)`.
428            None => false,
429        }
430    }
431}
432
433#[derive(Clone, Copy)]
434enum DeclKind {
435    Interface,
436    Enum,
437    Fn,
438}