ad_astra/runtime/
hints.rs

1////////////////////////////////////////////////////////////////////////////////
2// This file is part of "Ad Astra", an embeddable scripting programming       //
3// language platform.                                                         //
4//                                                                            //
5// This work is proprietary software with source-available code.              //
6//                                                                            //
7// To copy, use, distribute, or contribute to this work, you must agree to    //
8// the terms of the General License Agreement:                                //
9//                                                                            //
10// https://github.com/Eliah-Lakhin/ad-astra/blob/master/EULA.md               //
11//                                                                            //
12// The agreement grants a Basic Commercial License, allowing you to use       //
13// this work in non-commercial and limited commercial products with a total   //
14// gross revenue cap. To remove this commercial limit for one of your         //
15// products, you must acquire a Full Commercial License.                      //
16//                                                                            //
17// If you contribute to the source code, documentation, or related materials, //
18// you must grant me an exclusive license to these contributions.             //
19// Contributions are governed by the "Contributions" section of the General   //
20// License Agreement.                                                         //
21//                                                                            //
22// Copying the work in parts is strictly forbidden, except as permitted       //
23// under the General License Agreement.                                       //
24//                                                                            //
25// If you do not or cannot agree to the terms of this Agreement,              //
26// do not use this work.                                                      //
27//                                                                            //
28// This work is provided "as is", without any warranties, express or implied, //
29// except where such disclaimers are legally invalid.                         //
30//                                                                            //
31// Copyright (c) 2024 Ilya Lakhin (Илья Александрович Лахин).                 //
32// All rights reserved.                                                       //
33////////////////////////////////////////////////////////////////////////////////
34
35use std::{
36    cmp::Ordering,
37    fmt::{Debug, Display, Formatter},
38    hash::Hash,
39};
40
41use crate::runtime::{InvocationMeta, PackageMeta, RustIdent, TypeFamily, TypeMeta};
42
43/// An extended type metadata that can be a [TypeMeta], [TypeFamily], or
44/// [InvocationMeta].
45///
46/// The purpose of this object is to provide as precise type metadata as
47/// possible for preliminary static semantic analysis of the Script source code.
48///
49/// For example, if the analyzed object is a function, [InvocationMeta] is more
50/// precise than [TypeMeta] because it provides additional information about the
51/// function's parameter signature and return type.
52///
53/// When the object may potentially represent a set of distinct types within the
54/// same family (e.g., `usize` and `f64` are both numeric types), the TypeHint
55/// would be a [TypeFamily].
56///
57/// If the type cannot be statically determined, the TypeHint is
58/// [dynamic](TypeHint::dynamic).
59///
60/// The [Display] implementation of this object attempts to print the type's
61/// signature in a way that is more user-friendly.
62#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
63pub enum TypeHint {
64    Type(&'static TypeMeta),
65    Family(&'static TypeFamily),
66    Invocation(&'static InvocationMeta),
67}
68
69impl Display for TypeHint {
70    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
71        match self {
72            Self::Type(meta) => {
73                if formatter.alternate() || meta.is_fn() {
74                    return Display::fmt(meta, formatter);
75                }
76
77                Display::fmt(meta.family(), formatter)
78            }
79
80            Self::Family(meta) => Display::fmt(meta, formatter),
81
82            Self::Invocation(meta) => Display::fmt(meta, formatter),
83        }
84    }
85}
86
87impl From<&'static TypeMeta> for TypeHint {
88    #[inline(always)]
89    fn from(value: &'static TypeMeta) -> Self {
90        Self::Type(value)
91    }
92}
93
94impl From<&'static TypeFamily> for TypeHint {
95    #[inline(always)]
96    fn from(value: &'static TypeFamily) -> Self {
97        Self::Family(value)
98    }
99}
100
101impl From<&'static InvocationMeta> for TypeHint {
102    #[inline(always)]
103    fn from(value: &'static InvocationMeta) -> Self {
104        Self::Invocation(value)
105    }
106}
107
108impl TypeHint {
109    /// Returns a [TypeHint] for the [unit] `()` type.
110    #[inline(always)]
111    pub fn nil() -> Self {
112        Self::Type(TypeMeta::nil())
113    }
114
115    /// Returns a [TypeHint] for a type that cannot be determined statically.
116    #[inline(always)]
117    pub fn dynamic() -> Self {
118        Self::Type(TypeMeta::dynamic())
119    }
120
121    /// Returns true if the underlying type is the [unit] `()` type.
122    #[inline(always)]
123    pub fn is_nil(&self) -> bool {
124        self.type_family().is_nil()
125    }
126
127    /// Returns true if the underlying type cannot be determined statically.
128    #[inline(always)]
129    pub fn is_dynamic(&self) -> bool {
130        self.type_family().is_dynamic()
131    }
132
133    /// Returns true if the underlying type is a function, belongs to a family
134    /// of functions, or is an object that supports
135    /// [invocation operator](crate::runtime::Prototype::implements_invocation).
136    #[inline(always)]
137    pub fn is_fn(&self) -> bool {
138        match self {
139            Self::Type(meta) => meta.is_fn(),
140            Self::Family(meta) => meta.is_fn(),
141            Self::Invocation(_) => true,
142        }
143    }
144
145    /// Returns true if the underlying type represents a Script package
146    /// (i.e., a Rust struct that has been exported with `#[export(package)]`).
147    #[inline(always)]
148    pub fn is_package(&self) -> bool {
149        match self {
150            Self::Type(meta) => meta.family().is_package(),
151            Self::Family(meta) => meta.is_package(),
152            Self::Invocation(_) => false,
153        }
154    }
155
156    /// Returns true if the underlying type represents a number (e.g., `u8`,
157    /// `isize`, `f32`, etc.) or belongs to a family of numeric types.
158    #[inline(always)]
159    pub fn is_number(&self) -> bool {
160        match self {
161            Self::Type(meta) => meta.family().is_number(),
162            Self::Family(meta) => meta.is_number(),
163            Self::Invocation(_) => false,
164        }
165    }
166
167    /// Attempts to provide a [TypeMeta] for the underlying type. The function
168    /// returns None if a single type cannot be determined, for instance,
169    /// if the TypeInfo describes a family of types.
170    #[inline(always)]
171    pub fn type_meta(&self) -> Option<&'static TypeMeta> {
172        match self {
173            Self::Type(meta) => Some(*meta),
174            Self::Family(_) => None,
175            Self::Invocation(meta) => {
176                let arity = meta.arity()?;
177
178                TypeMeta::script_fn(arity)
179            }
180        }
181    }
182
183    /// Returns the family of types to which this type belongs.
184    ///
185    /// This function is infallible because every Script type belongs to some
186    /// family (even if the family consists of just one type).
187    #[inline(always)]
188    pub fn type_family(&self) -> &'static TypeFamily {
189        match self {
190            Self::Type(meta) => meta.family(),
191            Self::Family(meta) => *meta,
192            Self::Invocation(_) => TypeFamily::fn_family(),
193        }
194    }
195
196    /// If the underlying type supports the
197    /// [invocation operator](crate::runtime::Prototype::implements_invocation),
198    /// returns metadata for this invocation.
199    #[inline(always)]
200    pub fn invocation(&self) -> Option<&'static InvocationMeta> {
201        match self {
202            Self::Type(meta) => meta.prototype().hint_invocation(),
203            Self::Family(_) => None,
204            Self::Invocation(meta) => Some(meta),
205        }
206    }
207
208    /// Returns the package metadata of the crate from which this type was
209    /// exported.
210    ///
211    /// Formally, exported Rust types do not belong to Script packages. The type
212    /// could be exported from a crate that does not have a Script package at
213    /// all. Therefore, the result of this function is a best-effort estimation.
214    #[inline(always)]
215    pub fn package(&self) -> Option<&'static PackageMeta> {
216        match self {
217            Self::Type(meta) => meta.origin().package(),
218            Self::Family(_) => None,
219            Self::Invocation(meta) => meta.origin.package(),
220        }
221    }
222
223    /// Attempts to return the relevant RustDoc documentation for the referred
224    /// type.
225    #[inline(always)]
226    pub fn doc(&self) -> Option<&'static str> {
227        match self {
228            Self::Type(meta) => {
229                let family = meta.family();
230
231                if family.len() > 1 && !family.is_package() {
232                    return family.doc();
233                }
234
235                meta.doc()
236            }
237
238            Self::Family(meta) => meta.doc(),
239
240            Self::Invocation(meta) => meta.doc,
241        }
242    }
243}
244
245/// A description of a statically known field of the type: `object.field`.
246///
247/// The field could be an object of a particular type or an object's method
248/// available for invocation.
249#[derive(Clone, Copy, PartialEq, Eq, Debug)]
250pub struct ComponentHint {
251    /// The name of the field.
252    pub name: &'static RustIdent,
253
254    /// The type of the field.
255    ///
256    /// In the case of methods, the TypeHint is usually [TypeHint::Invocation],
257    /// which describes the invocation signature of the method.
258    pub ty: TypeHint,
259
260    /// The relevant RustDoc documentation for the field.
261    ///
262    /// This documentation pertains to the field itself and is (usually)
263    /// different from the documentation found in the [TypeHint::doc] of the
264    /// field's type description.
265    ///
266    /// ```no_run
267    /// use ad_astra::export;
268    ///
269    /// #[export]
270    /// struct Foo {
271    ///     /// A ComponentHint's documentation.
272    ///     pub field: usize,
273    /// }
274    ///
275    /// #[export]
276    /// impl Foo {
277    ///     /// A ComponentHint's documentation.
278    ///     pub fn method(&self) {}
279    /// }
280    /// ```
281    pub doc: Option<&'static str>,
282}
283
284impl Display for ComponentHint {
285    fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
286        formatter.write_str(self.name.as_ref())?;
287
288        if !self.ty.is_dynamic() {
289            formatter.write_str(": ")?;
290            Display::fmt(&self.ty, formatter)?;
291        }
292
293        Ok(())
294    }
295}
296
297impl PartialOrd for ComponentHint {
298    #[inline(always)]
299    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
300        Some(self.cmp(other))
301    }
302}
303
304impl Ord for ComponentHint {
305    #[inline]
306    fn cmp(&self, other: &Self) -> Ordering {
307        self.name.cmp(&other.name)
308    }
309}