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}