Skip to main content

rustpython_vm/stdlib/
sys.rs

1pub(crate) mod monitoring;
2
3use crate::{Py, PyPayload, PyResult, VirtualMachine, builtins::PyModule, convert::ToPyObject};
4
5#[cfg(all(not(feature = "host_env"), feature = "stdio"))]
6pub(crate) use sys::SandboxStdio;
7pub(crate) use sys::{DOC, MAXSIZE, RUST_MULTIARCH, UnraisableHookArgsData, module_def, multiarch};
8
9#[pymodule(name = "_jit")]
10mod sys_jit {
11    /// Return True if the current Python executable supports JIT compilation,
12    /// and False otherwise.
13    #[pyfunction]
14    const fn is_available() -> bool {
15        false // RustPython has no JIT
16    }
17
18    /// Return True if JIT compilation is enabled for the current Python process,
19    /// and False otherwise.
20    #[pyfunction]
21    const fn is_enabled() -> bool {
22        false // RustPython has no JIT
23    }
24
25    /// Return True if the topmost Python frame is currently executing JIT code,
26    /// and False otherwise.
27    #[pyfunction]
28    const fn is_active() -> bool {
29        false // RustPython has no JIT
30    }
31}
32
33#[pymodule]
34mod sys {
35    use crate::{
36        AsObject, PyObject, PyObjectRef, PyPayload, PyRef, PyRefExact, PyResult,
37        builtins::{
38            PyBaseExceptionRef, PyDictRef, PyFrozenSet, PyNamespace, PyStr, PyStrRef, PyTuple,
39            PyTupleRef, PyTypeRef, PyUtf8StrRef,
40        },
41        common::{
42            ascii,
43            hash::{PyHash, PyUHash},
44        },
45        convert::ToPyObject,
46        frame::{Frame, FrameRef},
47        function::{FuncArgs, KwArgs, OptionalArg, PosArgs},
48        stdlib::{_warnings::warn, builtins},
49        types::PyStructSequence,
50        version,
51        vm::{Settings, VirtualMachine},
52    };
53    use core::sync::atomic::Ordering;
54    use num_traits::ToPrimitive;
55    use std::{
56        env::{self, VarError},
57        io::{IsTerminal, Read, Write},
58    };
59
60    #[cfg(windows)]
61    use windows_sys::Win32::{
62        Foundation::MAX_PATH,
63        Storage::FileSystem::{
64            GetFileVersionInfoSizeW, GetFileVersionInfoW, VS_FIXEDFILEINFO, VerQueryValueW,
65        },
66        System::LibraryLoader::{GetModuleFileNameW, GetModuleHandleW},
67    };
68
69    // Rust target triple (e.g., "x86_64-unknown-linux-gnu")
70    pub(crate) const RUST_MULTIARCH: &str = env!("RUSTPYTHON_TARGET_TRIPLE");
71
72    /// Convert Rust target triple to CPython-style multiarch
73    /// e.g., "x86_64-unknown-linux-gnu" -> "x86_64-linux-gnu"
74    pub(crate) fn multiarch() -> String {
75        RUST_MULTIARCH.replace("-unknown", "")
76    }
77
78    #[pymodule(name = "monitoring", with(super::monitoring::sys_monitoring))]
79    pub(super) mod monitoring {}
80
81    #[pyclass(no_attr, name = "_BootstrapStderr")]
82    #[derive(Debug, PyPayload)]
83    pub(super) struct BootstrapStderr;
84
85    #[pyclass]
86    impl BootstrapStderr {
87        #[pymethod]
88        fn write(&self, s: PyStrRef) -> PyResult<usize> {
89            let bytes = s.as_bytes();
90            let _ = std::io::stderr().write_all(bytes);
91            Ok(bytes.len())
92        }
93
94        #[pymethod]
95        fn flush(&self) -> PyResult<()> {
96            let _ = std::io::stderr().flush();
97            Ok(())
98        }
99    }
100
101    /// Lightweight stdio wrapper for sandbox mode (no host_env).
102    /// Directly uses Rust's std::io for stdin/stdout/stderr without FileIO.
103    #[pyclass(no_attr, name = "_SandboxStdio")]
104    #[derive(Debug, PyPayload)]
105    pub struct SandboxStdio {
106        pub fd: i32,
107        pub name: String,
108        pub mode: String,
109    }
110
111    #[pyclass]
112    impl SandboxStdio {
113        #[pymethod]
114        fn write(&self, s: PyStrRef, vm: &VirtualMachine) -> PyResult<usize> {
115            if self.fd == 0 {
116                return Err(vm.new_os_error("not writable".to_owned()));
117            }
118            let bytes = s.as_bytes();
119            if self.fd == 2 {
120                std::io::stderr()
121                    .write_all(bytes)
122                    .map_err(|e| vm.new_os_error(e.to_string()))?;
123            } else {
124                std::io::stdout()
125                    .write_all(bytes)
126                    .map_err(|e| vm.new_os_error(e.to_string()))?;
127            }
128            Ok(bytes.len())
129        }
130
131        #[pymethod]
132        fn readline(&self, size: OptionalArg<isize>, vm: &VirtualMachine) -> PyResult<String> {
133            if self.fd != 0 {
134                return Err(vm.new_os_error("not readable".to_owned()));
135            }
136            let size = size.unwrap_or(-1);
137            if size == 0 {
138                return Ok(String::new());
139            }
140            let mut line = String::new();
141            std::io::stdin()
142                .read_line(&mut line)
143                .map_err(|e| vm.new_os_error(e.to_string()))?;
144            if size > 0 {
145                line.truncate(size as usize);
146            }
147            Ok(line)
148        }
149
150        #[pymethod]
151        fn flush(&self, vm: &VirtualMachine) -> PyResult<()> {
152            match self.fd {
153                1 => {
154                    std::io::stdout()
155                        .flush()
156                        .map_err(|e| vm.new_os_error(e.to_string()))?;
157                }
158                2 => {
159                    std::io::stderr()
160                        .flush()
161                        .map_err(|e| vm.new_os_error(e.to_string()))?;
162                }
163                _ => {}
164            }
165            Ok(())
166        }
167
168        #[pymethod]
169        fn fileno(&self) -> i32 {
170            self.fd
171        }
172
173        #[pymethod]
174        fn isatty(&self) -> bool {
175            match self.fd {
176                0 => std::io::stdin().is_terminal(),
177                1 => std::io::stdout().is_terminal(),
178                2 => std::io::stderr().is_terminal(),
179                _ => false,
180            }
181        }
182
183        #[pymethod]
184        fn readable(&self) -> bool {
185            self.fd == 0
186        }
187
188        #[pymethod]
189        fn writable(&self) -> bool {
190            self.fd == 1 || self.fd == 2
191        }
192
193        #[pygetset]
194        fn closed(&self) -> bool {
195            false
196        }
197
198        #[pygetset]
199        fn encoding(&self) -> String {
200            "utf-8".to_owned()
201        }
202
203        #[pygetset]
204        fn errors(&self) -> String {
205            if self.fd == 2 {
206                "backslashreplace"
207            } else {
208                "strict"
209            }
210            .to_owned()
211        }
212
213        #[pygetset(name = "name")]
214        fn name_prop(&self) -> String {
215            self.name.clone()
216        }
217
218        #[pygetset(name = "mode")]
219        fn mode_prop(&self) -> String {
220            self.mode.clone()
221        }
222    }
223
224    #[pyattr(name = "_rustpython_debugbuild")]
225    const RUSTPYTHON_DEBUGBUILD: bool = cfg!(debug_assertions);
226
227    #[cfg(not(windows))]
228    #[pyattr(name = "abiflags")]
229    const ABIFLAGS_ATTR: &str = "t"; // 't' for free-threaded (no GIL)
230    // Internal constant used for sysconfigdata_name
231    pub const ABIFLAGS: &str = "t";
232    #[pyattr(name = "api_version")]
233    const API_VERSION: u32 = 0x0; // what C api?
234    #[pyattr(name = "copyright")]
235    const COPYRIGHT: &str = "Copyright (c) 2019 RustPython Team";
236    #[pyattr(name = "float_repr_style")]
237    const FLOAT_REPR_STYLE: &str = "short";
238    #[pyattr(name = "_framework")]
239    const FRAMEWORK: &str = "";
240    #[pyattr(name = "hexversion")]
241    const HEXVERSION: usize = version::VERSION_HEX;
242    #[pyattr(name = "maxsize")]
243    pub(crate) const MAXSIZE: isize = isize::MAX;
244    #[pyattr(name = "maxunicode")]
245    const MAXUNICODE: u32 = core::char::MAX as u32;
246    #[pyattr(name = "platform")]
247    pub const PLATFORM: &str = {
248        cfg_if::cfg_if! {
249            if #[cfg(target_os = "linux")] {
250                "linux"
251            } else if #[cfg(target_os = "android")] {
252                "android"
253            } else if #[cfg(target_os = "macos")] {
254                "darwin"
255            } else if #[cfg(target_os = "ios")] {
256                "ios"
257            } else if #[cfg(windows)] {
258                "win32"
259            } else if #[cfg(target_os = "wasi")] {
260                "wasi"
261            } else {
262                "unknown"
263            }
264        }
265    };
266    #[pyattr(name = "ps1")]
267    const PS1: &str = ">>>>> ";
268    #[pyattr(name = "ps2")]
269    const PS2: &str = "..... ";
270
271    #[cfg(windows)]
272    #[pyattr(name = "_vpath")]
273    const VPATH: Option<&'static str> = None; // TODO: actual VPATH value
274
275    #[cfg(windows)]
276    #[pyattr(name = "dllhandle")]
277    const DLLHANDLE: usize = 0;
278
279    #[pyattr]
280    fn prefix(vm: &VirtualMachine) -> String {
281        vm.state.config.paths.prefix.clone()
282    }
283    #[pyattr]
284    fn base_prefix(vm: &VirtualMachine) -> String {
285        vm.state.config.paths.base_prefix.clone()
286    }
287    #[pyattr]
288    fn exec_prefix(vm: &VirtualMachine) -> String {
289        vm.state.config.paths.exec_prefix.clone()
290    }
291    #[pyattr]
292    fn base_exec_prefix(vm: &VirtualMachine) -> String {
293        vm.state.config.paths.base_exec_prefix.clone()
294    }
295    #[pyattr]
296    fn platlibdir(_vm: &VirtualMachine) -> &'static str {
297        option_env!("RUSTPYTHON_PLATLIBDIR").unwrap_or("lib")
298    }
299    #[pyattr]
300    fn _stdlib_dir(vm: &VirtualMachine) -> PyObjectRef {
301        vm.state.config.paths.stdlib_dir.clone().to_pyobject(vm)
302    }
303
304    // alphabetical order with segments of pyattr and others
305
306    #[pyattr]
307    fn argv(vm: &VirtualMachine) -> Vec<PyObjectRef> {
308        vm.state
309            .config
310            .settings
311            .argv
312            .iter()
313            .map(|arg| vm.ctx.new_str(arg.clone()).into())
314            .collect()
315    }
316
317    #[pyattr]
318    fn builtin_module_names(vm: &VirtualMachine) -> PyTupleRef {
319        let mut module_names: Vec<String> =
320            vm.state.module_defs.keys().map(|&s| s.to_owned()).collect();
321        module_names.push("sys".to_owned());
322        module_names.push("builtins".to_owned());
323
324        module_names.sort();
325        vm.ctx.new_tuple(
326            module_names
327                .into_iter()
328                .map(|n| vm.ctx.new_str(n).into())
329                .collect(),
330        )
331    }
332
333    // List from cpython/Python/stdlib_module_names.h
334    const STDLIB_MODULE_NAMES: &[&str] = &[
335        "__future__",
336        "_abc",
337        "_aix_support",
338        "_android_support",
339        "_apple_support",
340        "_ast",
341        "_asyncio",
342        "_bisect",
343        "_blake2",
344        "_bz2",
345        "_codecs",
346        "_codecs_cn",
347        "_codecs_hk",
348        "_codecs_iso2022",
349        "_codecs_jp",
350        "_codecs_kr",
351        "_codecs_tw",
352        "_collections",
353        "_collections_abc",
354        "_colorize",
355        "_compat_pickle",
356        "_compression",
357        "_contextvars",
358        "_csv",
359        "_ctypes",
360        "_curses",
361        "_curses_panel",
362        "_datetime",
363        "_dbm",
364        "_decimal",
365        "_elementtree",
366        "_frozen_importlib",
367        "_frozen_importlib_external",
368        "_functools",
369        "_gdbm",
370        "_hashlib",
371        "_heapq",
372        "_imp",
373        "_interpchannels",
374        "_interpqueues",
375        "_interpreters",
376        "_io",
377        "_ios_support",
378        "_json",
379        "_locale",
380        "_lsprof",
381        "_lzma",
382        "_markupbase",
383        "_md5",
384        "_multibytecodec",
385        "_multiprocessing",
386        "_opcode",
387        "_opcode_metadata",
388        "_operator",
389        "_osx_support",
390        "_overlapped",
391        "_pickle",
392        "_posixshmem",
393        "_posixsubprocess",
394        "_py_abc",
395        "_pydatetime",
396        "_pydecimal",
397        "_pyio",
398        "_pylong",
399        "_pyrepl",
400        "_queue",
401        "_random",
402        "_scproxy",
403        "_sha1",
404        "_sha2",
405        "_sha3",
406        "_signal",
407        "_sitebuiltins",
408        "_socket",
409        "_sqlite3",
410        "_sre",
411        "_ssl",
412        "_stat",
413        "_statistics",
414        "_string",
415        "_strptime",
416        "_struct",
417        "_suggestions",
418        "_symtable",
419        "_sysconfig",
420        "_thread",
421        "_threading_local",
422        "_tkinter",
423        "_tokenize",
424        "_tracemalloc",
425        "_typing",
426        "_uuid",
427        "_warnings",
428        "_weakref",
429        "_weakrefset",
430        "_winapi",
431        "_wmi",
432        "_zoneinfo",
433        "abc",
434        "antigravity",
435        "argparse",
436        "array",
437        "ast",
438        "asyncio",
439        "atexit",
440        "base64",
441        "bdb",
442        "binascii",
443        "bisect",
444        "builtins",
445        "bz2",
446        "cProfile",
447        "calendar",
448        "cmath",
449        "cmd",
450        "code",
451        "codecs",
452        "codeop",
453        "collections",
454        "colorsys",
455        "compileall",
456        "concurrent",
457        "configparser",
458        "contextlib",
459        "contextvars",
460        "copy",
461        "copyreg",
462        "csv",
463        "ctypes",
464        "curses",
465        "dataclasses",
466        "datetime",
467        "dbm",
468        "decimal",
469        "difflib",
470        "dis",
471        "doctest",
472        "email",
473        "encodings",
474        "ensurepip",
475        "enum",
476        "errno",
477        "faulthandler",
478        "fcntl",
479        "filecmp",
480        "fileinput",
481        "fnmatch",
482        "fractions",
483        "ftplib",
484        "functools",
485        "gc",
486        "genericpath",
487        "getopt",
488        "getpass",
489        "gettext",
490        "glob",
491        "graphlib",
492        "grp",
493        "gzip",
494        "hashlib",
495        "heapq",
496        "hmac",
497        "html",
498        "http",
499        "idlelib",
500        "imaplib",
501        "importlib",
502        "inspect",
503        "io",
504        "ipaddress",
505        "itertools",
506        "json",
507        "keyword",
508        "linecache",
509        "locale",
510        "logging",
511        "lzma",
512        "mailbox",
513        "marshal",
514        "math",
515        "mimetypes",
516        "mmap",
517        "modulefinder",
518        "msvcrt",
519        "multiprocessing",
520        "netrc",
521        "nt",
522        "ntpath",
523        "nturl2path",
524        "numbers",
525        "opcode",
526        "operator",
527        "optparse",
528        "os",
529        "pathlib",
530        "pdb",
531        "pickle",
532        "pickletools",
533        "pkgutil",
534        "platform",
535        "plistlib",
536        "poplib",
537        "posix",
538        "posixpath",
539        "pprint",
540        "profile",
541        "pstats",
542        "pty",
543        "pwd",
544        "py_compile",
545        "pyclbr",
546        "pydoc",
547        "pydoc_data",
548        "pyexpat",
549        "queue",
550        "quopri",
551        "random",
552        "re",
553        "readline",
554        "reprlib",
555        "resource",
556        "rlcompleter",
557        "runpy",
558        "sched",
559        "secrets",
560        "select",
561        "selectors",
562        "shelve",
563        "shlex",
564        "shutil",
565        "signal",
566        "site",
567        "smtplib",
568        "socket",
569        "socketserver",
570        "sqlite3",
571        "sre_compile",
572        "sre_constants",
573        "sre_parse",
574        "ssl",
575        "stat",
576        "statistics",
577        "string",
578        "stringprep",
579        "struct",
580        "subprocess",
581        "symtable",
582        "sys",
583        "sysconfig",
584        "syslog",
585        "tabnanny",
586        "tarfile",
587        "tempfile",
588        "termios",
589        "textwrap",
590        "this",
591        "threading",
592        "time",
593        "timeit",
594        "tkinter",
595        "token",
596        "tokenize",
597        "tomllib",
598        "trace",
599        "traceback",
600        "tracemalloc",
601        "tty",
602        "turtle",
603        "turtledemo",
604        "types",
605        "typing",
606        "unicodedata",
607        "unittest",
608        "urllib",
609        "uuid",
610        "venv",
611        "warnings",
612        "wave",
613        "weakref",
614        "webbrowser",
615        "winreg",
616        "winsound",
617        "wsgiref",
618        "xml",
619        "xmlrpc",
620        "zipapp",
621        "zipfile",
622        "zipimport",
623        "zlib",
624        "zoneinfo",
625    ];
626
627    #[pyattr(once)]
628    fn stdlib_module_names(vm: &VirtualMachine) -> PyObjectRef {
629        let names = STDLIB_MODULE_NAMES
630            .iter()
631            .map(|&n| vm.ctx.new_str(n).into());
632        PyFrozenSet::from_iter(vm, names)
633            .expect("Creating stdlib_module_names frozen set must succeed")
634            .to_pyobject(vm)
635    }
636
637    #[pyattr]
638    fn byteorder(vm: &VirtualMachine) -> PyStrRef {
639        // https://doc.rust-lang.org/reference/conditional-compilation.html#target_endian
640        vm.ctx
641            .intern_str(if cfg!(target_endian = "little") {
642                "little"
643            } else if cfg!(target_endian = "big") {
644                "big"
645            } else {
646                "unknown"
647            })
648            .to_owned()
649    }
650
651    #[pyattr]
652    fn _base_executable(vm: &VirtualMachine) -> String {
653        vm.state.config.paths.base_executable.clone()
654    }
655
656    #[pyattr]
657    fn dont_write_bytecode(vm: &VirtualMachine) -> bool {
658        !vm.state.config.settings.write_bytecode
659    }
660
661    #[pyattr]
662    fn executable(vm: &VirtualMachine) -> String {
663        vm.state.config.paths.executable.clone()
664    }
665
666    #[pyattr]
667    fn _git(vm: &VirtualMachine) -> PyTupleRef {
668        vm.new_tuple((
669            ascii!("RustPython"),
670            version::get_git_identifier(),
671            version::get_git_revision(),
672        ))
673    }
674
675    #[pyattr]
676    fn implementation(vm: &VirtualMachine) -> PyRef<PyNamespace> {
677        const NAME: &str = "rustpython";
678
679        let cache_tag = format!("{NAME}-{}{}", version::MAJOR, version::MINOR);
680        let ctx = &vm.ctx;
681        py_namespace!(vm, {
682            "name" => ctx.new_str(NAME),
683            "cache_tag" => ctx.new_str(cache_tag),
684            "_multiarch" => ctx.new_str(multiarch()),
685            "version" => version_info(vm),
686            "hexversion" => ctx.new_int(version::VERSION_HEX),
687            "supports_isolated_interpreters" => ctx.new_bool(false),
688        })
689    }
690
691    #[pyattr]
692    const fn meta_path(_vm: &VirtualMachine) -> Vec<PyObjectRef> {
693        Vec::new()
694    }
695
696    #[pyattr]
697    fn orig_argv(vm: &VirtualMachine) -> Vec<PyObjectRef> {
698        env::args().map(|arg| vm.ctx.new_str(arg).into()).collect()
699    }
700
701    #[pyattr]
702    fn path(vm: &VirtualMachine) -> Vec<PyObjectRef> {
703        vm.state
704            .config
705            .paths
706            .module_search_paths
707            .iter()
708            .map(|path| vm.ctx.new_str(path.clone()).into())
709            .collect()
710    }
711
712    #[pyattr]
713    const fn path_hooks(_vm: &VirtualMachine) -> Vec<PyObjectRef> {
714        Vec::new()
715    }
716
717    #[pyattr]
718    fn path_importer_cache(vm: &VirtualMachine) -> PyDictRef {
719        vm.ctx.new_dict()
720    }
721
722    #[pyattr]
723    fn pycache_prefix(vm: &VirtualMachine) -> PyObjectRef {
724        vm.ctx.none()
725    }
726
727    #[pyattr]
728    fn version(_vm: &VirtualMachine) -> String {
729        version::get_version()
730    }
731
732    #[cfg(windows)]
733    #[pyattr]
734    fn winver(_vm: &VirtualMachine) -> String {
735        // Note: This is Python DLL version in CPython, but we arbitrary fill it for compatibility
736        version::get_winver_number()
737    }
738
739    #[pyattr]
740    fn _xoptions(vm: &VirtualMachine) -> PyDictRef {
741        let ctx = &vm.ctx;
742        let xopts = ctx.new_dict();
743        for (key, value) in &vm.state.config.settings.xoptions {
744            let value = value.as_ref().map_or_else(
745                || ctx.new_bool(true).into(),
746                |s| ctx.new_str(s.clone()).into(),
747            );
748            xopts.set_item(&**key, value, vm).unwrap();
749        }
750        xopts
751    }
752
753    #[pyattr]
754    fn warnoptions(vm: &VirtualMachine) -> Vec<PyObjectRef> {
755        vm.state
756            .config
757            .settings
758            .warnoptions
759            .iter()
760            .map(|s| vm.ctx.new_str(s.clone()).into())
761            .collect()
762    }
763
764    #[cfg(feature = "rustpython-compiler")]
765    #[pyfunction]
766    fn _baserepl(vm: &VirtualMachine) -> PyResult<()> {
767        // read stdin to end
768        let stdin = std::io::stdin();
769        let mut handle = stdin.lock();
770        let mut source = String::new();
771        handle
772            .read_to_string(&mut source)
773            .map_err(|e| vm.new_os_error(format!("Error reading from stdin: {e}")))?;
774        vm.compile(&source, crate::compiler::Mode::Single, "<stdin>".to_owned())
775            .map_err(|e| vm.new_os_error(format!("Error running stdin: {e}")))?;
776        Ok(())
777    }
778
779    #[pyfunction]
780    fn audit(_args: FuncArgs) {
781        // TODO: sys.audit implementation
782    }
783
784    #[pyfunction]
785    const fn _is_gil_enabled() -> bool {
786        false // RustPython has no GIL (like free-threaded Python)
787    }
788
789    /// Return True if remote debugging is enabled, False otherwise.
790    #[pyfunction]
791    const fn is_remote_debug_enabled() -> bool {
792        false // RustPython does not support remote debugging
793    }
794
795    #[pyfunction]
796    fn exit(code: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
797        let status = code.unwrap_or_none(vm);
798        let args = if let Some(status_tuple) = status.downcast_ref::<PyTuple>() {
799            status_tuple.as_slice().to_vec()
800        } else {
801            vec![status]
802        };
803        let exc = vm.invoke_exception(vm.ctx.exceptions.system_exit.to_owned(), args)?;
804        Err(exc)
805    }
806
807    #[pyfunction]
808    fn call_tracing(func: PyObjectRef, args: PyTupleRef, vm: &VirtualMachine) -> PyResult {
809        // CPython temporarily enables tracing state around this call.
810        // RustPython does not currently model the full C-level tracing toggles,
811        // but call semantics (func(*args)) are matched.
812        func.call(PosArgs::new(args.as_slice().to_vec()), vm)
813    }
814
815    #[pyfunction]
816    fn exception(vm: &VirtualMachine) -> Option<PyBaseExceptionRef> {
817        vm.topmost_exception()
818    }
819
820    #[pyfunction(name = "__displayhook__")]
821    #[pyfunction]
822    fn displayhook(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
823        // Save non-None values as "_"
824        if vm.is_none(&obj) {
825            return Ok(());
826        }
827        // set to none to avoid recursion while printing
828        vm.builtins.set_attr("_", vm.ctx.none(), vm)?;
829        // TODO: catch encoding errors
830        let repr = obj.repr(vm)?.into();
831        builtins::print(PosArgs::new(vec![repr]), Default::default(), vm)?;
832        vm.builtins.set_attr("_", obj, vm)?;
833        Ok(())
834    }
835
836    #[pyfunction(name = "__excepthook__")]
837    #[pyfunction]
838    fn excepthook(
839        exc_type: PyObjectRef,
840        exc_val: PyObjectRef,
841        exc_tb: PyObjectRef,
842        vm: &VirtualMachine,
843    ) -> PyResult<()> {
844        let stderr = super::get_stderr(vm)?;
845        match vm.normalize_exception(exc_type.clone(), exc_val.clone(), exc_tb) {
846            Ok(exc) => {
847                // PyErr_Display: try traceback._print_exception_bltin first
848                if let Ok(tb_mod) = vm.import("traceback", 0)
849                    && let Ok(print_exc_builtin) = tb_mod.get_attr("_print_exception_bltin", vm)
850                    && print_exc_builtin
851                        .call((exc.as_object().to_owned(),), vm)
852                        .is_ok()
853                {
854                    return Ok(());
855                }
856                // Fallback to Rust-level exception printing
857                vm.write_exception(&mut crate::py_io::PyWriter(stderr, vm), &exc)
858            }
859            Err(_) => {
860                let type_name = exc_val.class().name();
861                let msg = format!(
862                    "TypeError: print_exception(): Exception expected for value, {type_name} found\n"
863                );
864                use crate::py_io::Write;
865                write!(&mut crate::py_io::PyWriter(stderr, vm), "{msg}")?;
866                Ok(())
867            }
868        }
869    }
870
871    #[pyfunction(name = "__breakpointhook__")]
872    #[pyfunction]
873    pub fn breakpointhook(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
874        let env_var = std::env::var("PYTHONBREAKPOINT")
875            .and_then(|env_var| {
876                if env_var.is_empty() {
877                    Err(VarError::NotPresent)
878                } else {
879                    Ok(env_var)
880                }
881            })
882            .unwrap_or_else(|_| "pdb.set_trace".to_owned());
883
884        if env_var.eq("0") {
885            return Ok(vm.ctx.none());
886        };
887
888        let print_unimportable_module_warn = || {
889            warn(
890                vm.ctx.exceptions.runtime_warning,
891                format!("Ignoring unimportable $PYTHONBREAKPOINT: \"{env_var}\"",),
892                0,
893                vm,
894            )
895            .unwrap();
896            Ok(vm.ctx.none())
897        };
898
899        let last = match env_var.rsplit_once('.') {
900            Some((_, last)) => last,
901            None if !env_var.is_empty() => env_var.as_str(),
902            _ => return print_unimportable_module_warn(),
903        };
904
905        let (module_path, attr_name) = if last == env_var {
906            ("builtins", env_var.as_str())
907        } else {
908            (&env_var[..(env_var.len() - last.len() - 1)], last)
909        };
910
911        let module = match vm.import(&vm.ctx.new_str(module_path), 0) {
912            Ok(module) => module,
913            Err(_) => {
914                return print_unimportable_module_warn();
915            }
916        };
917
918        match vm.get_attribute_opt(module, &vm.ctx.new_str(attr_name)) {
919            Ok(Some(hook)) => hook.as_ref().call(args, vm),
920            _ => print_unimportable_module_warn(),
921        }
922    }
923
924    #[pyfunction]
925    fn exc_info(vm: &VirtualMachine) -> (PyObjectRef, PyObjectRef, PyObjectRef) {
926        match vm.topmost_exception() {
927            Some(exception) => vm.split_exception(exception),
928            None => (vm.ctx.none(), vm.ctx.none(), vm.ctx.none()),
929        }
930    }
931
932    #[pyattr]
933    fn flags(vm: &VirtualMachine) -> PyTupleRef {
934        PyFlags::from_data(FlagsData::from_settings(&vm.state.config.settings), vm)
935    }
936
937    #[pyattr]
938    fn float_info(vm: &VirtualMachine) -> PyTupleRef {
939        PyFloatInfo::from_data(FloatInfoData::INFO, vm)
940    }
941
942    #[pyfunction]
943    const fn getdefaultencoding() -> &'static str {
944        crate::codecs::DEFAULT_ENCODING
945    }
946
947    #[pyfunction]
948    fn getrefcount(obj: PyObjectRef) -> usize {
949        obj.strong_count()
950    }
951
952    #[pyfunction]
953    fn getrecursionlimit(vm: &VirtualMachine) -> usize {
954        vm.recursion_limit.get()
955    }
956
957    #[derive(FromArgs)]
958    struct GetsizeofArgs {
959        obj: PyObjectRef,
960        #[pyarg(any, optional)]
961        default: Option<PyObjectRef>,
962    }
963
964    #[pyfunction]
965    fn getsizeof(args: GetsizeofArgs, vm: &VirtualMachine) -> PyResult {
966        let sizeof = || -> PyResult<usize> {
967            let res = vm.call_special_method(&args.obj, identifier!(vm, __sizeof__), ())?;
968            let res = res.try_index(vm)?.try_to_primitive::<usize>(vm)?;
969            Ok(res + core::mem::size_of::<PyObject>())
970        };
971        sizeof()
972            .map(|x| vm.ctx.new_int(x).into())
973            .or_else(|err| args.default.ok_or(err))
974    }
975
976    #[pyfunction]
977    fn getfilesystemencoding(vm: &VirtualMachine) -> PyStrRef {
978        vm.fs_encoding().to_owned()
979    }
980
981    #[pyfunction]
982    fn getfilesystemencodeerrors(vm: &VirtualMachine) -> PyUtf8StrRef {
983        vm.fs_encode_errors().to_owned()
984    }
985
986    #[pyfunction]
987    fn getprofile(vm: &VirtualMachine) -> PyObjectRef {
988        vm.profile_func.borrow().clone()
989    }
990
991    #[pyfunction]
992    fn _getframe(offset: OptionalArg<usize>, vm: &VirtualMachine) -> PyResult<FrameRef> {
993        let offset = offset.into_option().unwrap_or(0);
994        let frames = vm.frames.borrow();
995        if offset >= frames.len() {
996            return Err(vm.new_value_error("call stack is not deep enough"));
997        }
998        let idx = frames.len() - offset - 1;
999        // SAFETY: the FrameRef is alive on the call stack while it's in the Vec
1000        let py: &crate::Py<Frame> = unsafe { frames[idx].as_ref() };
1001        Ok(py.to_owned())
1002    }
1003
1004    #[pyfunction]
1005    fn _getframemodulename(depth: OptionalArg<usize>, vm: &VirtualMachine) -> PyResult {
1006        let depth = depth.into_option().unwrap_or(0);
1007
1008        // Get the frame at the specified depth
1009        let func_obj = {
1010            let frames = vm.frames.borrow();
1011            if depth >= frames.len() {
1012                return Ok(vm.ctx.none());
1013            }
1014            let idx = frames.len() - depth - 1;
1015            // SAFETY: the FrameRef is alive on the call stack while it's in the Vec
1016            let frame: &crate::Py<Frame> = unsafe { frames[idx].as_ref() };
1017            frame.func_obj.clone()
1018        };
1019
1020        // If the frame has a function object, return its __module__ attribute
1021        if let Some(func_obj) = func_obj {
1022            match func_obj.get_attr(identifier!(vm, __module__), vm) {
1023                Ok(module) => Ok(module),
1024                Err(_) => {
1025                    // CPython clears the error and returns None
1026                    Ok(vm.ctx.none())
1027                }
1028            }
1029        } else {
1030            Ok(vm.ctx.none())
1031        }
1032    }
1033
1034    /// Return a dictionary mapping each thread's identifier to the topmost stack frame
1035    /// currently active in that thread at the time the function is called.
1036    #[cfg(feature = "threading")]
1037    #[pyfunction]
1038    fn _current_frames(vm: &VirtualMachine) -> PyResult<PyDictRef> {
1039        use crate::AsObject;
1040        use crate::stdlib::_thread::get_all_current_frames;
1041
1042        let frames = get_all_current_frames(vm);
1043        let dict = vm.ctx.new_dict();
1044
1045        for (thread_id, frame) in frames {
1046            let key = vm.ctx.new_int(thread_id);
1047            dict.set_item(key.as_object(), frame.into(), vm)?;
1048        }
1049
1050        Ok(dict)
1051    }
1052
1053    /// Return a dictionary mapping each thread's identifier to its currently
1054    /// active exception, or None if no exception is active.
1055    #[cfg(feature = "threading")]
1056    #[pyfunction]
1057    fn _current_exceptions(vm: &VirtualMachine) -> PyResult<PyDictRef> {
1058        use crate::AsObject;
1059        use crate::vm::thread::get_all_current_exceptions;
1060
1061        let dict = vm.ctx.new_dict();
1062        for (thread_id, exc) in get_all_current_exceptions(vm) {
1063            let key = vm.ctx.new_int(thread_id);
1064            let value = exc.map_or_else(|| vm.ctx.none(), |e| e.into());
1065            dict.set_item(key.as_object(), value, vm)?;
1066        }
1067
1068        Ok(dict)
1069    }
1070
1071    #[cfg(not(feature = "threading"))]
1072    #[pyfunction]
1073    fn _current_exceptions(vm: &VirtualMachine) -> PyResult<PyDictRef> {
1074        let dict = vm.ctx.new_dict();
1075        let key = vm.ctx.new_int(0);
1076        dict.set_item(key.as_object(), vm.topmost_exception().to_pyobject(vm), vm)?;
1077        Ok(dict)
1078    }
1079
1080    /// Stub for non-threading builds - returns empty dict
1081    #[cfg(not(feature = "threading"))]
1082    #[pyfunction]
1083    fn _current_frames(vm: &VirtualMachine) -> PyResult<PyDictRef> {
1084        Ok(vm.ctx.new_dict())
1085    }
1086
1087    #[pyfunction]
1088    fn gettrace(vm: &VirtualMachine) -> PyObjectRef {
1089        vm.trace_func.borrow().clone()
1090    }
1091
1092    #[cfg(windows)]
1093    fn get_kernel32_version() -> std::io::Result<(u32, u32, u32)> {
1094        use crate::common::windows::ToWideString;
1095        unsafe {
1096            // Create a wide string for "kernel32.dll"
1097            let module_name: Vec<u16> = std::ffi::OsStr::new("kernel32.dll").to_wide_with_nul();
1098            let h_kernel32 = GetModuleHandleW(module_name.as_ptr());
1099            if h_kernel32.is_null() {
1100                return Err(std::io::Error::last_os_error());
1101            }
1102
1103            // Prepare a buffer for the module file path
1104            let mut kernel32_path = [0u16; MAX_PATH as usize];
1105            let len = GetModuleFileNameW(
1106                h_kernel32,
1107                kernel32_path.as_mut_ptr(),
1108                kernel32_path.len() as u32,
1109            );
1110            if len == 0 {
1111                return Err(std::io::Error::last_os_error());
1112            }
1113
1114            // Get the size of the version information block
1115            let ver_block_size =
1116                GetFileVersionInfoSizeW(kernel32_path.as_ptr(), core::ptr::null_mut());
1117            if ver_block_size == 0 {
1118                return Err(std::io::Error::last_os_error());
1119            }
1120
1121            // Allocate a buffer to hold the version information
1122            let mut ver_block = vec![0u8; ver_block_size as usize];
1123            if GetFileVersionInfoW(
1124                kernel32_path.as_ptr(),
1125                0,
1126                ver_block_size,
1127                ver_block.as_mut_ptr() as *mut _,
1128            ) == 0
1129            {
1130                return Err(std::io::Error::last_os_error());
1131            }
1132
1133            // Prepare an empty sub-block string (L"") as required by VerQueryValueW
1134            let sub_block: Vec<u16> = std::ffi::OsStr::new("").to_wide_with_nul();
1135
1136            let mut ffi_ptr: *mut VS_FIXEDFILEINFO = core::ptr::null_mut();
1137            let mut ffi_len: u32 = 0;
1138            if VerQueryValueW(
1139                ver_block.as_ptr() as *const _,
1140                sub_block.as_ptr(),
1141                &mut ffi_ptr as *mut *mut VS_FIXEDFILEINFO as *mut *mut _,
1142                &mut ffi_len as *mut u32,
1143            ) == 0
1144                || ffi_ptr.is_null()
1145            {
1146                return Err(std::io::Error::last_os_error());
1147            }
1148
1149            // Extract the version numbers from the VS_FIXEDFILEINFO structure.
1150            let ffi = *ffi_ptr;
1151            let real_major = (ffi.dwProductVersionMS >> 16) & 0xFFFF;
1152            let real_minor = ffi.dwProductVersionMS & 0xFFFF;
1153            let real_build = (ffi.dwProductVersionLS >> 16) & 0xFFFF;
1154
1155            Ok((real_major, real_minor, real_build))
1156        }
1157    }
1158
1159    #[cfg(windows)]
1160    #[pyfunction]
1161    fn getwindowsversion(vm: &VirtualMachine) -> PyResult<crate::builtins::tuple::PyTupleRef> {
1162        use std::ffi::OsString;
1163        use std::os::windows::ffi::OsStringExt;
1164        use windows_sys::Win32::System::SystemInformation::{
1165            GetVersionExW, OSVERSIONINFOEXW, OSVERSIONINFOW,
1166        };
1167
1168        let mut version: OSVERSIONINFOEXW = unsafe { core::mem::zeroed() };
1169        version.dwOSVersionInfoSize = core::mem::size_of::<OSVERSIONINFOEXW>() as u32;
1170        let result = unsafe {
1171            let os_vi = &mut version as *mut OSVERSIONINFOEXW as *mut OSVERSIONINFOW;
1172            // SAFETY: GetVersionExW accepts a pointer of OSVERSIONINFOW, but windows-sys crate's type currently doesn't allow to do so.
1173            // https://docs.microsoft.com/en-us/windows/win32/api/sysinfoapi/nf-sysinfoapi-getversionexw#parameters
1174            GetVersionExW(os_vi)
1175        };
1176
1177        if result == 0 {
1178            return Err(vm.new_os_error("failed to get windows version".to_owned()));
1179        }
1180
1181        let service_pack = {
1182            let (last, _) = version
1183                .szCSDVersion
1184                .iter()
1185                .take_while(|&x| x != &0)
1186                .enumerate()
1187                .last()
1188                .unwrap_or((0, &0));
1189            let sp = OsString::from_wide(&version.szCSDVersion[..last]);
1190            sp.into_string()
1191                .map_err(|_| vm.new_os_error("service pack is not ASCII".to_owned()))?
1192        };
1193        let real_version = get_kernel32_version().map_err(|e| vm.new_os_error(e.to_string()))?;
1194        let winver = WindowsVersionData {
1195            major: real_version.0,
1196            minor: real_version.1,
1197            build: real_version.2,
1198            platform: version.dwPlatformId,
1199            service_pack,
1200            service_pack_major: version.wServicePackMajor,
1201            service_pack_minor: version.wServicePackMinor,
1202            suite_mask: version.wSuiteMask,
1203            product_type: version.wProductType,
1204            platform_version: (real_version.0, real_version.1, real_version.2), // TODO Provide accurate version, like CPython impl
1205        };
1206        Ok(PyWindowsVersion::from_data(winver, vm))
1207    }
1208
1209    fn _unraisablehook(unraisable: UnraisableHookArgsData, vm: &VirtualMachine) -> PyResult<()> {
1210        use super::PyStderr;
1211
1212        let stderr = PyStderr(vm);
1213        if !vm.is_none(&unraisable.object) {
1214            if !vm.is_none(&unraisable.err_msg) {
1215                write!(stderr, "{}: ", unraisable.err_msg.str(vm)?);
1216            } else {
1217                write!(stderr, "Exception ignored in: ");
1218            }
1219            // exception in del will be ignored but printed
1220            let repr = &unraisable.object.repr(vm);
1221            let str = match repr {
1222                Ok(v) => v.to_string(),
1223                Err(_) => format!(
1224                    "<object {} repr() failed>",
1225                    unraisable.object.class().name()
1226                ),
1227            };
1228            writeln!(stderr, "{str}");
1229        } else if !vm.is_none(&unraisable.err_msg) {
1230            writeln!(stderr, "{}:", unraisable.err_msg.str(vm)?);
1231        }
1232
1233        // Print traceback (using actual exc_traceback, not current stack)
1234        if !vm.is_none(&unraisable.exc_traceback) {
1235            let tb_module = vm.import("traceback", 0)?;
1236            let print_tb = tb_module.get_attr("print_tb", vm)?;
1237            let stderr_obj = super::get_stderr(vm)?;
1238            let kwargs: KwArgs = [("file".to_string(), stderr_obj)].into_iter().collect();
1239            let _ = print_tb.call(
1240                FuncArgs::new(vec![unraisable.exc_traceback.clone()], kwargs),
1241                vm,
1242            );
1243        }
1244
1245        // Check exc_type
1246        if vm.is_none(unraisable.exc_type.as_object()) {
1247            return Ok(());
1248        }
1249        assert!(
1250            unraisable
1251                .exc_type
1252                .fast_issubclass(vm.ctx.exceptions.base_exception_type)
1253        );
1254
1255        // Print module name (if not builtins or __main__)
1256        let module_name = unraisable.exc_type.__module__(vm);
1257        if let Ok(module_str) = module_name.downcast::<PyStr>() {
1258            let module = module_str.as_wtf8();
1259            if module != "builtins" && module != "__main__" {
1260                write!(stderr, "{}.", module);
1261            }
1262        } else {
1263            write!(stderr, "<unknown>.");
1264        }
1265
1266        // Print qualname
1267        let qualname = unraisable.exc_type.__qualname__(vm);
1268        if let Ok(qualname_str) = qualname.downcast::<PyStr>() {
1269            write!(stderr, "{}", qualname_str.as_wtf8());
1270        } else {
1271            write!(stderr, "{}", unraisable.exc_type.name());
1272        }
1273
1274        // Print exception value
1275        if !vm.is_none(&unraisable.exc_value) {
1276            write!(stderr, ": ");
1277            if let Ok(str) = unraisable.exc_value.str(vm) {
1278                write!(stderr, "{}", str.as_wtf8());
1279            } else {
1280                write!(stderr, "<exception str() failed>");
1281            }
1282        }
1283        writeln!(stderr);
1284
1285        // Flush stderr
1286        if let Ok(stderr_obj) = super::get_stderr(vm)
1287            && let Ok(flush) = stderr_obj.get_attr("flush", vm)
1288        {
1289            let _ = flush.call((), vm);
1290        }
1291
1292        Ok(())
1293    }
1294
1295    #[pyattr]
1296    #[pyfunction(name = "__unraisablehook__")]
1297    fn unraisablehook(unraisable: UnraisableHookArgsData, vm: &VirtualMachine) {
1298        if let Err(e) = _unraisablehook(unraisable, vm) {
1299            let stderr = super::PyStderr(vm);
1300            writeln!(
1301                stderr,
1302                "{}",
1303                e.as_object()
1304                    .repr(vm)
1305                    .unwrap_or_else(|_| vm.ctx.empty_str.to_owned())
1306            );
1307        }
1308    }
1309
1310    #[pyattr]
1311    fn hash_info(vm: &VirtualMachine) -> PyTupleRef {
1312        PyHashInfo::from_data(HashInfoData::INFO, vm)
1313    }
1314
1315    #[pyfunction]
1316    fn intern(s: PyRefExact<PyStr>, vm: &VirtualMachine) -> PyRef<PyStr> {
1317        vm.ctx.intern_str(s).to_owned()
1318    }
1319
1320    #[pyattr]
1321    fn int_info(vm: &VirtualMachine) -> PyTupleRef {
1322        PyIntInfo::from_data(IntInfoData::INFO, vm)
1323    }
1324
1325    #[pyfunction]
1326    fn get_int_max_str_digits(vm: &VirtualMachine) -> usize {
1327        vm.state.int_max_str_digits.load()
1328    }
1329
1330    #[pyfunction]
1331    fn set_int_max_str_digits(maxdigits: usize, vm: &VirtualMachine) -> PyResult<()> {
1332        let threshold = IntInfoData::INFO.str_digits_check_threshold;
1333        if maxdigits == 0 || maxdigits >= threshold {
1334            vm.state.int_max_str_digits.store(maxdigits);
1335            Ok(())
1336        } else {
1337            let error = format!("maxdigits must be 0 or larger than {threshold:?}");
1338            Err(vm.new_value_error(error))
1339        }
1340    }
1341
1342    #[pyfunction]
1343    fn is_finalizing(vm: &VirtualMachine) -> bool {
1344        vm.state.finalizing.load(Ordering::Acquire)
1345    }
1346
1347    #[pyfunction]
1348    fn setprofile(profilefunc: PyObjectRef, vm: &VirtualMachine) {
1349        vm.profile_func.replace(profilefunc);
1350        update_use_tracing(vm);
1351    }
1352
1353    #[pyfunction]
1354    fn setrecursionlimit(recursion_limit: i32, vm: &VirtualMachine) -> PyResult<()> {
1355        let recursion_limit = recursion_limit
1356            .to_usize()
1357            .filter(|&u| u >= 1)
1358            .ok_or_else(|| {
1359                vm.new_value_error("recursion limit must be greater than or equal to one")
1360            })?;
1361        let recursion_depth = vm.current_recursion_depth();
1362
1363        if recursion_limit > recursion_depth {
1364            vm.recursion_limit.set(recursion_limit);
1365            Ok(())
1366        } else {
1367            Err(vm.new_recursion_error(format!(
1368                "cannot set the recursion limit to {recursion_limit} at the recursion depth {recursion_depth}: the limit is too low"
1369            )))
1370        }
1371    }
1372
1373    #[pyfunction]
1374    fn settrace(tracefunc: PyObjectRef, vm: &VirtualMachine) {
1375        vm.trace_func.replace(tracefunc);
1376        update_use_tracing(vm);
1377    }
1378
1379    #[pyfunction]
1380    fn _settraceallthreads(tracefunc: PyObjectRef, vm: &VirtualMachine) {
1381        let func = (!vm.is_none(&tracefunc)).then(|| tracefunc.clone());
1382        *vm.state.global_trace_func.lock() = func;
1383        vm.trace_func.replace(tracefunc);
1384        update_use_tracing(vm);
1385    }
1386
1387    #[pyfunction]
1388    fn _setprofileallthreads(profilefunc: PyObjectRef, vm: &VirtualMachine) {
1389        let func = (!vm.is_none(&profilefunc)).then(|| profilefunc.clone());
1390        *vm.state.global_profile_func.lock() = func;
1391        vm.profile_func.replace(profilefunc);
1392        update_use_tracing(vm);
1393    }
1394
1395    #[cfg(feature = "threading")]
1396    #[pyattr]
1397    fn thread_info(vm: &VirtualMachine) -> PyTupleRef {
1398        PyThreadInfo::from_data(ThreadInfoData::INFO, vm)
1399    }
1400
1401    #[pyattr]
1402    fn version_info(vm: &VirtualMachine) -> PyTupleRef {
1403        PyVersionInfo::from_data(VersionInfoData::VERSION, vm)
1404    }
1405
1406    fn update_use_tracing(vm: &VirtualMachine) {
1407        let trace_is_none = vm.is_none(&vm.trace_func.borrow());
1408        let profile_is_none = vm.is_none(&vm.profile_func.borrow());
1409        let tracing = !(trace_is_none && profile_is_none);
1410        vm.use_tracing.set(tracing);
1411    }
1412
1413    #[pyfunction]
1414    fn set_coroutine_origin_tracking_depth(depth: i32, vm: &VirtualMachine) -> PyResult<()> {
1415        if depth < 0 {
1416            return Err(vm.new_value_error("depth must be >= 0"));
1417        }
1418        crate::vm::thread::COROUTINE_ORIGIN_TRACKING_DEPTH.set(depth as u32);
1419        Ok(())
1420    }
1421
1422    #[pyfunction]
1423    fn get_coroutine_origin_tracking_depth() -> i32 {
1424        crate::vm::thread::COROUTINE_ORIGIN_TRACKING_DEPTH.get() as i32
1425    }
1426
1427    #[pyfunction]
1428    fn _clear_type_descriptors(type_obj: PyTypeRef, vm: &VirtualMachine) -> PyResult<()> {
1429        use crate::types::PyTypeFlags;
1430
1431        // Check if type is immutable
1432        if type_obj.slots.flags.has_feature(PyTypeFlags::IMMUTABLETYPE) {
1433            return Err(vm.new_type_error("argument is immutable"));
1434        }
1435
1436        let mut attributes = type_obj.attributes.write();
1437
1438        // Remove __dict__ descriptor if present
1439        attributes.swap_remove(identifier!(vm, __dict__));
1440
1441        // Remove __weakref__ descriptor if present
1442        attributes.swap_remove(identifier!(vm, __weakref__));
1443
1444        drop(attributes);
1445
1446        // Update slots to notify subclasses and recalculate cached values
1447        type_obj.update_slot::<true>(identifier!(vm, __dict__), &vm.ctx);
1448        type_obj.update_slot::<true>(identifier!(vm, __weakref__), &vm.ctx);
1449
1450        Ok(())
1451    }
1452
1453    #[pyfunction]
1454    fn getswitchinterval(vm: &VirtualMachine) -> f64 {
1455        // Return the stored switch interval
1456        vm.state.switch_interval.load()
1457    }
1458
1459    // TODO: vm.state.switch_interval is currently not used anywhere in the VM
1460    #[pyfunction]
1461    fn setswitchinterval(interval: f64, vm: &VirtualMachine) -> PyResult<()> {
1462        // Validate the interval parameter like CPython does
1463        if interval <= 0.0 {
1464            return Err(vm.new_value_error("switch interval must be strictly positive"));
1465        }
1466
1467        // Store the switch interval value
1468        vm.state.switch_interval.store(interval);
1469        Ok(())
1470    }
1471
1472    #[derive(FromArgs)]
1473    struct SetAsyncgenHooksArgs {
1474        #[pyarg(any, optional)]
1475        firstiter: OptionalArg<Option<PyObjectRef>>,
1476        #[pyarg(any, optional)]
1477        finalizer: OptionalArg<Option<PyObjectRef>>,
1478    }
1479
1480    #[pyfunction]
1481    fn set_asyncgen_hooks(args: SetAsyncgenHooksArgs, vm: &VirtualMachine) -> PyResult<()> {
1482        if let Some(Some(finalizer)) = args.finalizer.as_option()
1483            && !finalizer.is_callable()
1484        {
1485            return Err(vm.new_type_error(format!(
1486                "callable finalizer expected, got {:.50}",
1487                finalizer.class().name()
1488            )));
1489        }
1490
1491        if let Some(Some(firstiter)) = args.firstiter.as_option()
1492            && !firstiter.is_callable()
1493        {
1494            return Err(vm.new_type_error(format!(
1495                "callable firstiter expected, got {:.50}",
1496                firstiter.class().name()
1497            )));
1498        }
1499
1500        if let Some(finalizer) = args.finalizer.into_option() {
1501            *vm.async_gen_finalizer.borrow_mut() = finalizer;
1502        }
1503        if let Some(firstiter) = args.firstiter.into_option() {
1504            *vm.async_gen_firstiter.borrow_mut() = firstiter;
1505        }
1506
1507        Ok(())
1508    }
1509
1510    #[pystruct_sequence_data]
1511    pub(super) struct AsyncgenHooksData {
1512        firstiter: PyObjectRef,
1513        finalizer: PyObjectRef,
1514    }
1515
1516    #[pyattr]
1517    #[pystruct_sequence(name = "asyncgen_hooks", data = "AsyncgenHooksData")]
1518    pub(super) struct PyAsyncgenHooks;
1519
1520    #[pyclass(with(PyStructSequence))]
1521    impl PyAsyncgenHooks {}
1522
1523    #[pyfunction]
1524    fn get_asyncgen_hooks(vm: &VirtualMachine) -> AsyncgenHooksData {
1525        AsyncgenHooksData {
1526            firstiter: vm.async_gen_firstiter.borrow().clone().to_pyobject(vm),
1527            finalizer: vm.async_gen_finalizer.borrow().clone().to_pyobject(vm),
1528        }
1529    }
1530
1531    /// sys.flags
1532    ///
1533    /// Flags provided through command line arguments or environment vars.
1534    #[derive(Debug)]
1535    #[pystruct_sequence_data]
1536    pub(super) struct FlagsData {
1537        /// -d
1538        debug: u8,
1539        /// -i
1540        inspect: u8,
1541        /// -i
1542        interactive: u8,
1543        /// -O or -OO
1544        optimize: u8,
1545        /// -B
1546        dont_write_bytecode: u8,
1547        /// -s
1548        no_user_site: u8,
1549        /// -S
1550        no_site: u8,
1551        /// -E
1552        ignore_environment: u8,
1553        /// -v
1554        verbose: u8,
1555        /// -b
1556        bytes_warning: u64,
1557        /// -q
1558        quiet: u8,
1559        /// -R
1560        hash_randomization: u8,
1561        /// -I
1562        isolated: u8,
1563        /// -X dev
1564        dev_mode: bool,
1565        /// -X utf8
1566        utf8_mode: u8,
1567        /// -X int_max_str_digits=number
1568        int_max_str_digits: i64,
1569        /// -P, `PYTHONSAFEPATH`
1570        safe_path: bool,
1571        /// -X warn_default_encoding, PYTHONWARNDEFAULTENCODING
1572        warn_default_encoding: u8,
1573    }
1574
1575    impl FlagsData {
1576        const fn from_settings(settings: &Settings) -> Self {
1577            Self {
1578                debug: settings.debug,
1579                inspect: settings.inspect as u8,
1580                interactive: settings.interactive as u8,
1581                optimize: settings.optimize,
1582                dont_write_bytecode: (!settings.write_bytecode) as u8,
1583                no_user_site: (!settings.user_site_directory) as u8,
1584                no_site: (!settings.import_site) as u8,
1585                ignore_environment: settings.ignore_environment as u8,
1586                verbose: settings.verbose,
1587                bytes_warning: settings.bytes_warning,
1588                quiet: settings.quiet as u8,
1589                hash_randomization: settings.hash_seed.is_none() as u8,
1590                isolated: settings.isolated as u8,
1591                dev_mode: settings.dev_mode,
1592                utf8_mode: if settings.utf8_mode < 0 {
1593                    1
1594                } else {
1595                    settings.utf8_mode as u8
1596                },
1597                int_max_str_digits: settings.int_max_str_digits,
1598                safe_path: settings.safe_path,
1599                warn_default_encoding: settings.warn_default_encoding as u8,
1600            }
1601        }
1602    }
1603
1604    #[pystruct_sequence(name = "flags", data = "FlagsData", no_attr)]
1605    pub(super) struct PyFlags;
1606
1607    #[pyclass(with(PyStructSequence))]
1608    impl PyFlags {
1609        #[pyslot]
1610        fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1611            Err(vm.new_type_error("cannot create 'sys.flags' instances"))
1612        }
1613
1614        #[pygetset]
1615        fn context_aware_warnings(&self, vm: &VirtualMachine) -> bool {
1616            vm.state.config.settings.context_aware_warnings
1617        }
1618
1619        #[pygetset]
1620        fn thread_inherit_context(&self, vm: &VirtualMachine) -> bool {
1621            vm.state.config.settings.thread_inherit_context
1622        }
1623    }
1624
1625    #[cfg(feature = "threading")]
1626    #[pystruct_sequence_data]
1627    pub(super) struct ThreadInfoData {
1628        name: Option<&'static str>,
1629        lock: Option<&'static str>,
1630        version: Option<&'static str>,
1631    }
1632
1633    #[cfg(feature = "threading")]
1634    impl ThreadInfoData {
1635        const INFO: Self = Self {
1636            name: crate::stdlib::_thread::_thread::PYTHREAD_NAME,
1637            // As I know, there's only way to use lock as "Mutex" in Rust
1638            // with satisfying python document spec.
1639            lock: Some("mutex+cond"),
1640            version: None,
1641        };
1642    }
1643
1644    #[cfg(feature = "threading")]
1645    #[pystruct_sequence(name = "thread_info", data = "ThreadInfoData", no_attr)]
1646    pub(super) struct PyThreadInfo;
1647
1648    #[cfg(feature = "threading")]
1649    #[pyclass(with(PyStructSequence))]
1650    impl PyThreadInfo {}
1651
1652    #[pystruct_sequence_data]
1653    pub(super) struct FloatInfoData {
1654        max: f64,
1655        max_exp: i32,
1656        max_10_exp: i32,
1657        min: f64,
1658        min_exp: i32,
1659        min_10_exp: i32,
1660        dig: u32,
1661        mant_dig: u32,
1662        epsilon: f64,
1663        radix: u32,
1664        rounds: i32,
1665    }
1666
1667    impl FloatInfoData {
1668        const INFO: Self = Self {
1669            max: f64::MAX,
1670            max_exp: f64::MAX_EXP,
1671            max_10_exp: f64::MAX_10_EXP,
1672            min: f64::MIN_POSITIVE,
1673            min_exp: f64::MIN_EXP,
1674            min_10_exp: f64::MIN_10_EXP,
1675            dig: f64::DIGITS,
1676            mant_dig: f64::MANTISSA_DIGITS,
1677            epsilon: f64::EPSILON,
1678            radix: f64::RADIX,
1679            rounds: 1, // FE_TONEAREST
1680        };
1681    }
1682
1683    #[pystruct_sequence(name = "float_info", data = "FloatInfoData", no_attr)]
1684    pub(super) struct PyFloatInfo;
1685
1686    #[pyclass(with(PyStructSequence))]
1687    impl PyFloatInfo {}
1688
1689    #[pystruct_sequence_data]
1690    pub(super) struct HashInfoData {
1691        width: usize,
1692        modulus: PyUHash,
1693        inf: PyHash,
1694        nan: PyHash,
1695        imag: PyHash,
1696        algorithm: &'static str,
1697        hash_bits: usize,
1698        seed_bits: usize,
1699        cutoff: usize,
1700    }
1701
1702    impl HashInfoData {
1703        const INFO: Self = {
1704            use rustpython_common::hash::*;
1705            Self {
1706                width: core::mem::size_of::<PyHash>() * 8,
1707                modulus: MODULUS,
1708                inf: INF,
1709                nan: NAN,
1710                imag: IMAG,
1711                algorithm: ALGO,
1712                hash_bits: HASH_BITS,
1713                seed_bits: SEED_BITS,
1714                cutoff: 0, // no small string optimizations
1715            }
1716        };
1717    }
1718
1719    #[pystruct_sequence(name = "hash_info", data = "HashInfoData", no_attr)]
1720    pub(super) struct PyHashInfo;
1721
1722    #[pyclass(with(PyStructSequence))]
1723    impl PyHashInfo {}
1724
1725    #[pystruct_sequence_data]
1726    pub(super) struct IntInfoData {
1727        bits_per_digit: usize,
1728        sizeof_digit: usize,
1729        default_max_str_digits: usize,
1730        str_digits_check_threshold: usize,
1731    }
1732
1733    impl IntInfoData {
1734        const INFO: Self = Self {
1735            bits_per_digit: 30, //?
1736            sizeof_digit: core::mem::size_of::<u32>(),
1737            default_max_str_digits: 4300,
1738            str_digits_check_threshold: 640,
1739        };
1740    }
1741
1742    #[pystruct_sequence(name = "int_info", data = "IntInfoData", no_attr)]
1743    pub(super) struct PyIntInfo;
1744
1745    #[pyclass(with(PyStructSequence))]
1746    impl PyIntInfo {}
1747
1748    #[derive(Default, Debug)]
1749    #[pystruct_sequence_data]
1750    pub struct VersionInfoData {
1751        major: usize,
1752        minor: usize,
1753        micro: usize,
1754        releaselevel: &'static str,
1755        serial: usize,
1756    }
1757
1758    impl VersionInfoData {
1759        pub const VERSION: Self = Self {
1760            major: version::MAJOR,
1761            minor: version::MINOR,
1762            micro: version::MICRO,
1763            releaselevel: version::RELEASELEVEL,
1764            serial: version::SERIAL,
1765        };
1766    }
1767
1768    #[pystruct_sequence(name = "version_info", data = "VersionInfoData", no_attr)]
1769    pub struct PyVersionInfo;
1770
1771    #[pyclass(with(PyStructSequence))]
1772    impl PyVersionInfo {
1773        #[pyslot]
1774        fn slot_new(
1775            _cls: crate::builtins::type_::PyTypeRef,
1776            _args: crate::function::FuncArgs,
1777            vm: &crate::VirtualMachine,
1778        ) -> crate::PyResult {
1779            Err(vm.new_type_error("cannot create 'sys.version_info' instances"))
1780        }
1781    }
1782
1783    #[cfg(windows)]
1784    #[derive(Default, Debug)]
1785    #[pystruct_sequence_data]
1786    pub(super) struct WindowsVersionData {
1787        major: u32,
1788        minor: u32,
1789        build: u32,
1790        platform: u32,
1791        service_pack: String,
1792        #[pystruct_sequence(skip)]
1793        service_pack_major: u16,
1794        #[pystruct_sequence(skip)]
1795        service_pack_minor: u16,
1796        #[pystruct_sequence(skip)]
1797        suite_mask: u16,
1798        #[pystruct_sequence(skip)]
1799        product_type: u8,
1800        #[pystruct_sequence(skip)]
1801        platform_version: (u32, u32, u32),
1802    }
1803
1804    #[cfg(windows)]
1805    #[pystruct_sequence(name = "getwindowsversion", data = "WindowsVersionData", no_attr)]
1806    pub(super) struct PyWindowsVersion;
1807
1808    #[cfg(windows)]
1809    #[pyclass(with(PyStructSequence))]
1810    impl PyWindowsVersion {
1811        #[pyslot]
1812        fn slot_new(_cls: PyTypeRef, _args: FuncArgs, vm: &VirtualMachine) -> PyResult {
1813            Err(vm.new_type_error("cannot create 'sys.getwindowsversion' instances"))
1814        }
1815    }
1816
1817    #[derive(Debug)]
1818    #[pystruct_sequence_data(try_from_object)]
1819    pub struct UnraisableHookArgsData {
1820        pub exc_type: PyTypeRef,
1821        pub exc_value: PyObjectRef,
1822        pub exc_traceback: PyObjectRef,
1823        pub err_msg: PyObjectRef,
1824        pub object: PyObjectRef,
1825    }
1826
1827    #[pystruct_sequence(name = "UnraisableHookArgs", data = "UnraisableHookArgsData", no_attr)]
1828    pub struct PyUnraisableHookArgs;
1829
1830    #[pyclass(with(PyStructSequence))]
1831    impl PyUnraisableHookArgs {}
1832}
1833
1834pub(crate) fn init_module(vm: &VirtualMachine, module: &Py<PyModule>, builtins: &Py<PyModule>) {
1835    module.__init_methods(vm).unwrap();
1836    sys::module_exec(vm, module).unwrap();
1837
1838    let modules = vm.ctx.new_dict();
1839    modules
1840        .set_item("sys", module.to_owned().into(), vm)
1841        .unwrap();
1842    modules
1843        .set_item("builtins", builtins.to_owned().into(), vm)
1844        .unwrap();
1845
1846    // Create sys._jit submodule
1847    let jit_def = sys_jit::module_def(&vm.ctx);
1848    let jit_module = jit_def.create_module(vm).unwrap();
1849
1850    extend_module!(vm, module, {
1851        "__doc__" => sys::DOC.to_owned().to_pyobject(vm),
1852        "modules" => modules,
1853        "_jit" => jit_module,
1854    });
1855}
1856
1857pub(crate) fn set_bootstrap_stderr(vm: &VirtualMachine) -> PyResult<()> {
1858    let stderr = sys::BootstrapStderr.into_ref(&vm.ctx);
1859    let stderr_obj: crate::PyObjectRef = stderr.into();
1860    vm.sys_module.set_attr("stderr", stderr_obj.clone(), vm)?;
1861    vm.sys_module.set_attr("__stderr__", stderr_obj, vm)?;
1862    Ok(())
1863}
1864
1865/// Similar to PySys_WriteStderr in CPython.
1866///
1867/// # Usage
1868///
1869/// ```rust,ignore
1870/// writeln!(sys::PyStderr(vm), "foo bar baz :)");
1871/// ```
1872///
1873/// Unlike writing to a `std::io::Write` with the `write[ln]!()` macro, there's no error condition here;
1874/// this is intended to be a replacement for the `eprint[ln]!()` macro, so `write!()`-ing to PyStderr just
1875/// returns `()`.
1876pub struct PyStderr<'vm>(pub &'vm VirtualMachine);
1877
1878impl PyStderr<'_> {
1879    pub fn write_fmt(&self, args: core::fmt::Arguments<'_>) {
1880        use crate::py_io::Write;
1881
1882        let vm = self.0;
1883        if let Ok(stderr) = get_stderr(vm) {
1884            let mut stderr = crate::py_io::PyWriter(stderr, vm);
1885            if let Ok(()) = stderr.write_fmt(args) {
1886                return;
1887            }
1888        }
1889        eprint!("{args}")
1890    }
1891}
1892
1893pub fn get_stdin(vm: &VirtualMachine) -> PyResult {
1894    vm.sys_module
1895        .get_attr("stdin", vm)
1896        .map_err(|_| vm.new_runtime_error("lost sys.stdin"))
1897}
1898pub fn get_stdout(vm: &VirtualMachine) -> PyResult {
1899    vm.sys_module
1900        .get_attr("stdout", vm)
1901        .map_err(|_| vm.new_runtime_error("lost sys.stdout"))
1902}
1903pub fn get_stderr(vm: &VirtualMachine) -> PyResult {
1904    vm.sys_module
1905        .get_attr("stderr", vm)
1906        .map_err(|_| vm.new_runtime_error("lost sys.stderr"))
1907}
1908
1909pub(crate) fn sysconfigdata_name() -> String {
1910    format!(
1911        "_sysconfigdata_{}_{}_{}",
1912        sys::ABIFLAGS,
1913        sys::PLATFORM,
1914        sys::multiarch()
1915    )
1916}