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    /// A VALUE of a nested enum, addressable bare on the class (`Control.SIZE_EXPAND_FILL` — and
28    /// bare `SIZE_EXPAND_FILL` inside a class extending it). Carries the declaring class + enum
29    /// so consumers can type it as its **enum** (`Class.EnumName`), not bare `int` — an enum
30    /// member into its own enum slot must stay `Assign::Ok` (no false `INT_AS_ENUM_WITHOUT_CAST`).
31    EnumValue {
32        /// The declaring class's name.
33        class: &'a str,
34        /// The declaring enum.
35        decl: &'a EnumInfo,
36        /// The value itself.
37        value: &'a crate::model::EnumValue,
38    },
39}
40
41impl MemberRef<'_> {
42    /// The member's name.
43    #[must_use]
44    pub fn name(&self) -> &str {
45        match self {
46            Self::Method(m) => &m.name,
47            Self::Property(p) => &p.name,
48            Self::Signal(s) => &s.name,
49            Self::Const(c) => &c.name,
50            Self::Enum(e) => &e.name,
51            Self::EnumValue { value, .. } => &value.name,
52        }
53    }
54}
55
56impl EngineApi {
57    // ---- tables ----
58
59    /// All engine classes (`extension_api.json` order).
60    #[must_use]
61    pub fn classes(&self) -> &[ClassData] {
62        &self.data.classes
63    }
64
65    /// All builtin Variant types.
66    #[must_use]
67    pub fn builtins(&self) -> &[BuiltinData] {
68        &self.data.builtins
69    }
70
71    /// The class with the given id.
72    #[must_use]
73    pub fn class(&self, id: ClassId) -> &ClassData {
74        &self.data.classes[id.0 as usize]
75    }
76
77    /// The builtin type with the given id.
78    #[must_use]
79    pub fn builtin(&self, id: BuiltinId) -> &BuiltinData {
80        &self.data.builtins[id.0 as usize]
81    }
82
83    // ---- name → id ----
84
85    /// Resolve an engine class name to its id.
86    #[must_use]
87    pub fn class_by_name(&self, name: &str) -> Option<ClassId> {
88        self.class_by_name.get(name).copied()
89    }
90
91    /// Resolve a builtin type name to its id.
92    #[must_use]
93    pub fn builtin_by_name(&self, name: &str) -> Option<BuiltinId> {
94        self.builtin_by_name.get(name).copied()
95    }
96
97    /// The cached id of the `int` builtin.
98    #[must_use]
99    pub fn int_builtin(&self) -> Option<BuiltinId> {
100        self.int_builtin
101    }
102
103    /// Resolve a singleton symbol (`Input`, `OS`, …) to the class it is an instance of.
104    #[must_use]
105    pub fn singleton(&self, name: &str) -> Option<ClassId> {
106        self.singleton_by_name.get(name).copied()
107    }
108
109    /// Look up a `@GlobalScope` utility function (`sin`, `print`, …).
110    #[must_use]
111    pub fn utility(&self, name: &str) -> Option<&UtilityFn> {
112        let i = *self.utility_by_name.get(name)?;
113        self.data.utilities.get(i as usize)
114    }
115
116    /// Look up a global (`@GlobalScope`) enum (`Error`, `Key`, …).
117    #[must_use]
118    pub fn global_enum(&self, name: &str) -> Option<&EnumInfo> {
119        let i = *self.global_enum_by_name.get(name)?;
120        self.data.global_enums.get(i as usize)
121    }
122
123    /// Look up a `@GlobalScope` enum VALUE by its bare name (`MOUSE_BUTTON_LEFT`, `OK`,
124    /// `TYPE_STRING`, …) — these are global identifiers in GDScript. Returns the declaring enum
125    /// and the value.
126    #[must_use]
127    pub fn global_enum_value(&self, name: &str) -> Option<(&EnumInfo, &crate::model::EnumValue)> {
128        let (ei, vi) = *self.global_enum_value_by_name.get(name)?;
129        let e = self.data.global_enums.get(ei as usize)?;
130        Some((e, e.values.get(vi as usize)?))
131    }
132
133    /// Look up a hand-authored pseudo-constant (`PI`/`TAU`/`INF`/`NAN`).
134    #[must_use]
135    pub fn global_const(&self, name: &str) -> Option<&GlobalConst> {
136        self.global_consts.iter().find(|c| c.name == name)
137    }
138
139    /// Look up a hand-authored GDScript builtin function (`preload`/`range`/`len`/…).
140    #[must_use]
141    pub fn gdscript_builtin(&self, name: &str) -> Option<&BuiltinFn> {
142        self.gdscript_builtins.iter().find(|f| f.name == name)
143    }
144
145    // ---- member resolution ----
146
147    /// Resolve `name` on `class`, walking the base chain; the nearest declarer wins.
148    #[must_use]
149    pub fn lookup_member(&self, class: ClassId, name: &str) -> Option<MemberRef<'_>> {
150        let mut cur = Some(class);
151        while let Some(cid) = cur {
152            let c = self.class(cid);
153            if let Some(m) = c.methods.iter().find(|m| m.name == name) {
154                return Some(MemberRef::Method(m));
155            }
156            if let Some(p) = c.properties.iter().find(|p| p.name == name) {
157                return Some(MemberRef::Property(p));
158            }
159            if let Some(s) = c.signals.iter().find(|s| s.name == name) {
160                return Some(MemberRef::Signal(s));
161            }
162            if let Some(k) = c.constants.iter().find(|k| k.name == name) {
163                return Some(MemberRef::Const(k));
164            }
165            if let Some(e) = c.enums.iter().find(|e| e.name == name) {
166                return Some(MemberRef::Enum(e));
167            }
168            // A class enum's VALUES are addressable bare on the class, like constants
169            // (`Control.SIZE_EXPAND_FILL`, and bare inside a subclass).
170            if let Some((e, v)) = c
171                .enums
172                .iter()
173                .find_map(|e| e.values.iter().find(|v| v.name == name).map(|v| (e, v)))
174            {
175                return Some(MemberRef::EnumValue {
176                    class: &c.name,
177                    decl: e,
178                    value: v,
179                });
180            }
181            cur = c.base;
182        }
183        None
184    }
185
186    /// Whether `sub` is `sup` or transitively inherits it.
187    #[must_use]
188    pub fn is_subclass(&self, sub: ClassId, sup: ClassId) -> bool {
189        let mut cur = Some(sub);
190        while let Some(cid) = cur {
191            if cid == sup {
192                return true;
193            }
194            cur = self.class(cid).base;
195        }
196        false
197    }
198
199    /// Every member visible on `class` including inherited ones, deduped with the nearest
200    /// declarer winning — the candidate set for `recv.<TAB>` completion (Playbook §4.3).
201    #[must_use]
202    pub fn members_of(&self, class: ClassId) -> Vec<MemberRef<'_>> {
203        let mut seen: FxHashSet<&str> = FxHashSet::default();
204        let mut out = Vec::new();
205        let mut cur = Some(class);
206        while let Some(cid) = cur {
207            let c = self.class(cid);
208            for m in &c.methods {
209                if seen.insert(&m.name) {
210                    out.push(MemberRef::Method(m));
211                }
212            }
213            for p in &c.properties {
214                if seen.insert(&p.name) {
215                    out.push(MemberRef::Property(p));
216                }
217            }
218            for s in &c.signals {
219                if seen.insert(&s.name) {
220                    out.push(MemberRef::Signal(s));
221                }
222            }
223            for k in &c.constants {
224                if seen.insert(&k.name) {
225                    out.push(MemberRef::Const(k));
226                }
227            }
228            for e in &c.enums {
229                if seen.insert(&e.name) {
230                    out.push(MemberRef::Enum(e));
231                }
232                // Enum VALUES are addressable on the class too (`Control.SIZE_EXPAND_FILL`) —
233                // offer them in the same candidate set `lookup_member` resolves them from.
234                for v in &e.values {
235                    if seen.insert(&v.name) {
236                        out.push(MemberRef::EnumValue {
237                            class: &c.name,
238                            decl: e,
239                            value: v,
240                        });
241                    }
242                }
243            }
244            cur = c.base;
245        }
246        out
247    }
248
249    // ---- builtins ----
250
251    /// A field of a builtin type (`Vector2.x`).
252    #[must_use]
253    pub fn builtin_member(&self, builtin: BuiltinId, name: &str) -> Option<&crate::BuiltinMember> {
254        self.builtin(builtin)
255            .members
256            .iter()
257            .find(|m| m.name == name)
258    }
259
260    /// A method of a builtin type (`Array.size`).
261    #[must_use]
262    pub fn builtin_method(&self, builtin: BuiltinId, name: &str) -> Option<&MethodSig> {
263        self.builtin(builtin)
264            .methods
265            .iter()
266            .find(|m| m.name == name)
267    }
268
269    /// The operator overloads of a builtin type (the caller matches `op` + RHS).
270    #[must_use]
271    pub fn builtin_operators(&self, builtin: BuiltinId) -> &[OperatorSig] {
272        &self.builtin(builtin).operators
273    }
274}