Skip to main content

smelt_stdlib/
globals.rs

1//! Recognition of well-known language builtins that Smelt does not model.
2//!
3//! When a reference fails to resolve, the frontend asks this module whether the
4//! name is a language/runtime builtin. A builtin that fails to resolve is a
5//! [`MissingStdlib`](crate::DiagnosticCategory::MissingStdlib) gap (Smelt should
6//! grow support for it); any other unresolved name is an
7//! [`UnresolvedReference`](crate::DiagnosticCategory::UnresolvedReference).
8//!
9//! The set is intentionally a recognition list, not a support list — names here
10//! may or may not be lowered yet. It only answers "is this a builtin?".
11
12/// Returns whether `name` is a well-known JavaScript/TypeScript global builtin
13/// (ECMAScript intrinsics plus widely available Web/Node runtime globals).
14#[must_use]
15pub fn is_javascript_global_builtin(name: &str) -> bool {
16    matches!(
17        name,
18        // Core constructors and namespaces.
19        "Array" | "Object" | "Number" | "String" | "Boolean" | "BigInt"
20        | "Symbol" | "Function" | "Math" | "JSON" | "Reflect" | "Proxy"
21        | "Date" | "RegExp" | "Promise" | "Map" | "Set" | "WeakMap"
22        | "WeakSet" | "WeakRef" | "Iterator" | "Generator" | "Intl"
23        // Error constructors.
24        | "Error" | "TypeError" | "RangeError" | "SyntaxError"
25        | "ReferenceError" | "EvalError" | "URIError" | "AggregateError"
26        // Binary data / typed arrays.
27        | "ArrayBuffer" | "SharedArrayBuffer" | "DataView" | "Int8Array"
28        | "Uint8Array" | "Uint8ClampedArray" | "Int16Array" | "Uint16Array"
29        | "Int32Array" | "Uint32Array" | "Float32Array" | "Float64Array"
30        | "BigInt64Array" | "BigUint64Array"
31        // Global functions.
32        | "parseInt" | "parseFloat" | "isNaN" | "isFinite" | "structuredClone"
33        | "encodeURIComponent" | "decodeURIComponent" | "encodeURI" | "decodeURI"
34        | "queueMicrotask" | "setTimeout" | "clearTimeout" | "setInterval"
35        | "clearInterval"
36        // Ambient globals and environment objects.
37        | "globalThis" | "global" | "self" | "window" | "console" | "process"
38        // Common Web / Node runtime classes.
39        | "TextEncoder" | "TextDecoder" | "URL" | "URLSearchParams" | "Blob"
40        | "File" | "FormData" | "Headers" | "Request" | "Response" | "Buffer"
41        | "AbortController" | "AbortSignal" | "Event" | "EventTarget"
42    )
43}
44
45/// Compile-time availability of a global object member in the active target profile.
46///
47/// The current target is a deterministic non-DOM, Node-compatible environment
48/// (the default generated Rust test profile from the global-objects plan). A
49/// feature probe such as `"X" in globalThis` may only fold to a literal when the
50/// answer is *known* for that profile; an [`Unknown`](GlobalPresence::Unknown)
51/// member must keep its runtime check so erased and runtime answers never
52/// disagree.
53#[derive(Clone, Copy, Debug, Eq, PartialEq)]
54#[non_exhaustive]
55pub enum GlobalPresence {
56    /// The member is a recognized builtin that exists in the non-DOM profile, so
57    /// `"X" in globalThis` is statically `true`.
58    Present,
59    /// The member is a recognized builtin that is intentionally absent from the
60    /// non-DOM profile (browser/DOM-only surfaces), so `"X" in globalThis` is
61    /// statically `false`.
62    Absent,
63    /// The member is not in any recognition registry, so its presence cannot be
64    /// decided at compile time and the probe must stay a runtime check.
65    Unknown,
66}
67
68/// Global members that the non-DOM Node-compatible profile treats as absent.
69///
70/// These names are still recognized JavaScript globals (see
71/// [`is_javascript_global_builtin`]) but only exist in a browser/DOM host, so the
72/// non-DOM profile answers `"X" in globalThis` with `false`. Keeping the absent
73/// set explicit means everything else recognized is derived as present, instead
74/// of maintaining a parallel hand-written "present" list that could drift from
75/// the recognition registry.
76const NON_DOM_ABSENT_GLOBALS: &[&str] = &["window", "self", "document"];
77
78/// Classify a candidate global member name for the non-DOM Node-compatible profile.
79///
80/// The result is derived from the recognition registries that codegen actually
81/// lowers — [`is_javascript_global_builtin`] plus the absent-in-non-DOM denylist —
82/// rather than a separate literal "present" list, so the compile-time answer to
83/// `"X" in globalThis` cannot drift from what Smelt can lower. Per the plan,
84/// `structuredClone` and `crypto` are *not* answered `Present` here: they are
85/// runtime functions whose probes may only fold once a deterministic runtime
86/// implementation exists, so they stay [`Unknown`](GlobalPresence::Unknown).
87#[must_use]
88pub fn global_member_presence(name: &str) -> GlobalPresence {
89    if NON_DOM_ABSENT_GLOBALS.contains(&name) {
90        return GlobalPresence::Absent;
91    }
92    // Runtime-capability functions are gated on real deterministic runtime
93    // support landing (plan section 7); until then their probe must not fold.
94    if matches!(name, "structuredClone" | "crypto" | "fetch") {
95        return GlobalPresence::Unknown;
96    }
97    if is_javascript_global_builtin(name) {
98        GlobalPresence::Present
99    } else {
100        GlobalPresence::Unknown
101    }
102}
103
104#[cfg(test)]
105mod tests {
106    use super::*;
107
108    /// Recognized intrinsics and runtime globals report as builtins.
109    #[test]
110    fn recognizes_known_globals() {
111        for name in ["Array", "Number", "Reflect", "Proxy", "TextEncoder", "globalThis"] {
112            assert!(is_javascript_global_builtin(name), "{name} should be a builtin");
113        }
114    }
115
116    /// Ordinary user identifiers are not builtins.
117    #[test]
118    fn rejects_user_identifiers() {
119        for name in ["Foo", "curried", "myHelper", "Circle"] {
120            assert!(!is_javascript_global_builtin(name), "{name} should not be a builtin");
121        }
122    }
123
124    /// Recognized non-DOM globals are present so `"X" in globalThis` folds true.
125    #[test]
126    fn recognized_node_globals_are_present() {
127        for name in ["Map", "Set", "ArrayBuffer", "Reflect", "Promise", "process"] {
128            assert_eq!(global_member_presence(name), GlobalPresence::Present, "{name}");
129        }
130    }
131
132    /// DOM-only globals are absent so `"X" in globalThis` folds false.
133    #[test]
134    fn dom_only_globals_are_absent() {
135        for name in ["window", "self", "document"] {
136            assert_eq!(global_member_presence(name), GlobalPresence::Absent, "{name}");
137        }
138    }
139
140    /// Unrecognized names and runtime-gated capabilities stay unknown.
141    #[test]
142    fn unmodeled_members_are_unknown() {
143        for name in ["DocumentFragment", "__feature", "structuredClone", "crypto"] {
144            assert_eq!(global_member_presence(name), GlobalPresence::Unknown, "{name}");
145        }
146    }
147
148    /// The present set is derived from the recognition registry, not a copy of it.
149    #[test]
150    fn presence_tracks_recognition_registry() {
151        // Every recognized builtin is either present or explicitly absent; none
152        // silently fall through to Unknown except the runtime-gated capabilities.
153        for name in ["Array", "JSON", "Math", "Buffer", "URL", "TextEncoder"] {
154            assert_ne!(
155                global_member_presence(name),
156                GlobalPresence::Unknown,
157                "recognized builtin {name} should have a decided presence"
158            );
159        }
160    }
161}