Skip to main content

nodejs/
module.rs

1//! CommonJS module loader.
2//!
3//! Node's `require()` semantics, layered on the existing engine — no bespoke VM
4//! primitive. A `.js` file is wrapped in the canonical Node module wrapper
5//! `(function (exports, require, module, __dirname, __filename) { … })`, compiled
6//! through the ordinary `compile` → `load_merged` path to obtain the wrapper
7//! FUNCTION value, then `host::invoke`d with a fresh `module = { exports: {} }`.
8//! Whatever the body assigns to `module.exports` (or hangs off `exports`) is the
9//! module's value; it is cached by resolved absolute path so a second `require`
10//! of the same file returns the identical object and circular requires observe
11//! the partially-filled `exports`.
12//!
13//! Core modules (`fs`, `path`, `http`, …) short-circuit to their native
14//! `JsObj::Builtin` namespace (see `stdlib::resolve`) and are never read from
15//! disk. Everything else — relative paths, JSON files, and bare `node_modules`
16//! packages with their `package.json` `"exports"`/`"main"` and `index.js`
17//! fallbacks — resolves on the real filesystem and runs the genuine, unmodified
18//! source.
19//!
20//! Per-module `require` is a real JS closure that bakes in the defining module's
21//! directory, so a `require(...)` deferred inside a function called much later
22//! still resolves against the module that defined it (a single global
23//! "current dir" would resolve against the wrong module). The closure is minted
24//! by a one-time compiled factory (`FACTORY`) invoked with the directory string;
25//! it dispatches back into this loader through the `__cjs_require` /
26//! `__cjs_resolve` global native builtins.
27
28use std::cell::RefCell;
29use std::collections::HashMap;
30use std::path::{Path, PathBuf};
31
32use crate::host::{self, with_host, JsObj};
33use fusevm::Value;
34
35thread_local! {
36    /// Require cache: resolved absolute path → the `module` object (its `.exports`
37    /// is re-read on every hit, matching Node — `module.exports = X` reassignment
38    /// is observed by later requires).
39    static CACHE: RefCell<HashMap<PathBuf, Value>> = RefCell::new(HashMap::new());
40    /// Resolved `(specifier, from_dir)` to the CANONICAL absolute path it named,
41    /// so a repeated `require` of an already-loaded module costs one hash lookup
42    /// instead of walking `node_modules` and calling `canonicalize` again.
43    ///
44    /// Node keeps the same table (`Module._pathCache`) with the same
45    /// consequence: a file that appears after a specifier has already resolved
46    /// is not picked up by a later `require` of that specifier.
47    static PATH_CACHE: RefCell<HashMap<(String, PathBuf), PathBuf>> =
48        RefCell::new(HashMap::new());
49    /// Base directory the ENTRY script's top-level `require` resolves against
50    /// (the dir of `node app.js`, or cwd for `node -e`).
51    static ENTRY_DIR: RefCell<PathBuf> = RefCell::new(std::env::current_dir().unwrap_or_default());
52    /// The compiled per-module `require`-closure factory (see module docs),
53    /// minted once per host and reused for every module.
54    static FACTORY: RefCell<Option<Value>> = const { RefCell::new(None) };
55    /// The compiled synthetic-CallSite-array factory (for `Error.captureStackTrace`
56    /// under a custom `Error.prepareStackTrace`), minted once per host.
57    static CALLSITE_FACTORY: RefCell<Option<Value>> = const { RefCell::new(None) };
58}
59
60/// Clear all per-host loader state. Called from `host::reset_host` so a fresh
61/// eval (which rebuilds the heap) never reuses a stale heap handle.
62pub fn reset() {
63    CACHE.with(|c| c.borrow_mut().clear());
64    PATH_CACHE.with(|c| c.borrow_mut().clear());
65    FACTORY.with(|f| *f.borrow_mut() = None);
66    CALLSITE_FACTORY.with(|f| *f.borrow_mut() = None);
67    ENTRY_DIR.with(|d| *d.borrow_mut() = std::env::current_dir().unwrap_or_default());
68}
69
70/// The resolved filenames of every currently loaded module, in load order —
71/// the keys `require.cache` exposes.
72pub fn cache_keys() -> Vec<String> {
73    CACHE.with(|c| {
74        c.borrow()
75            .keys()
76            .map(|p| p.to_string_lossy().into_owned())
77            .collect()
78    })
79}
80
81/// The module object cached under the resolved filename `key`, if any.
82pub fn cache_get(key: &str) -> Option<Value> {
83    CACHE.with(|c| c.borrow().get(Path::new(key)).cloned())
84}
85
86/// Drop `key` from the module cache, so the next `require` of that file runs it
87/// again. This is what `delete require.cache[id]` must do to mean anything.
88pub fn cache_delete(key: &str) -> bool {
89    CACHE.with(|c| c.borrow_mut().remove(Path::new(key)).is_some())
90}
91
92/// Set the base directory the ENTRY script's `require` resolves against.
93pub fn set_entry_dir(dir: PathBuf) {
94    ENTRY_DIR.with(|d| *d.borrow_mut() = dir);
95}
96
97/// The ENTRY script's base directory.
98pub fn entry_dir() -> PathBuf {
99    ENTRY_DIR.with(|d| d.borrow().clone())
100}
101
102/// Install the CJS wrapper variables the ENTRY script sees.
103///
104/// A `require`d module already receives `exports`/`require`/`module`/`__dirname`
105/// /`__filename` as wrapper parameters (see `compile_wrapper`); the entry
106/// script used to receive none of them, so `typeof module` was `"undefined"`
107/// there and every UMD header took its browser branch. Node gives the entry
108/// script the same five names, with values that DEPEND ON THE ENTRY POINT:
109///
110/// | | `node f.js` | `node -e` | `node -` / piped |
111/// | --- | --- | --- | --- |
112/// | `__filename` | resolved abs path | `[eval]` | `[stdin]` |
113/// | `__dirname` | its directory | `.` | `.` |
114/// | `module.id` | `.` | `[eval]` | `[stdin]` |
115/// | `module.path` | its directory | `.` | `.` |
116///
117/// `module.filename` is `path.resolve(__filename)` in every case, so under `-e`
118/// it is `<cwd>/[eval]` — a path that does not exist, which is Node's own
119/// value. Measured on node v26.7.0.
120///
121/// `origin` is the `__filename` value: an absolute script path, or `[eval]` /
122/// `[stdin]` for the two source-on-the-command-line entry points.
123pub fn install_entry_globals(origin: &str) {
124    let from_file = origin != "[eval]" && origin != "[stdin]";
125    let (dirname, id) = if from_file {
126        let dir = Path::new(origin)
127            .parent()
128            .map(|p| p.to_string_lossy().into_owned())
129            .unwrap_or_else(|| ".".into());
130        (dir, ".".to_string())
131    } else {
132        (".".to_string(), origin.to_string())
133    };
134    let filename = crate::stdlib::path::resolve_one(origin);
135    let module = new_module(&id, &dirname, &filename);
136    let exports = module_exports(&module);
137    with_host(|h| {
138        let origin_str = h.new_str(origin.to_string());
139        let dirname = h.new_str(dirname);
140        h.set_global("__filename", origin_str);
141        h.set_global("__dirname", dirname);
142        h.set_global("module", module.clone());
143        // `require.main === module` in the entry script is the canonical
144        // "am I the program" test, and it read `undefined === <module>`
145        // because nothing ever set `main`. The ENTRY module is the value for
146        // every `require` in the process, not just this one, so it is recorded
147        // for the per-module closures too (`make_require` installs it).
148        // …but only when the program IS a module. `node -e` and a script on
149        // stdin run as a Script, not a CommonJS module, and node reports
150        // `require.main` as `undefined` for both.
151        if from_file {
152            h.set_builtin_static("require", "main", module.clone());
153        }
154        h.set_global("exports", exports.clone());
155        // Top-level `this`: the module's `exports` from a file (CommonJS
156        // module), `globalThis` from `-e` and from stdin (a Script). See
157        // `JsHost::set_top_this` for the measurements.
158        let top = if from_file {
159            exports
160        } else {
161            h.global_object()
162        };
163        h.set_top_this(top);
164    });
165}
166
167// ── resolution ───────────────────────────────────────────────────────────────
168
169/// Append `.ext` to a path (Node appends the extension, it does not replace an
170/// existing one — `foo.min` → `foo.min.js`, not `foo.js`).
171fn add_ext(p: &Path, ext: &str) -> PathBuf {
172    let mut s = p.as_os_str().to_owned();
173    s.push(".");
174    s.push(ext);
175    PathBuf::from(s)
176}
177
178/// `require`-as-a-file: `p`, then `p.js`, then `p.json`. `.node` native addons
179/// are skipped (unsupported), matching the resolution order minus that step.
180fn load_as_file(p: &Path) -> Option<PathBuf> {
181    if p.is_file() {
182        return Some(p.to_path_buf());
183    }
184    for ext in ["js", "json"] {
185        let cand = add_ext(p, ext);
186        if cand.is_file() {
187            return Some(cand);
188        }
189    }
190    None
191}
192
193/// `require`-as-a-directory: honor `package.json` `"exports"`/`"main"`, else
194/// `index.js` / `index.json`.
195fn load_as_dir(p: &Path) -> Option<PathBuf> {
196    let pkg = p.join("package.json");
197    if pkg.is_file() {
198        if let Some(main) = pkg_entry(&pkg) {
199            let mp = p.join(&main);
200            if let Some(f) = load_as_file(&mp).or_else(|| load_index(&mp)) {
201                return Some(f);
202            }
203        }
204    }
205    load_index(p)
206}
207
208/// `index.js` / `index.json` inside directory `p`.
209fn load_index(p: &Path) -> Option<PathBuf> {
210    for name in ["index.js", "index.json"] {
211        let cand = p.join(name);
212        if cand.is_file() {
213            return Some(cand);
214        }
215    }
216    None
217}
218
219/// The relative entry path a `package.json` declares: the `"."` (or main-string)
220/// `"exports"` target if present, else `"main"`. Only the common `"exports"`
221/// shapes are handled — a bare string, or an object whose `"."` maps to a string
222/// or to `{ "require"/"default"/"node": "…" }`. Anything more exotic falls back
223/// to `"main"`, then to the directory's `index.js`.
224fn pkg_entry(pkg: &Path) -> Option<String> {
225    let text = std::fs::read_to_string(pkg).ok()?;
226    let json: serde_json::Value = serde_json::from_str(&text).ok()?;
227    if let Some(e) = exports_main(json.get("exports")) {
228        return Some(strip_dot_slash(&e));
229    }
230    json.get("main")
231        .and_then(|m| m.as_str())
232        .map(strip_dot_slash)
233}
234
235/// Resolve the `"exports"` field down to a single relative path for the `"."`
236/// (package root) entry, across the shapes CommonJS packages commonly ship.
237fn exports_main(exports: Option<&serde_json::Value>) -> Option<String> {
238    let exports = exports?;
239    // `"exports": "./index.js"` — a bare string is the `"."` target.
240    if let Some(s) = exports.as_str() {
241        return Some(s.to_string());
242    }
243    let obj = exports.as_object()?;
244    // Either a subpath map keyed by `"."`, or a bare conditions map at the root.
245    let target = obj.get(".").unwrap_or(exports);
246    condition_target(target)
247}
248
249/// Reduce an `"exports"` target — a string, or a conditions object — to a path,
250/// preferring the CommonJS-relevant conditions (`require`/`node`/`default`).
251fn condition_target(target: &serde_json::Value) -> Option<String> {
252    if let Some(s) = target.as_str() {
253        return Some(s.to_string());
254    }
255    let obj = target.as_object()?;
256    for cond in ["require", "node", "default"] {
257        if let Some(v) = obj.get(cond) {
258            if let Some(s) = condition_target(v) {
259                return Some(s);
260            }
261        }
262    }
263    None
264}
265
266/// Drop a leading `./` from a package-relative path.
267fn strip_dot_slash(s: &str) -> String {
268    s.strip_prefix("./").unwrap_or(s).to_string()
269}
270
271/// Resolve `spec` (already known to be a bare specifier) by walking parent
272/// directories from `from_dir`, checking `<dir>/node_modules/<spec>` at each
273/// level with the file-then-directory rules.
274fn resolve_bare(spec: &str, from_dir: &Path) -> Option<PathBuf> {
275    let mut dir = Some(from_dir);
276    while let Some(d) = dir {
277        // Skip a `node_modules/node_modules` descent.
278        if d.file_name().is_some_and(|n| n == "node_modules") {
279            dir = d.parent();
280            continue;
281        }
282        let candidate = d.join("node_modules").join(spec);
283        if let Some(f) = load_as_file(&candidate).or_else(|| load_as_dir(&candidate)) {
284            return Some(f);
285        }
286        dir = d.parent();
287    }
288    None
289}
290
291/// Resolve `spec` relative to `from_dir` to an absolute file path, or `None` if
292/// no file matches (core modules are handled earlier, by the caller).
293pub fn resolve(spec: &str, from_dir: &Path) -> Option<PathBuf> {
294    let is_relative =
295        spec.starts_with("./") || spec.starts_with("../") || spec == "." || spec == "..";
296    let is_absolute = spec.starts_with('/');
297    if is_relative || is_absolute {
298        // NORMALIZED, not merely joined: `Path::join` keeps the `.` in
299        // `<dir>/./d.js`, and that string is what `require.resolve` returns and
300        // what keys the module cache — so `./d.js` and `d.js` from the same
301        // directory would be two cache entries of one file.
302        let joined = if is_absolute {
303            spec.to_string()
304        } else {
305            from_dir.join(spec).to_string_lossy().into_owned()
306        };
307        let base = PathBuf::from(crate::stdlib::path::resolve_one(&joined));
308        return load_as_file(&base).or_else(|| load_as_dir(&base));
309    }
310    resolve_bare(spec, from_dir)
311}
312
313// ── loading / execution ──────────────────────────────────────────────────────
314
315/// `require(spec)` from `from_dir`: the single entry point shared by the
316/// top-level `require` builtin and the per-module `__cjs_require`. Returns the
317/// module's exports value.
318pub fn require(spec: &str, from_dir: &Path) -> Result<Value, String> {
319    // Core module: the native namespace value, never a file (mirrors the legacy
320    // `require` path — `require('events')` yields the EventEmitter ctor, etc.).
321    if let Some(ns) = crate::stdlib::resolve(spec) {
322        return Ok(with_host(|h| h.alloc(JsObj::Builtin(ns.to_string()))));
323    }
324    // Resolution is filesystem work — a `node_modules` walk, a set of extension
325    // probes, then `canonicalize` — and a program that requires the same
326    // specifier in a loop paid all of it on every call even though the module
327    // itself was already loaded and cached. The answer is memoized per
328    // `(specifier, from_dir)`.
329    let key = (spec.to_string(), from_dir.to_path_buf());
330    if let Some(hit) = PATH_CACHE.with(|c| c.borrow().get(&key).cloned()) {
331        return load_file(&hit);
332    }
333    let path = resolve(spec, from_dir).ok_or_else(|| {
334        crate::host::plain_coded_error(
335            "Error",
336            "MODULE_NOT_FOUND",
337            &format!("Cannot find module '{spec}'"),
338        )
339    })?;
340    // A canonical absolute key so the same file required via different relative
341    // specifiers shares one cache entry.
342    let path = std::fs::canonicalize(&path).unwrap_or(path);
343    PATH_CACHE.with(|c| c.borrow_mut().insert(key, path.clone()));
344    load_file(&path)
345}
346
347/// Load the resolved absolute file `path` (`.json` parses to its value; `.js`
348/// runs through the module wrapper) and return its exports, caching by path.
349fn load_file(path: &Path) -> Result<Value, String> {
350    if let Some(cached) = CACHE.with(|c| c.borrow().get(path).cloned()) {
351        // Re-read `.exports` — a cached module may have reassigned it.
352        return Ok(module_exports(&cached));
353    }
354    if path.extension().is_some_and(|e| e == "json") {
355        let text = std::fs::read_to_string(path)
356            .map_err(|e| format!("cannot read {}: {e}", path.display()))?;
357        let src = with_host(|h| h.new_str(text));
358        let val = crate::builtins::call_builtin_function("JSON.parse", vec![src])?;
359        // A JSON module's value IS the parsed data; cache a synthetic wrapper so
360        // repeated requires share it.
361        let abs = path.to_string_lossy().into_owned();
362        let dir = path.parent().unwrap_or(Path::new("")).to_string_lossy();
363        let module = new_module(&abs, &dir, &abs);
364        with_host(|h| {
365            if let Some(JsObj::Object(p)) = h.get_mut(&module) {
366                p.insert("exports".to_string(), val.clone());
367                p.insert("loaded".to_string(), Value::Bool(true));
368            }
369        });
370        CACHE.with(|c| c.borrow_mut().insert(path.to_path_buf(), module));
371        return Ok(val);
372    }
373
374    let source = std::fs::read_to_string(path)
375        .map_err(|e| format!("cannot read {}: {e}", path.display()))?;
376    let dir = path.parent().map(Path::to_path_buf).unwrap_or_default();
377
378    // Compile the Node module wrapper to obtain the wrapper FUNCTION value.
379    // A compile error is annotated with the offending file (Node does likewise).
380    let wrapper = compile_wrapper(&source)
381        .map_err(|e| format!("{e}\n    while loading {}", path.display()))?;
382
383    // The `module` object, plus the aliases the wrapper receives. A required
384    // module's `id` IS its absolute filename (only the entry module's is `.`).
385    let abs = path.to_string_lossy().into_owned();
386    let module = new_module(&abs, &dir.to_string_lossy(), &abs);
387    let exports = module_exports(&module);
388    let require_fn = make_require(&dir)?;
389    let (dirname, filename) = with_host(|h| {
390        (
391            h.new_str(dir.to_string_lossy().to_string()),
392            h.new_str(path.to_string_lossy().to_string()),
393        )
394    });
395
396    // Cache BEFORE running so a circular `require` back to this module observes
397    // the partial `exports`.
398    CACHE.with(|c| c.borrow_mut().insert(path.to_path_buf(), module.clone()));
399
400    host::invoke(
401        &wrapper,
402        vec![exports, require_fn, module.clone(), dirname, filename],
403        None,
404    )?;
405    mark_loaded(&module);
406
407    Ok(module_exports(&module))
408}
409
410/// A fresh `module` object: `{ id, path, exports, filename, loaded, children,
411/// paths }`, in that key order.
412///
413/// The order is observable (`Object.keys(module)`) and this is node's. Only
414/// `exports` used to be present, so a module reading `module.id` or
415/// `module.filename` — both of which a bundler-emitted or `__dirname`-avoiding
416/// package does — got `undefined`.
417///
418/// `paths` is the `node_modules` chain from `dir` up to the root, the same walk
419/// `require` performs to resolve a bare specifier. `loaded` starts `false`; it
420/// is set once the body returns.
421fn new_module(id: &str, dir: &str, filename: &str) -> Value {
422    let mut node_modules: Vec<String> = Vec::new();
423    let mut cur = Some(Path::new(dir));
424    while let Some(d) = cur.filter(|d| !d.as_os_str().is_empty()) {
425        node_modules.push(d.join("node_modules").to_string_lossy().into_owned());
426        cur = d.parent();
427    }
428    with_host(|h| {
429        let exports = h.new_object(indexmap::IndexMap::new());
430        let mut props = indexmap::IndexMap::new();
431        props.insert("id".to_string(), h.new_str(id.to_string()));
432        props.insert("path".to_string(), h.new_str(dir.to_string()));
433        props.insert("exports".to_string(), exports);
434        props.insert("filename".to_string(), h.new_str(filename.to_string()));
435        props.insert("loaded".to_string(), Value::Bool(false));
436        let children = h.new_array(Vec::new());
437        props.insert("children".to_string(), children);
438        let paths: Vec<Value> = node_modules.into_iter().map(|p| h.new_str(p)).collect();
439        let paths = h.new_array(paths);
440        props.insert("paths".to_string(), paths);
441        h.new_object(props)
442    })
443}
444
445/// Flip `module.loaded` once the body has run, as Node's loader does.
446fn mark_loaded(module: &Value) {
447    with_host(|h| {
448        if let Some(JsObj::Object(p)) = h.get_mut(module) {
449            p.insert("loaded".to_string(), Value::Bool(true));
450        }
451    });
452}
453
454/// Read `module.exports` (falls back to `undefined` for a malformed module).
455fn module_exports(module: &Value) -> Value {
456    with_host(|h| match h.get(module) {
457        Some(JsObj::Object(p)) => p.get("exports").cloned().unwrap_or(Value::Undef),
458        _ => Value::Undef,
459    })
460}
461
462/// Compile `<source>` wrapped in the Node module wrapper and return the wrapper
463/// FUNCTION value.
464fn compile_wrapper(source: &str) -> Result<Value, String> {
465    // A trailing newline before `})` guards a source ending in a `//` comment.
466    eval_binding(&format!(
467        "(function (exports, require, module, __dirname, __filename) {{\n{source}\n}})"
468    ))
469}
470
471/// Compile+run a single JS expression on the LIVE host — no reset, no
472/// event-loop drain — and return its value.
473///
474/// This delegates to `crate::eval_in_global_scope`, the frontend's one
475/// runtime-source evaluator, and two things changed with it. The wrapper used to
476/// be compiled as `var __cjs_wN = (function …);` and read back out of the scope
477/// with `read_name`, because a bare expression statement pops its value; a
478/// completion-value compile returns the expression directly, so the capture
479/// variable and its uniquifying counter are gone. And the run used to happen on
480/// the CALLER's frame, which let a module body see the locals of whatever
481/// function called `require`: measured against node v26.7.0,
482/// `function outer(){ let secret = 1; return require('./m.js'); }` with `m.js` =
483/// `module.exports = typeof secret` is `"undefined"` there and was `"number"`
484/// here.
485fn eval_binding(src: &str) -> Result<Value, String> {
486    crate::eval_in_global_scope(src)
487}
488
489/// Build a per-module `require` closure bound to `dir` (see module docs).
490fn make_require(dir: &Path) -> Result<Value, String> {
491    let factory = factory()?;
492    let dir_str = with_host(|h| h.new_str(dir.to_string_lossy().to_string()));
493    let req = host::invoke(&factory, vec![dir_str], None)?;
494    // Every `require` in the process reports the same `main` — the ENTRY
495    // module — which is what `require.main === module` tests against.
496    if let Some(main) = with_host(|h| h.builtin_static("require", "main")) {
497        // `req` is a FUNCTION value, so its properties live in the fn-prop side
498        // table, not in an object property map.
499        with_host(|h| h.set_fn_prop(&req, "main", main));
500    }
501    Ok(req)
502}
503
504/// The one-time compiled `require`-closure factory. `require.resolve` /
505/// `require.cache` are provided since some packages read them.
506fn factory() -> Result<Value, String> {
507    if let Some(f) = FACTORY.with(|f| f.borrow().clone()) {
508        return Ok(f);
509    }
510    let src = "(function (__cjs_dir) {\n\
511        var req = function (spec) { return __cjs_require(spec, __cjs_dir); };\n\
512        req.resolve = function (spec) { return __cjs_resolve(spec, __cjs_dir); };\n\
513        req.cache = __cjs_cache;\n\
514        req.main = undefined;\n\
515        req.extensions = {};\n\
516        return req;\n\
517    });";
518    let f = eval_binding(src)?;
519    FACTORY.with(|c| *c.borrow_mut() = Some(f.clone()));
520    Ok(f)
521}
522
523/// An array of `depth` synthetic V8 CallSite objects for `Error.captureStackTrace`.
524/// Stack-introspection packages (e.g. `depd`) set `Error.prepareStackTrace` to a
525/// function that receives this array; the getters return neutral placeholders (no
526/// real frame info is available), which is enough for those packages to build
527/// their deprecation sites without throwing.
528pub fn callsite_stack(depth: usize) -> Result<Value, String> {
529    let factory = if let Some(f) = CALLSITE_FACTORY.with(|f| f.borrow().clone()) {
530        f
531    } else {
532        let src = "(function (n) {\n\
533            var a = [];\n\
534            for (var i = 0; i < n; i++) {\n\
535                a.push({\n\
536                    getFileName: function () { return null; },\n\
537                    getLineNumber: function () { return 0; },\n\
538                    getColumnNumber: function () { return 0; },\n\
539                    getFunctionName: function () { return null; },\n\
540                    getMethodName: function () { return null; },\n\
541                    getTypeName: function () { return null; },\n\
542                    getThis: function () { return undefined; },\n\
543                    isNative: function () { return false; },\n\
544                    isEval: function () { return false; },\n\
545                    toString: function () { return '<anonymous>'; }\n\
546                });\n\
547            }\n\
548            return a;\n\
549        });";
550        let f = eval_binding(src)?;
551        CALLSITE_FACTORY.with(|c| *c.borrow_mut() = Some(f.clone()));
552        f
553    };
554    host::invoke(&factory, vec![Value::Float(depth as f64)], None)
555}