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}