Skip to main content

gdscript_api/
lookup.rs

1//! The lookup API over [`EngineApi`] (Playbook §4.3): name → id, member resolution with base
2//! walks, subclass tests, and the inherited member set for `recv.<TAB>` completion.
3//!
4//! Everything returns borrowed [`MemberRef`]s into the model — no cloning.
5
6use crate::EngineApi;
7use crate::gdscript_layer::{BuiltinFn, GlobalConst};
8use crate::model::{
9    BuiltinData, BuiltinId, ClassData, ClassId, ConstInfo, EnumInfo, MethodSig, OperatorSig,
10    PropertyInfo, SignalSig, UtilityFn,
11};
12use rustc_hash::FxHashSet;
13
14/// A borrowed reference to a resolved class member (Playbook §4.3 — no clone).
15#[derive(Debug, Clone, Copy)]
16pub enum MemberRef<'a> {
17    /// A method.
18    Method(&'a MethodSig),
19    /// A property.
20    Property(&'a PropertyInfo),
21    /// A signal.
22    Signal(&'a SignalSig),
23    /// A constant.
24    Const(&'a ConstInfo),
25    /// A nested enum.
26    Enum(&'a EnumInfo),
27}
28
29impl MemberRef<'_> {
30    /// The member's name.
31    #[must_use]
32    pub fn name(&self) -> &str {
33        match self {
34            Self::Method(m) => &m.name,
35            Self::Property(p) => &p.name,
36            Self::Signal(s) => &s.name,
37            Self::Const(c) => &c.name,
38            Self::Enum(e) => &e.name,
39        }
40    }
41}
42
43impl EngineApi {
44    // ---- tables ----
45
46    /// All engine classes (`extension_api.json` order).
47    #[must_use]
48    pub fn classes(&self) -> &[ClassData] {
49        &self.data.classes
50    }
51
52    /// All builtin Variant types.
53    #[must_use]
54    pub fn builtins(&self) -> &[BuiltinData] {
55        &self.data.builtins
56    }
57
58    /// The class with the given id.
59    #[must_use]
60    pub fn class(&self, id: ClassId) -> &ClassData {
61        &self.data.classes[id.0 as usize]
62    }
63
64    /// The builtin type with the given id.
65    #[must_use]
66    pub fn builtin(&self, id: BuiltinId) -> &BuiltinData {
67        &self.data.builtins[id.0 as usize]
68    }
69
70    // ---- name → id ----
71
72    /// Resolve an engine class name to its id.
73    #[must_use]
74    pub fn class_by_name(&self, name: &str) -> Option<ClassId> {
75        self.class_by_name.get(name).copied()
76    }
77
78    /// Resolve a builtin type name to its id.
79    #[must_use]
80    pub fn builtin_by_name(&self, name: &str) -> Option<BuiltinId> {
81        self.builtin_by_name.get(name).copied()
82    }
83
84    /// The cached id of the `int` builtin.
85    #[must_use]
86    pub fn int_builtin(&self) -> Option<BuiltinId> {
87        self.int_builtin
88    }
89
90    /// Resolve a singleton symbol (`Input`, `OS`, …) to the class it is an instance of.
91    #[must_use]
92    pub fn singleton(&self, name: &str) -> Option<ClassId> {
93        self.singleton_by_name.get(name).copied()
94    }
95
96    /// Look up a `@GlobalScope` utility function (`sin`, `print`, …).
97    #[must_use]
98    pub fn utility(&self, name: &str) -> Option<&UtilityFn> {
99        let i = *self.utility_by_name.get(name)?;
100        self.data.utilities.get(i as usize)
101    }
102
103    /// Look up a global (`@GlobalScope`) enum (`Error`, `Key`, …).
104    #[must_use]
105    pub fn global_enum(&self, name: &str) -> Option<&EnumInfo> {
106        let i = *self.global_enum_by_name.get(name)?;
107        self.data.global_enums.get(i as usize)
108    }
109
110    /// Look up a hand-authored pseudo-constant (`PI`/`TAU`/`INF`/`NAN`).
111    #[must_use]
112    pub fn global_const(&self, name: &str) -> Option<&GlobalConst> {
113        self.global_consts.iter().find(|c| c.name == name)
114    }
115
116    /// Look up a hand-authored GDScript builtin function (`preload`/`range`/`len`/…).
117    #[must_use]
118    pub fn gdscript_builtin(&self, name: &str) -> Option<&BuiltinFn> {
119        self.gdscript_builtins.iter().find(|f| f.name == name)
120    }
121
122    // ---- member resolution ----
123
124    /// Resolve `name` on `class`, walking the base chain; the nearest declarer wins.
125    #[must_use]
126    pub fn lookup_member(&self, class: ClassId, name: &str) -> Option<MemberRef<'_>> {
127        let mut cur = Some(class);
128        while let Some(cid) = cur {
129            let c = self.class(cid);
130            if let Some(m) = c.methods.iter().find(|m| m.name == name) {
131                return Some(MemberRef::Method(m));
132            }
133            if let Some(p) = c.properties.iter().find(|p| p.name == name) {
134                return Some(MemberRef::Property(p));
135            }
136            if let Some(s) = c.signals.iter().find(|s| s.name == name) {
137                return Some(MemberRef::Signal(s));
138            }
139            if let Some(k) = c.constants.iter().find(|k| k.name == name) {
140                return Some(MemberRef::Const(k));
141            }
142            if let Some(e) = c.enums.iter().find(|e| e.name == name) {
143                return Some(MemberRef::Enum(e));
144            }
145            cur = c.base;
146        }
147        None
148    }
149
150    /// Whether `sub` is `sup` or transitively inherits it.
151    #[must_use]
152    pub fn is_subclass(&self, sub: ClassId, sup: ClassId) -> bool {
153        let mut cur = Some(sub);
154        while let Some(cid) = cur {
155            if cid == sup {
156                return true;
157            }
158            cur = self.class(cid).base;
159        }
160        false
161    }
162
163    /// Every member visible on `class` including inherited ones, deduped with the nearest
164    /// declarer winning — the candidate set for `recv.<TAB>` completion (Playbook §4.3).
165    #[must_use]
166    pub fn members_of(&self, class: ClassId) -> Vec<MemberRef<'_>> {
167        let mut seen: FxHashSet<&str> = FxHashSet::default();
168        let mut out = Vec::new();
169        let mut cur = Some(class);
170        while let Some(cid) = cur {
171            let c = self.class(cid);
172            for m in &c.methods {
173                if seen.insert(&m.name) {
174                    out.push(MemberRef::Method(m));
175                }
176            }
177            for p in &c.properties {
178                if seen.insert(&p.name) {
179                    out.push(MemberRef::Property(p));
180                }
181            }
182            for s in &c.signals {
183                if seen.insert(&s.name) {
184                    out.push(MemberRef::Signal(s));
185                }
186            }
187            for k in &c.constants {
188                if seen.insert(&k.name) {
189                    out.push(MemberRef::Const(k));
190                }
191            }
192            for e in &c.enums {
193                if seen.insert(&e.name) {
194                    out.push(MemberRef::Enum(e));
195                }
196            }
197            cur = c.base;
198        }
199        out
200    }
201
202    // ---- builtins ----
203
204    /// A field of a builtin type (`Vector2.x`).
205    #[must_use]
206    pub fn builtin_member(&self, builtin: BuiltinId, name: &str) -> Option<&crate::BuiltinMember> {
207        self.builtin(builtin)
208            .members
209            .iter()
210            .find(|m| m.name == name)
211    }
212
213    /// A method of a builtin type (`Array.size`).
214    #[must_use]
215    pub fn builtin_method(&self, builtin: BuiltinId, name: &str) -> Option<&MethodSig> {
216        self.builtin(builtin)
217            .methods
218            .iter()
219            .find(|m| m.name == name)
220    }
221
222    /// The operator overloads of a builtin type (the caller matches `op` + RHS).
223    #[must_use]
224    pub fn builtin_operators(&self, builtin: BuiltinId) -> &[OperatorSig] {
225        &self.builtin(builtin).operators
226    }
227}