Skip to main content

rustpython_vm/stdlib/
builtins.rs

1//! Builtin function definitions.
2//!
3//! Implements the list of [builtin Python functions](https://docs.python.org/3/library/builtins.html).
4use crate::{Py, VirtualMachine, builtins::PyModule, class::PyClassImpl};
5pub(crate) use builtins::{DOC, module_def};
6pub use builtins::{ascii, print, reversed};
7
8#[pymodule]
9mod builtins {
10    use crate::{
11        AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, TryFromObject,
12        VirtualMachine,
13        builtins::{
14            PyByteArray, PyBytes, PyDictRef, PyStr, PyStrRef, PyTuple, PyTupleRef, PyType,
15            PyUtf8StrRef,
16            enumerate::PyReverseSequenceIterator,
17            function::{PyCell, PyCellRef, PyFunction},
18            iter::PyCallableIterator,
19            list::{PyList, SortOptions},
20        },
21        bytecode,
22        common::hash::PyHash,
23        function::{
24            ArgCallable, ArgIndex, ArgIntoBool, ArgIterable, ArgMapping, ArgStrOrBytesLike, Either,
25            FsPath, FuncArgs, KwArgs, NameKws, OptionalArg, PosArgs,
26        },
27        protocol::{PyIter, PyIterReturn},
28        py_io,
29        readline::{Readline, ReadlineResult},
30        stdlib::sys,
31        types::PyComparisonOp,
32        vm::compile_mode::{CompileStart, CompilerFlags, compile_future_features_from_flags},
33    };
34    use itertools::Itertools;
35    use num_traits::{Signed, ToPrimitive};
36    use rustpython_common::wtf8::CodePoint;
37
38    #[cfg(not(feature = "rustpython-compiler"))]
39    const CODEGEN_NOT_SUPPORTED: &str =
40        "can't compile() to bytecode when the `codegen` feature of rustpython is disabled";
41
42    #[pyfunction]
43    fn abs(x: PyObjectRef, vm: &VirtualMachine) -> PyResult {
44        vm._abs(&x)
45    }
46
47    #[pyfunction]
48    fn all(iterable: ArgIterable<ArgIntoBool>, vm: &VirtualMachine) -> PyResult<bool> {
49        for item in iterable.iter(vm)? {
50            if !item?.into_bool() {
51                return Ok(false);
52            }
53        }
54        Ok(true)
55    }
56
57    #[pyfunction]
58    fn any(iterable: ArgIterable<ArgIntoBool>, vm: &VirtualMachine) -> PyResult<bool> {
59        for item in iterable.iter(vm)? {
60            if item?.into_bool() {
61                return Ok(true);
62            }
63        }
64        Ok(false)
65    }
66
67    #[pyfunction]
68    pub fn ascii(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
69        obj.ascii(vm)
70    }
71
72    #[pyfunction]
73    fn bin(number: ArgIndex) -> String {
74        let number = number.into_int_ref();
75        let x = number.as_bigint();
76        if x.is_negative() {
77            format!("-0b{:b}", x.abs())
78        } else {
79            format!("0b{x:b}")
80        }
81    }
82
83    #[pyfunction]
84    fn callable(obj: PyObjectRef) -> bool {
85        obj.is_callable()
86    }
87
88    #[pyfunction]
89    fn chr(i: ArgIndex, vm: &VirtualMachine) -> PyResult<CodePoint> {
90        let i = i.into_int_ref();
91        let value = i
92            .as_bigint()
93            .to_u32()
94            .and_then(CodePoint::from_u32)
95            .ok_or_else(|| vm.new_value_error("chr() arg not in range(0x110000)"))?;
96        Ok(value)
97    }
98
99    #[derive(FromArgs)]
100    #[allow(dead_code)]
101    struct CompileArgs {
102        source: PyObjectRef,
103        // Resolved to FsPath at the start of compile() so that bytearray /
104        // memoryview / other buffer-protocol objects raise TypeError, matching
105        // CPython's PyUnicode_FSDecoder (str / bytes / __fspath__ only).
106        filename: PyObjectRef,
107        mode: PyUtf8StrRef,
108        // CPython parity: flags / optimize accept any object with __index__,
109        // not just exact int. Matches the argument conversion used by
110        // builtin_compile_impl.
111        // Any object with __index__ is accepted.
112        #[pyarg(any, default = 0)]
113        flags: i32,
114        // dont_inherit goes through PyObject_IsTrue, so arbitrary objects
115        // with `__bool__` are accepted (and any exception raised inside
116        // `__bool__` propagates) — not the strict bool type.
117        #[pyarg(any, default = false)]
118        dont_inherit: ArgIntoBool,
119        #[pyarg(any, default = -1)]
120        optimize: i32,
121        #[pyarg(named, default = -1)]
122        _feature_version: i32,
123    }
124
125    fn merge_compile_future_features(
126        flags: i32,
127        dont_inherit: bool,
128        _vm: &VirtualMachine,
129    ) -> bytecode::CodeFlags {
130        let mut future_features = compile_future_features_from_flags(flags);
131        if !dont_inherit && let Some(code) = crate::frame::current_code() {
132            future_features |= code.flags & bytecode::CodeFlags::FUTURE_MASK;
133        }
134        future_features
135    }
136
137    fn audit_compile_source(vm: &VirtualMachine, source: &[u8], filename: &str) -> PyResult<()> {
138        vm.audit("compile", || {
139            (vm.ctx.new_bytes(source.to_vec()), vm.ctx.new_str(filename))
140        })
141    }
142
143    fn trim_eval_source_bytes(mut source: &[u8]) -> &[u8] {
144        while let Some((&first, rest)) = source.split_first()
145            && matches!(first, b' ' | b'\t')
146        {
147            source = rest;
148        }
149        source
150    }
151
152    fn decode_eval_exec_source_bytes(
153        vm: &VirtualMachine,
154        source: &[u8],
155        filename: &str,
156    ) -> PyResult<String> {
157        #[cfg(feature = "parser")]
158        {
159            vm.decode_source_bytes(source, filename, false)
160        }
161        #[cfg(not(feature = "parser"))]
162        {
163            _ = filename;
164            core::str::from_utf8(source)
165                .map(str::to_owned)
166                .map_err(|err| {
167                    let msg = format!(
168                        "(unicode error) 'utf-8' codec can't decode byte 0x{:x?} in position {}: invalid start byte",
169                        source[err.valid_up_to()],
170                        err.valid_up_to()
171                    );
172                    vm.new_exception_msg(vm.ctx.exceptions.syntax_error.to_owned(), msg.into())
173                })
174        }
175    }
176
177    #[cfg(any(feature = "parser", feature = "compiler"))]
178    #[pyfunction]
179    fn compile(args: CompileArgs, vm: &VirtualMachine) -> PyResult {
180        #[cfg(not(feature = "ast"))]
181        {
182            _ = args; // to disable unused warning
183            return Err(vm.new_type_error("AST Not Supported"));
184        }
185        #[cfg(feature = "ast")]
186        {
187            // CPython parity: PyUnicode_FSDecoder accepts only str / bytes /
188            // __fspath__-bearing objects. Reject buffer-protocol types like
189            // bytearray and memoryview that would otherwise pass through
190            // `FsPath::TryFromObject`'s permissive fallback.
191            let filename = FsPath::try_from_path_like(args.filename, true, vm)?;
192
193            use crate::{class::PyClassImpl, stdlib::_ast};
194
195            let feature_version = args._feature_version;
196
197            let mode_str = args.mode.as_str();
198            let flags: i32 = args.flags;
199            let cf = CompilerFlags::from_bits_retain(flags);
200
201            if (flags & !CompilerFlags::ALLOWED_FLAGS.bits()) != 0 {
202                return Err(vm.new_value_error("compile(): unrecognised flags"));
203            }
204
205            let optimize: i32 = args.optimize;
206            let optimize: u8 = match optimize {
207                -1 => vm.state.config.settings.optimize.min(2),
208                0..=2 => optimize as u8,
209                _ => return Err(vm.new_value_error("compile(): invalid optimize value")),
210            };
211            let dont_inherit = args.dont_inherit.into_bool();
212            let is_ast_only = cf.contains(CompilerFlags::ONLY_AST);
213            let future_features = merge_compile_future_features(flags, dont_inherit, vm);
214
215            let start = if mode_str == "exec" {
216                CompileStart::File
217            } else if mode_str == "eval" {
218                CompileStart::Eval
219            } else if mode_str == "single" {
220                CompileStart::Single
221            } else if mode_str == "func_type" {
222                if !is_ast_only {
223                    return Err(vm.new_value_error(
224                        "compile() mode 'func_type' requires flag PyCF_ONLY_AST",
225                    ));
226                }
227                CompileStart::FuncType
228            } else {
229                let msg = if is_ast_only {
230                    "compile() mode must be 'exec', 'eval', 'single' or 'func_type'"
231                } else {
232                    "compile() mode must be 'exec', 'eval' or 'single'"
233                };
234                return Err(vm.new_value_error(msg));
235            };
236
237            let ast_type = _ast::NodeAst::make_static_type().as_object().to_owned();
238            if args.source.is_instance(&ast_type, vm)? {
239                let explicit_future_annotations =
240                    future_features.contains(bytecode::CodeFlags::FUTURE_ANNOTATIONS);
241                vm.audit("compile", || (args.source.clone(), vm.ctx.none()))?;
242
243                // compile(ast_node, ..., PyCF_ONLY_AST) returns the AST after validation
244                if is_ast_only {
245                    let (expected_type, expected_name) = _ast::mode_type_and_name(mode_str)
246                        .ok_or_else(|| {
247                            vm.new_value_error(
248                                "compile() mode must be 'exec', 'eval', 'single' or 'func_type'",
249                            )
250                        })?;
251                    if !args.source.is_instance(expected_type.as_object(), vm)? {
252                        return Err(vm.new_type_error(format!(
253                            "expected {} node, got {}",
254                            expected_name,
255                            args.source.class().name()
256                        )));
257                    }
258                    #[cfg(not(feature = "rustpython-codegen"))]
259                    {
260                        _ast::validate_ast_object(vm, args.source.clone())?;
261                        return Ok(args.source);
262                    }
263                    #[cfg(feature = "rustpython-codegen")]
264                    {
265                        return _ast::preprocess_ast_object(
266                            vm,
267                            args.source,
268                            &filename.to_string_lossy(),
269                            optimize,
270                            cf.contains(CompilerFlags::OPTIMIZED_AST),
271                            explicit_future_annotations,
272                        );
273                    }
274                }
275
276                #[cfg(not(feature = "rustpython-codegen"))]
277                {
278                    return Err(vm.new_type_error(CODEGEN_NOT_SUPPORTED));
279                }
280                #[cfg(feature = "rustpython-codegen")]
281                {
282                    let (expected_type, expected_name) = _ast::mode_type_and_name(mode_str)
283                        .ok_or_else(|| {
284                            vm.new_value_error("compile() mode must be 'exec', 'eval' or 'single'")
285                        })?;
286                    if !args.source.is_instance(expected_type.as_object(), vm)? {
287                        return Err(vm.new_type_error(format!(
288                            "expected {} node, got {}",
289                            expected_name,
290                            args.source.class().name()
291                        )));
292                    }
293                    let mode = mode_str
294                        .parse::<crate::compiler::Mode>()
295                        .map_err(|err| vm.new_value_error(err.to_string()))?;
296                    let mut opts = vm.compile_opts();
297                    opts.optimize = optimize;
298                    opts.allow_top_level_await = cf.contains(CompilerFlags::ALLOW_TOP_LEVEL_AWAIT);
299                    opts.future_features = future_features;
300                    return _ast::compile(vm, args.source, &filename.to_string_lossy(), mode, opts);
301                }
302            }
303
304            #[cfg(not(feature = "parser"))]
305            {
306                Err(vm.new_type_error(
307                    "can't compile() source code when the `parser` feature of rustpython is disabled",
308                ))
309            }
310            #[cfg(feature = "parser")]
311            {
312                let source = ArgStrOrBytesLike::try_from_object(vm, args.source)?;
313
314                let mut compile_flags = flags | future_features.bits() as i32;
315                #[cfg(feature = "rustpython-compiler")]
316                let compile_source = |source: &[u8], compile_flags: i32| {
317                    vm.compile_string_object_with_flags(
318                        source,
319                        &filename.to_string_lossy(),
320                        start.as_i32(),
321                        compile_flags,
322                        feature_version,
323                        optimize as i32,
324                    )
325                };
326                match &source {
327                    ArgStrOrBytesLike::Str(source) => {
328                        let source = source.try_as_utf8(vm)?.as_str();
329                        if source.as_bytes().contains(&0) {
330                            return Err(vm.new_exception_msg(
331                                vm.ctx.exceptions.syntax_error.to_owned(),
332                                "source code string cannot contain null bytes".into(),
333                            ));
334                        }
335                        audit_compile_source(
336                            vm,
337                            source.as_bytes(),
338                            filename.to_string_lossy().as_ref(),
339                        )?;
340                        compile_flags |= CompilerFlags::IGNORE_COOKIE.bits();
341                        #[cfg(feature = "rustpython-compiler")]
342                        {
343                            compile_source(source.as_bytes(), compile_flags)
344                        }
345                        #[cfg(not(feature = "rustpython-compiler"))]
346                        {
347                            Err(vm.new_value_error(CODEGEN_NOT_SUPPORTED))
348                        }
349                    }
350                    ArgStrOrBytesLike::Buf(source) => {
351                        let source_bytes = source.borrow_buf();
352                        let source_bytes: &[u8] = &source_bytes;
353                        if source_bytes.contains(&0) {
354                            return Err(vm.new_exception_msg(
355                                vm.ctx.exceptions.syntax_error.to_owned(),
356                                "source code string cannot contain null bytes".into(),
357                            ));
358                        }
359                        audit_compile_source(
360                            vm,
361                            source_bytes,
362                            filename.to_string_lossy().as_ref(),
363                        )?;
364                        #[cfg(feature = "rustpython-compiler")]
365                        {
366                            compile_source(source_bytes, compile_flags)
367                        }
368                        #[cfg(not(feature = "rustpython-compiler"))]
369                        {
370                            Err(vm.new_value_error(CODEGEN_NOT_SUPPORTED))
371                        }
372                    }
373                }
374            }
375        }
376    }
377
378    #[pyfunction]
379    fn delattr(obj: PyObjectRef, name: PyObjectRef, vm: &VirtualMachine) -> PyResult<()> {
380        let attr = name.try_to_ref::<PyStr>(vm).map_err(|_e| {
381            vm.new_type_error(format!(
382                "attribute name must be string, not '{}'",
383                name.class().name()
384            ))
385        })?;
386        obj.del_attr(attr, vm)
387    }
388
389    #[pyfunction]
390    fn dir(obj: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult<PyList> {
391        vm.dir(obj.into_option())
392    }
393
394    #[pyfunction]
395    fn divmod(x: PyObjectRef, y: PyObjectRef, vm: &VirtualMachine) -> PyResult {
396        vm._divmod(&x, &y)
397    }
398
399    #[derive(FromArgs)]
400    struct ScopeArgs {
401        #[pyarg(any, optional)]
402        globals: Option<PyObjectRef>,
403        #[pyarg(any, optional)]
404        locals: Option<ArgMapping>,
405    }
406
407    impl ScopeArgs {
408        fn make_scope(
409            self,
410            vm: &VirtualMachine,
411            func_name: &'static str,
412        ) -> PyResult<crate::scope::Scope> {
413            fn validate_globals_dict(
414                globals: &PyObject,
415                vm: &VirtualMachine,
416                func_name: &'static str,
417            ) -> PyResult<()> {
418                if !globals.fast_isinstance(vm.ctx.types.dict_type) {
419                    return Err(match func_name {
420                        "eval" => {
421                            let is_mapping = globals.mapping_unchecked().check();
422                            vm.new_type_error(if is_mapping {
423                                "globals must be a real dict; try eval(expr, {}, mapping)"
424                            } else {
425                                "globals must be a dict"
426                            })
427                        }
428                        "exec" => vm.new_type_error(format!(
429                            "exec() globals must be a dict, not {}",
430                            globals.class().name()
431                        )),
432                        _ => vm.new_type_error("globals must be a dict"),
433                    });
434                }
435                Ok(())
436            }
437
438            let (globals, locals) = match self.globals {
439                Some(globals) => {
440                    validate_globals_dict(&globals, vm, func_name)?;
441
442                    let globals = PyDictRef::try_from_object(vm, globals)?;
443                    if !globals.contains_key(identifier!(vm, __builtins__), vm) {
444                        let builtins_dict = vm.builtins.dict().into();
445                        globals.set_item(identifier!(vm, __builtins__), builtins_dict, vm)?;
446                    }
447                    (
448                        globals.clone(),
449                        self.locals
450                            .unwrap_or_else(|| ArgMapping::from_dict_exact(globals.clone())),
451                    )
452                }
453                None => (
454                    vm.current_globals(),
455                    if let Some(locals) = self.locals {
456                        locals
457                    } else {
458                        vm.current_locals()?
459                    },
460                ),
461            };
462
463            let scope = crate::scope::Scope::with_builtins(Some(locals), globals, vm);
464            Ok(scope)
465        }
466    }
467
468    #[derive(FromArgs)]
469    struct ExecArgs {
470        #[pyarg(positional)]
471        source: Either<PyRef<crate::builtins::PyCode>, ArgStrOrBytesLike>,
472        #[pyarg(any, optional)]
473        globals: Option<PyObjectRef>,
474        #[pyarg(any, optional)]
475        locals: Option<ArgMapping>,
476        #[pyarg(named, optional)]
477        closure: Option<PyObjectRef>,
478    }
479
480    fn exec_closure(
481        code_obj: &Py<crate::builtins::PyCode>,
482        closure: Option<PyObjectRef>,
483        vm: &VirtualMachine,
484    ) -> PyResult<Option<PyRef<PyTuple<PyCellRef>>>> {
485        let num_free = code_obj.freevars.len();
486        let Some(closure) = closure else {
487            if num_free == 0 {
488                return Ok(None);
489            }
490            return Err(vm.new_type_error(format!(
491                "code object requires a closure of exactly length {num_free}"
492            )));
493        };
494
495        if num_free == 0 {
496            return Err(vm.new_type_error("cannot use a closure with this code object"));
497        }
498
499        let closure_tuple = closure
500            .downcast_exact::<PyTuple>(vm)
501            .map_err(|_| {
502                vm.new_type_error(format!(
503                    "code object requires a closure of exactly length {num_free}"
504                ))
505            })?
506            .into_pyref();
507        if closure_tuple.as_slice().len() != num_free {
508            return Err(vm.new_type_error(format!(
509                "code object requires a closure of exactly length {num_free}"
510            )));
511        }
512
513        closure_tuple
514            .try_into_typed::<PyCell>(vm)
515            .map(Some)
516            .map_err(|_| {
517                vm.new_type_error(format!(
518                    "code object requires a closure of exactly length {num_free}"
519                ))
520            })
521    }
522
523    #[pyfunction]
524    fn eval(
525        source: Either<PyRef<crate::builtins::PyCode>, ArgStrOrBytesLike>,
526        scope: ScopeArgs,
527        vm: &VirtualMachine,
528    ) -> PyResult {
529        let scope = scope.make_scope(vm, "eval")?;
530
531        // source as string
532        let code = match source {
533            Either::B(either) => {
534                let source = match &either {
535                    ArgStrOrBytesLike::Str(source) => {
536                        let source = source.try_as_utf8(vm)?.as_str();
537                        if source.as_bytes().contains(&0) {
538                            return Err(vm.new_exception_msg(
539                                vm.ctx.exceptions.syntax_error.to_owned(),
540                                "source code string cannot contain null bytes".into(),
541                            ));
542                        }
543                        let source = source.trim_start_matches([' ', '\t']);
544                        audit_compile_source(vm, source.as_bytes(), "<string>")?;
545                        source.to_owned()
546                    }
547                    ArgStrOrBytesLike::Buf(source) => {
548                        let source: &[u8] = &source.borrow_buf();
549                        if source.contains(&0) {
550                            return Err(vm.new_exception_msg(
551                                vm.ctx.exceptions.syntax_error.to_owned(),
552                                "source code string cannot contain null bytes".into(),
553                            ));
554                        }
555                        let source = trim_eval_source_bytes(source);
556                        audit_compile_source(vm, source, "<string>")?;
557                        decode_eval_exec_source_bytes(vm, source, "eval")?
558                    }
559                };
560                Ok(Either::B(vm.ctx.new_utf8_str(source)))
561            }
562            Either::A(code) => Ok(Either::A(code)),
563        }?;
564        run_code(vm, code, scope, crate::compiler::Mode::Eval, "eval", None)
565    }
566
567    #[pyfunction]
568    fn exec(args: ExecArgs, vm: &VirtualMachine) -> PyResult {
569        let ExecArgs {
570            source,
571            globals,
572            locals,
573            closure,
574        } = args;
575        let scope = ScopeArgs { globals, locals }.make_scope(vm, "exec")?;
576        let (source, closure) = match source {
577            Either::B(either) => {
578                if closure.is_some() {
579                    return Err(
580                        vm.new_type_error("closure can only be used when source is a code object")
581                    );
582                }
583                let source = match &either {
584                    ArgStrOrBytesLike::Str(source) => {
585                        let source = source.try_as_utf8(vm)?.as_str();
586                        if source.as_bytes().contains(&0) {
587                            return Err(vm.new_exception_msg(
588                                vm.ctx.exceptions.syntax_error.to_owned(),
589                                "source code string cannot contain null bytes".into(),
590                            ));
591                        }
592                        audit_compile_source(vm, source.as_bytes(), "<string>")?;
593                        source.to_owned()
594                    }
595                    ArgStrOrBytesLike::Buf(source) => {
596                        let source: &[u8] = &source.borrow_buf();
597                        if source.contains(&0) {
598                            return Err(vm.new_exception_msg(
599                                vm.ctx.exceptions.syntax_error.to_owned(),
600                                "source code string cannot contain null bytes".into(),
601                            ));
602                        }
603                        audit_compile_source(vm, source, "<string>")?;
604                        decode_eval_exec_source_bytes(vm, source, "exec")?
605                    }
606                };
607                (Either::B(vm.ctx.new_utf8_str(source)), None)
608            }
609            Either::A(code) => {
610                let closure = exec_closure(&code, closure, vm)?;
611                (Either::A(code), closure)
612            }
613        };
614        run_code(
615            vm,
616            source,
617            scope,
618            crate::compiler::Mode::Exec,
619            "exec",
620            closure,
621        )
622    }
623
624    fn run_code(
625        vm: &VirtualMachine,
626        source: Either<PyRef<crate::builtins::PyCode>, PyUtf8StrRef>,
627        scope: crate::scope::Scope,
628        #[allow(unused_variables)] mode: crate::compiler::Mode,
629        func: &str,
630        closure: Option<PyRef<PyTuple<PyCellRef>>>,
631    ) -> PyResult {
632        // Determine code object:
633        let code_obj = match source {
634            #[cfg(feature = "rustpython-compiler")]
635            Either::B(string) => {
636                let source = string.as_str();
637                let mut opts = vm.compile_opts();
638                if let Some(code) = crate::frame::current_code() {
639                    opts.future_features = code.flags & bytecode::CodeFlags::FUTURE_MASK;
640                }
641                vm.compile_with_opts(source, mode, "<string>", opts)
642                    .map_err(|err| err.into_pyexception(vm, Some(source)))?
643            }
644            #[cfg(not(feature = "rustpython-compiler"))]
645            Either::B(_) => return Err(vm.new_type_error(CODEGEN_NOT_SUPPORTED)),
646            Either::A(code_obj) => code_obj,
647        };
648
649        vm.audit("exec", || (code_obj.clone(),))?;
650
651        if closure.is_none() && !code_obj.freevars.is_empty() {
652            return Err(vm.new_type_error(format!(
653                "code object passed to {func}() may not contain free variables"
654            )));
655        }
656
657        // Run the code:
658        vm.run_code_obj_with_closure(code_obj, scope, closure)
659    }
660
661    #[derive(FromArgs)]
662    struct FormatArgs {
663        #[pyarg(positional)]
664        value: PyObjectRef,
665        #[pyarg(positional, default = "")]
666        format_spec: PyStrRef,
667    }
668
669    #[derive(FromArgs)]
670    struct InputArgs {
671        // Missing means an empty prompt.
672        #[pyarg(positional, optional, py_default = "''")]
673        prompt: OptionalArg<PyStrRef>,
674    }
675
676    #[pyfunction]
677    fn format(args: FormatArgs, vm: &VirtualMachine) -> PyResult<PyStrRef> {
678        vm.format(&args.value, args.format_spec)
679    }
680
681    #[pyfunction]
682    fn getattr(
683        obj: PyObjectRef,
684        attr: PyObjectRef,
685        default: OptionalArg<PyObjectRef>,
686        vm: &VirtualMachine,
687    ) -> PyResult {
688        let attr = attr.try_to_ref::<PyStr>(vm).map_err(|_e| {
689            vm.new_type_error(format!(
690                "attribute name must be string, not '{}'",
691                attr.class().name()
692            ))
693        })?;
694
695        if let OptionalArg::Present(default) = default {
696            Ok(vm.get_attribute_opt(&obj, attr)?.unwrap_or(default))
697        } else {
698            obj.get_attr(attr, vm)
699        }
700    }
701
702    #[pyfunction]
703    fn globals(vm: &VirtualMachine) -> PyDictRef {
704        vm.current_globals()
705    }
706
707    #[pyfunction]
708    fn hasattr(obj: PyObjectRef, name: PyObjectRef, vm: &VirtualMachine) -> PyResult<bool> {
709        let attr = name.try_to_ref::<PyStr>(vm).map_err(|_e| {
710            vm.new_type_error(format!(
711                "attribute name must be string, not '{}'",
712                name.class().name()
713            ))
714        })?;
715        obj.has_attr(attr, vm)
716    }
717
718    #[pyfunction]
719    fn hash(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyHash> {
720        obj.hash(vm)
721    }
722
723    #[pyfunction]
724    fn breakpoint(
725        args: PosArgs,
726        kws: KwArgs<PyObjectRef, NameKws>,
727        vm: &VirtualMachine,
728    ) -> PyResult {
729        let args = FuncArgs {
730            args: args.into_vec(),
731            kwargs: kws.into_default(),
732        };
733        match vm
734            .sys_module
735            .get_attr(vm.ctx.intern_str("breakpointhook"), vm)
736        {
737            Ok(hook) => hook.as_ref().call(args, vm),
738            Err(_) => Err(vm.new_runtime_error("lost sys.breakpointhook")),
739        }
740    }
741
742    #[pyfunction]
743    fn hex(number: ArgIndex) -> String {
744        let number = number.into_int_ref();
745        let n = number.as_bigint();
746        format!("{n:#x}")
747    }
748
749    #[pyfunction]
750    fn id(obj: PyObjectRef) -> usize {
751        obj.get_id()
752    }
753
754    #[pyfunction]
755    fn input(args: InputArgs, vm: &VirtualMachine) -> PyResult {
756        let prompt = args.prompt;
757        use std::io::IsTerminal;
758
759        let stdin = sys::get_stdin(vm)?;
760        let stdout = sys::get_stdout(vm)?;
761        let stderr = sys::get_stderr(vm)?;
762
763        let _ = vm.call_method(&stderr, "flush", ());
764
765        let fd_matches = |obj, expected| {
766            vm.call_method(obj, "fileno", ())
767                .and_then(|o| i64::try_from_object(vm, o))
768                .is_ok_and(|fd| fd == expected)
769        };
770
771        let tty = fd_matches(&stdin, 0)
772            && fd_matches(&stdout, 1)
773            && std::io::stdin().is_terminal()
774            && std::io::stdout().is_terminal();
775
776        // Encode the prompt with stdout's encoding and reject embedded NULs
777        // (`strlen` on the encoded bytes).
778        if tty && let OptionalArg::Present(prompt) = &prompt {
779            let _ = vm.call_method(&stdout, "flush", ());
780            let encoding = stdout.get_attr("encoding", vm)?;
781            let errors = stdout.get_attr("errors", vm)?;
782            if !vm.is_none(&encoding) {
783                let encoded = vm.call_method(prompt.as_object(), "encode", (encoding, errors))?;
784                let bytes = encoded
785                    .downcast::<PyBytes>()
786                    .map_err(|_| vm.new_type_error("encode() must return bytes".to_owned()))?;
787                if bytes.as_bytes().contains(&0) {
788                    return Err(vm.new_value_error(
789                        "input: prompt string cannot contain null characters".to_owned(),
790                    ));
791                }
792            }
793        }
794
795        // rustyline is the interactive tty reader. Skip it in a PTY child
796        // (`pty.fork` + `setsid`): raw mode hangs there.
797        let prompt_str = match &prompt {
798            OptionalArg::Present(s) => s.to_str(),
799            OptionalArg::Missing => Some(""),
800        };
801        let use_rustyline = tty && !is_pty_child() && prompt_str.is_some();
802
803        if use_rustyline {
804            let prompt = prompt_str.unwrap();
805            let mut readline = Readline::new(());
806            match readline.readline(prompt) {
807                ReadlineResult::Line(s) => Ok(vm.ctx.new_str(s).into()),
808                ReadlineResult::Eof => {
809                    Err(vm.new_exception_empty(vm.ctx.exceptions.eof_error.to_owned()))
810                }
811                ReadlineResult::Interrupt => {
812                    Err(vm.new_exception_empty(vm.ctx.exceptions.keyboard_interrupt.to_owned()))
813                }
814                ReadlineResult::Io(e) => Err(vm.new_os_error(e.to_string())),
815                #[cfg(unix)]
816                ReadlineResult::OsError(num) => Err(vm.new_os_error(num)),
817                ReadlineResult::Other(e) => Err(vm.new_runtime_error(e.to_string())),
818            }
819        } else {
820            if let OptionalArg::Present(prompt) = prompt {
821                vm.call_method(&stdout, "write", (prompt,))?;
822            }
823            let _ = vm.call_method(&stdout, "flush", ());
824            py_io::file_readline(&stdin, None, vm)
825        }
826    }
827
828    /// Check if we're running in a PTY child process (e.g., after pty.fork()).
829    /// pty.fork() calls setsid(), making the child a session leader.
830    /// In this case, rustyline may hang because it uses raw mode.
831    #[cfg(unix)]
832    fn is_pty_child() -> bool {
833        crate::host_env::posix::is_session_leader()
834    }
835
836    #[cfg(not(unix))]
837    fn is_pty_child() -> bool {
838        false
839    }
840
841    #[pyfunction]
842    fn isinstance(
843        obj: PyObjectRef,
844        class_or_tuple: PyObjectRef,
845        vm: &VirtualMachine,
846    ) -> PyResult<bool> {
847        obj.is_instance(&class_or_tuple, vm)
848    }
849
850    #[pyfunction]
851    fn issubclass(
852        cls: PyObjectRef,
853        class_or_tuple: PyObjectRef,
854        vm: &VirtualMachine,
855    ) -> PyResult<bool> {
856        cls.is_subclass(&class_or_tuple, vm)
857    }
858
859    #[pyfunction]
860    fn iter(
861        iter_target: PyObjectRef,
862        sentinel: OptionalArg<PyObjectRef>,
863        vm: &VirtualMachine,
864    ) -> PyResult<PyIter> {
865        if let OptionalArg::Present(sentinel) = sentinel {
866            let callable = ArgCallable::try_from_object(vm, iter_target)?;
867            let iterator = PyCallableIterator::new(callable, sentinel)
868                .into_ref(&vm.ctx)
869                .into();
870            Ok(PyIter::new(iterator))
871        } else {
872            PyIter::try_from_object(vm, iter_target)
873        }
874    }
875
876    #[pyfunction]
877    fn aiter(async_iterable: PyObjectRef, vm: &VirtualMachine) -> PyResult {
878        async_iterable.get_aiter(vm)
879    }
880
881    #[pyfunction]
882    fn anext(
883        aiter: PyObjectRef,
884        default_value: OptionalArg<PyObjectRef>,
885        vm: &VirtualMachine,
886    ) -> PyResult {
887        use crate::builtins::asyncgenerator::PyAnextAwaitable;
888
889        // Check if object is an async iterator (has __anext__ method)
890        if !aiter.class().has_attr(identifier!(vm, __anext__)) {
891            return Err(vm.new_type_error(format!(
892                "'{}' object is not an async iterator",
893                aiter.class().name()
894            )));
895        }
896
897        let awaitable = vm.call_method(&aiter, "__anext__", ())?;
898
899        if let OptionalArg::Present(default) = default_value {
900            Ok(PyAnextAwaitable::new(awaitable, default)
901                .into_ref(&vm.ctx)
902                .into())
903        } else {
904            Ok(awaitable)
905        }
906    }
907
908    #[pyfunction]
909    fn len(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<usize> {
910        obj.length(vm)
911    }
912
913    #[pyfunction]
914    fn locals(vm: &VirtualMachine) -> PyResult<ArgMapping> {
915        vm.current_locals()
916    }
917
918    fn min_or_max(
919        mut args: FuncArgs,
920        vm: &VirtualMachine,
921        func_name: &'static str,
922        op: PyComparisonOp,
923    ) -> PyResult {
924        // A call with nothing to compare is refused before the keywords are read.
925        if args.args.is_empty() {
926            return Err(vm.new_arity_type_error(func_name, 1..=usize::MAX, 0));
927        }
928
929        let default = args.take_keyword("default");
930        let key_func = args.take_keyword("key");
931
932        if let Some(err) = args.check_kwargs_empty_for(vm, func_name) {
933            return Err(err);
934        }
935
936        let candidates = if args.args.len() > 1 {
937            if default.is_some() {
938                return Err(vm.new_type_error(format!(
939                    "Cannot specify a default for {func_name}() with multiple positional arguments"
940                )));
941            }
942            args.args
943        } else {
944            args.args[0].try_to_value(vm)?
945        };
946
947        let mut candidates_iter = candidates.into_iter();
948        let mut x = match candidates_iter.next() {
949            Some(x) => x,
950            None => {
951                return default.ok_or_else(|| {
952                    vm.new_value_error(format!("{func_name}() iterable argument is empty"))
953                });
954            }
955        };
956
957        let key_func = key_func.filter(|f| !vm.is_none(f));
958        if let Some(ref key_func) = key_func {
959            let mut x_key = key_func.call((x.clone(),), vm)?;
960            for y in candidates_iter {
961                let y_key = key_func.call((y.clone(),), vm)?;
962                if y_key.rich_compare_bool(&x_key, op, vm)? {
963                    x = y;
964                    x_key = y_key;
965                }
966            }
967        } else {
968            for y in candidates_iter {
969                if y.rich_compare_bool(&x, op, vm)? {
970                    x = y;
971                }
972            }
973        }
974
975        Ok(x)
976    }
977
978    #[pyfunction]
979    fn max(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
980        min_or_max(args, vm, "max", PyComparisonOp::Gt)
981    }
982
983    #[pyfunction]
984    fn min(args: FuncArgs, vm: &VirtualMachine) -> PyResult {
985        min_or_max(args, vm, "min", PyComparisonOp::Lt)
986    }
987
988    #[pyfunction]
989    fn next(
990        iterator: PyObjectRef,
991        default_value: OptionalArg<PyObjectRef>,
992        vm: &VirtualMachine,
993    ) -> PyResult<PyIterReturn> {
994        if !PyIter::check(&iterator) {
995            return Err(vm.new_type_error(format!(
996                "'{}' object is not an iterator",
997                iterator.class().slot_name()
998            )));
999        }
1000        PyIter::new(iterator)
1001            .next(vm)
1002            .map(|iter_ret| match iter_ret {
1003                PyIterReturn::Return(obj) => PyIterReturn::Return(obj),
1004                PyIterReturn::StopIteration(v) => {
1005                    default_value.map_or(PyIterReturn::StopIteration(v), PyIterReturn::Return)
1006                }
1007            })
1008    }
1009
1010    #[pyfunction]
1011    fn oct(number: ArgIndex, vm: &VirtualMachine) -> PyObjectRef {
1012        let number = number.into_int_ref();
1013        let n = number.as_bigint();
1014        let s = if n.is_negative() {
1015            format!("-0o{:o}", n.abs())
1016        } else {
1017            format!("0o{n:o}")
1018        };
1019
1020        vm.ctx.new_str(s).into()
1021    }
1022
1023    #[pyfunction]
1024    // builtin_ord
1025    fn ord(character: PyObjectRef, vm: &VirtualMachine) -> PyResult<u32> {
1026        let bytes = if let Some(string) = character.downcast_ref::<PyStr>() {
1027            return match string.as_wtf8().code_points().exactly_one() {
1028                Ok(character) => Ok(character.to_u32()),
1029                Err(_) => {
1030                    let string_len = string.char_len();
1031                    Err(vm.new_type_error(format!(
1032                        "ord() expected a character, but string of length {string_len} found"
1033                    )))
1034                }
1035            };
1036        } else if let Some(bytes) = character.downcast_ref::<PyBytes>() {
1037            bytes.as_bytes().to_vec()
1038        } else if let Some(bytearray) = character.downcast_ref::<PyByteArray>() {
1039            bytearray.borrow_buf().to_vec()
1040        } else {
1041            return Err(vm.new_type_error(format!(
1042                "ord() expected string of length 1, but {} found",
1043                character.class().name()
1044            )));
1045        };
1046        let bytes_len = bytes.len();
1047        if bytes_len != 1 {
1048            return Err(vm.new_type_error(format!(
1049                "ord() expected a character, but string of length {bytes_len} found"
1050            )));
1051        }
1052        Ok(u32::from(bytes[0]))
1053    }
1054
1055    #[derive(FromArgs)]
1056    struct PowArgs {
1057        base: PyObjectRef,
1058        exp: PyObjectRef,
1059        #[pyarg(any, optional, name = "mod")]
1060        modulus: Option<PyObjectRef>,
1061    }
1062
1063    #[pyfunction]
1064    fn pow(args: PowArgs, vm: &VirtualMachine) -> PyResult {
1065        let PowArgs {
1066            base: x,
1067            exp: y,
1068            modulus,
1069        } = args;
1070        let modulus = modulus
1071            .as_deref()
1072            .unwrap_or_else(|| vm.ctx.none.as_object());
1073        vm._pow(&x, &y, modulus)
1074    }
1075
1076    #[pyfunction]
1077    pub(super) fn exit(exit_code_arg: OptionalArg<PyObjectRef>, vm: &VirtualMachine) -> PyResult {
1078        let code = exit_code_arg.unwrap_or_else(|| vm.ctx.new_int(0).into());
1079        Err(vm.new_system_exit(vec![code].into()))
1080    }
1081
1082    #[derive(Debug, Default, FromArgs)]
1083    pub struct PrintOptions {
1084        // None means a space; the string is filled in when printing.
1085        #[pyarg(named, default, py_default = "' '")]
1086        sep: Option<PyStrRef>,
1087        // None means a newline; the string is filled in when printing.
1088        #[pyarg(named, default, py_default = "'\\n'")]
1089        end: Option<PyStrRef>,
1090        #[pyarg(named, optional)]
1091        file: Option<PyObjectRef>,
1092        #[pyarg(named, default = ArgIntoBool::FALSE)]
1093        flush: ArgIntoBool,
1094    }
1095
1096    #[pyfunction]
1097    pub fn print(objects: PosArgs, options: PrintOptions, vm: &VirtualMachine) -> PyResult<()> {
1098        let file = match options.file {
1099            Some(f) => f,
1100            None => sys::get_stdout(vm)?,
1101        };
1102        let write = |obj: PyStrRef| vm.call_method(&file, "write", (obj,));
1103
1104        let sep = options.sep.unwrap_or_else(|| vm.ctx.new_str(" "));
1105
1106        let mut first = true;
1107        for object in objects {
1108            if first {
1109                first = false;
1110            } else {
1111                write(sep.clone())?;
1112            }
1113
1114            write(object.str(vm)?)?;
1115        }
1116
1117        let end = options.end.unwrap_or_else(|| vm.ctx.new_str("\n"));
1118        write(end)?;
1119
1120        if options.flush.into() {
1121            vm.call_method(&file, "flush", ())?;
1122        }
1123
1124        Ok(())
1125    }
1126
1127    #[pyfunction]
1128    fn repr(obj: PyObjectRef, vm: &VirtualMachine) -> PyResult<PyStrRef> {
1129        obj.repr(vm)
1130    }
1131
1132    #[pyfunction]
1133    pub fn reversed(sequence: PyObjectRef, vm: &VirtualMachine) -> PyResult {
1134        if let Some(reversed_method) =
1135            vm.get_method(sequence.clone(), identifier!(vm, __reversed__))
1136        {
1137            reversed_method?.call((), vm)
1138        } else {
1139            vm.get_method_or_type_error(sequence.clone(), identifier!(vm, __getitem__), || {
1140                "argument to reversed() must be a sequence".to_owned()
1141            })?;
1142            let len = sequence.length(vm)?;
1143            let obj_iterator = PyReverseSequenceIterator::new(sequence, len);
1144            Ok(obj_iterator.into_pyobject(vm))
1145        }
1146    }
1147
1148    #[derive(FromArgs)]
1149    pub(super) struct RoundArgs {
1150        number: PyObjectRef,
1151        #[pyarg(any, optional)]
1152        ndigits: Option<PyObjectRef>,
1153    }
1154
1155    #[pyfunction]
1156    fn round(RoundArgs { number, ndigits }: RoundArgs, vm: &VirtualMachine) -> PyResult {
1157        let meth = vm
1158            .get_special_method(&number, identifier!(vm, __round__))?
1159            .ok_or_else(|| {
1160                vm.new_type_error(format!(
1161                    "type {} doesn't define __round__ method",
1162                    number.class().slot_name()
1163                ))
1164            })?;
1165        match ndigits {
1166            Some(obj) => {
1167                let ndigits = obj.try_index(vm)?;
1168                meth.invoke((ndigits,), vm)
1169            }
1170            None => {
1171                // without a parameter, the result type is coerced to int
1172                meth.invoke((), vm)
1173            }
1174        }
1175    }
1176
1177    #[pyfunction]
1178    fn setattr(
1179        obj: PyObjectRef,
1180        name: PyObjectRef,
1181        value: PyObjectRef,
1182        vm: &VirtualMachine,
1183    ) -> PyResult<()> {
1184        let attr = name.try_to_ref::<PyStr>(vm).map_err(|_e| {
1185            vm.new_type_error(format!(
1186                "attribute name must be string, not '{}'",
1187                name.class().name()
1188            ))
1189        })?;
1190        obj.set_attr(attr, value, vm)?;
1191        Ok(())
1192    }
1193
1194    // builtin_slice
1195
1196    #[pyfunction]
1197    fn sorted(iterable: PyObjectRef, opts: SortOptions, vm: &VirtualMachine) -> PyResult<PyList> {
1198        // `PySequence_List()`, so the room comes from what the iterable reports
1199        // rather than from its iterator.
1200        let items = vm.extract_elements_sized(&iterable, &|| 0, Ok)?;
1201        let lst = PyList::from(items);
1202        lst.sort(opts, vm)?;
1203        Ok(lst)
1204    }
1205
1206    #[derive(FromArgs)]
1207    pub(super) struct SumArgs {
1208        #[pyarg(positional)]
1209        iterable: ArgIterable,
1210        // The int object needs the VM, so the default is not a literal.
1211        #[pyarg(any, default = 0)]
1212        start: PyObjectRef,
1213    }
1214
1215    #[pyfunction]
1216    fn sum(SumArgs { iterable, start }: SumArgs, vm: &VirtualMachine) -> PyResult {
1217        let mut sum = start;
1218
1219        match_class!(match sum {
1220            PyStr =>
1221                return Err(vm.new_type_error("sum() can't sum strings [use ''.join(seq) instead]")),
1222            PyBytes =>
1223                return Err(vm.new_type_error("sum() can't sum bytes [use b''.join(seq) instead]")),
1224            PyByteArray =>
1225                return Err(
1226                    vm.new_type_error("sum() can't sum bytearray [use b''.join(seq) instead]")
1227                ),
1228            _ => (),
1229        });
1230
1231        for item in iterable.iter(vm)? {
1232            sum = vm._add(&sum, &*item?)?;
1233        }
1234        Ok(sum)
1235    }
1236
1237    #[derive(FromArgs)]
1238    struct ImportArgs {
1239        #[pyarg(any)]
1240        name: PyObjectRef,
1241        #[pyarg(any, optional)]
1242        globals: Option<PyObjectRef>,
1243        #[allow(dead_code)]
1244        #[pyarg(any, optional)]
1245        locals: Option<PyObjectRef>,
1246        // Missing means an empty fromlist.
1247        #[pyarg(any, default, py_default = "()")]
1248        fromlist: Option<PyObjectRef>,
1249        #[pyarg(any, default)]
1250        level: i32,
1251    }
1252
1253    #[pyfunction]
1254    fn __import__(args: ImportArgs, vm: &VirtualMachine) -> PyResult {
1255        // The name is faulted for not being a string by the import itself,
1256        // ahead of the level beside it. = PyImport_ImportModuleLevelObject
1257        let name = args
1258            .name
1259            .downcast_ref::<PyStr>()
1260            .ok_or_else(|| vm.new_type_error("module name must be a string"))?;
1261        crate::import::import_module_level(
1262            name,
1263            args.globals.as_deref(),
1264            args.fromlist,
1265            args.level,
1266            vm,
1267        )
1268    }
1269
1270    #[pyfunction]
1271    fn vars(obj: OptionalArg, vm: &VirtualMachine) -> PyResult {
1272        if let OptionalArg::Present(obj) = obj {
1273            obj.get_attr(identifier!(vm, __dict__), vm)
1274                .map_err(|_| vm.new_type_error("vars() argument must have __dict__ attribute"))
1275        } else {
1276            Ok(vm.current_locals()?.into())
1277        }
1278    }
1279
1280    #[pyfunction]
1281    pub(super) fn __build_class__(
1282        function: PyRef<PyFunction>,
1283        name: PyStrRef,
1284        bases: PosArgs,
1285        mut kwargs: KwArgs,
1286        vm: &VirtualMachine,
1287    ) -> PyResult {
1288        let name_obj: PyObjectRef = name.clone().into();
1289
1290        // Update bases.
1291        let mut new_bases: Option<Vec<PyObjectRef>> = None;
1292        let bases = PyTuple::new_ref(bases.into_vec(), &vm.ctx);
1293        for (i, base) in bases.as_slice().iter().enumerate() {
1294            if base.fast_isinstance(vm.ctx.types.type_type) {
1295                if let Some(bases) = &mut new_bases {
1296                    bases.push(base.clone());
1297                }
1298                continue;
1299            }
1300            let mro_entries = vm.get_attribute_opt(base, identifier!(vm, __mro_entries__))?;
1301            let entries = match mro_entries {
1302                Some(meth) => meth.call((bases.clone(),), vm)?,
1303                None => {
1304                    if let Some(bases) = &mut new_bases {
1305                        bases.push(base.clone());
1306                    }
1307                    continue;
1308                }
1309            };
1310            let entries: PyTupleRef = entries
1311                .downcast()
1312                .map_err(|_| vm.new_type_error("__mro_entries__ must return a tuple"))?;
1313            let new_bases = new_bases.get_or_insert_with(|| bases.as_slice()[..i].to_vec());
1314            new_bases.extend_from_slice(entries.as_slice());
1315        }
1316
1317        let new_bases = new_bases.map(|v| PyTuple::new_ref(v, &vm.ctx));
1318        let (orig_bases, bases) = match new_bases {
1319            Some(new) => (Some(bases), new),
1320            None => (None, bases),
1321        };
1322
1323        // Use downcast_exact to keep ref to old object on error.
1324        let metaclass = kwargs.pop_kwarg("metaclass").map_or_else(
1325            || {
1326                // if there are no bases, use type; else get the type of the first base
1327                Ok(if bases.as_slice().is_empty() {
1328                    vm.ctx.types.type_type.to_owned()
1329                } else {
1330                    bases.as_slice().first().unwrap().class().to_owned()
1331                })
1332            },
1333            |metaclass| {
1334                metaclass
1335                    .downcast_exact::<PyType>(vm)
1336                    .map(|m| m.into_pyref())
1337            },
1338        );
1339
1340        let (metaclass, meta_name) = match metaclass {
1341            Ok(mut metaclass) => {
1342                for base in bases.as_slice() {
1343                    let base_class = base.class();
1344                    // if winner is subtype of tmptype, continue (winner is more derived)
1345                    if metaclass.fast_issubclass(base_class) {
1346                        continue;
1347                    }
1348                    // if tmptype is subtype of winner, update (tmptype is more derived)
1349                    if base_class.fast_issubclass(&metaclass) {
1350                        metaclass = base_class.to_owned();
1351                        continue;
1352                    }
1353                    // Metaclass conflict
1354                    return Err(vm.new_type_error(
1355                        "metaclass conflict: the metaclass of a derived class must be a (non-strict) \
1356                        subclass of the metaclasses of all its bases",
1357                    ));
1358                }
1359                let meta_name = metaclass.slot_name();
1360                (metaclass.to_owned().into(), meta_name.to_owned())
1361            }
1362            Err(obj) => (obj, "<metaclass>".to_owned()),
1363        };
1364
1365        let bases: PyObjectRef = bases.into();
1366
1367        // Prepare uses full __getattribute__ resolution chain.
1368        let namespace = vm
1369            .get_attribute_opt(&metaclass, identifier!(vm, __prepare__))?
1370            .map_or(Ok(vm.ctx.new_dict().into()), |prepare| {
1371                let args = FuncArgs::new(vec![name_obj.clone(), bases.clone()], kwargs.clone());
1372                prepare.call(args, vm)
1373            })?;
1374
1375        // Accept any PyMapping as namespace.
1376        let namespace = ArgMapping::try_from_object(vm, namespace.clone()).map_err(|_| {
1377            vm.new_type_error(format!(
1378                "{}.__prepare__() must return a mapping, not {}",
1379                meta_name,
1380                namespace.class()
1381            ))
1382        })?;
1383
1384        // For PEP 695 classes, set .type_params in namespace before calling the function
1385        if let Ok(type_params) = function
1386            .as_object()
1387            .get_attr(identifier!(vm, __type_params__), vm)
1388            && let Some(type_params_tuple) = type_params.downcast_ref::<PyTuple>()
1389            && !type_params_tuple.as_slice().is_empty()
1390        {
1391            // Set .type_params in namespace so the compiler-generated code can use it
1392            namespace
1393                .as_object()
1394                .set_item(vm.ctx.intern_str(".type_params"), type_params, vm)?;
1395        }
1396
1397        let classcell = function.invoke_with_locals(().into(), Some(namespace.clone()), vm)?;
1398        let classcell = <Option<PyCellRef>>::try_from_object(vm, classcell)?;
1399
1400        if let Some(orig_bases) = orig_bases {
1401            namespace.as_object().set_item(
1402                identifier!(vm, __orig_bases__),
1403                orig_bases.into(),
1404                vm,
1405            )?;
1406        }
1407
1408        // Remove .type_params from namespace before creating the class
1409        namespace
1410            .as_object()
1411            .del_item(vm.ctx.intern_str(".type_params"), vm)
1412            .ok();
1413
1414        let args = FuncArgs::new(vec![name_obj, bases, namespace.into()], kwargs);
1415        let class = metaclass.call(args, vm)?;
1416
1417        // For PEP 695 classes, set __type_params__ on the class from the function
1418        if let Ok(type_params) = function
1419            .as_object()
1420            .get_attr(identifier!(vm, __type_params__), vm)
1421            && let Some(type_params_tuple) = type_params.downcast_ref::<PyTuple>()
1422            && !type_params_tuple.as_slice().is_empty()
1423        {
1424            class.set_attr(identifier!(vm, __type_params__), type_params.clone(), vm)?;
1425            // Also set __parameters__ for compatibility with typing module
1426            class.set_attr(identifier!(vm, __parameters__), type_params, vm)?;
1427        }
1428
1429        // only check cell if cls is a type and cell is a cell object
1430        if let Some(ref classcell) = classcell
1431            && class.fast_isinstance(vm.ctx.types.type_type)
1432        {
1433            let cell_value = classcell.get().ok_or_else(|| {
1434                vm.new_runtime_error(format!(
1435                    "__class__ not set defining {name:?} as {class:?}. Was __classcell__ propagated to type.__new__?"
1436                ))
1437            })?;
1438
1439            if !cell_value.is(&class) {
1440                return Err(vm.new_type_error(format!(
1441                    "__class__ set to {cell_value:?} defining {name:?} as {class:?}"
1442                )));
1443            }
1444        }
1445
1446        Ok(class)
1447    }
1448}
1449
1450pub fn init_module(vm: &VirtualMachine, module: &Py<PyModule>) {
1451    let ctx = &vm.ctx;
1452
1453    let _ = crate::protocol::VecBuffer::make_static_type();
1454
1455    module.__init_methods(vm).unwrap();
1456    builtins::module_exec(vm, module).unwrap();
1457
1458    let debug_mode: bool = vm.state.config.settings.optimize == 0;
1459    // Create dynamic ExceptionGroup with multiple inheritance (BaseExceptionGroup + Exception)
1460    let exception_group = crate::exception_group::exception_group();
1461
1462    extend_module!(vm, module, {
1463        "__debug__" => ctx.new_bool(debug_mode),
1464
1465        "bool" => ctx.types.bool_type.to_owned(),
1466        "bytearray" => ctx.types.bytearray_type.to_owned(),
1467        "bytes" => ctx.types.bytes_type.to_owned(),
1468        "classmethod" => ctx.types.classmethod_type.to_owned(),
1469        "complex" => ctx.types.complex_type.to_owned(),
1470        "dict" => ctx.types.dict_type.to_owned(),
1471        "enumerate" => ctx.types.enumerate_type.to_owned(),
1472        "float" => ctx.types.float_type.to_owned(),
1473        "frozenset" => ctx.types.frozenset_type.to_owned(),
1474        "filter" => ctx.types.filter_type.to_owned(),
1475        "int" => ctx.types.int_type.to_owned(),
1476        "list" => ctx.types.list_type.to_owned(),
1477        "map" => ctx.types.map_type.to_owned(),
1478        "memoryview" => ctx.types.memoryview_type.to_owned(),
1479        "object" => ctx.types.object_type.to_owned(),
1480        "property" => ctx.types.property_type.to_owned(),
1481        "range" => ctx.types.range_type.to_owned(),
1482        "set" => ctx.types.set_type.to_owned(),
1483        "slice" => ctx.types.slice_type.to_owned(),
1484        "staticmethod" => ctx.types.staticmethod_type.to_owned(),
1485        "str" => ctx.types.str_type.to_owned(),
1486        "super" => ctx.types.super_type.to_owned(),
1487        "tuple" => ctx.types.tuple_type.to_owned(),
1488        "type" => ctx.types.type_type.to_owned(),
1489        "zip" => ctx.types.zip_type.to_owned(),
1490
1491        // Constants
1492        "None" => ctx.none(),
1493        "True" => ctx.new_bool(true),
1494        "False" => ctx.new_bool(false),
1495        "NotImplemented" => ctx.not_implemented(),
1496        "Ellipsis" => vm.ctx.ellipsis.clone(),
1497
1498        // ordered by exception_hierarchy.txt
1499        // Exceptions:
1500        "BaseException" => ctx.exceptions.base_exception_type.to_owned(),
1501        "BaseExceptionGroup" => ctx.exceptions.base_exception_group.to_owned(),
1502        "ExceptionGroup" => exception_group.to_owned(),
1503        "SystemExit" => ctx.exceptions.system_exit.to_owned(),
1504        "KeyboardInterrupt" => ctx.exceptions.keyboard_interrupt.to_owned(),
1505        "GeneratorExit" => ctx.exceptions.generator_exit.to_owned(),
1506        "Exception" => ctx.exceptions.exception_type.to_owned(),
1507        "StopIteration" => ctx.exceptions.stop_iteration.to_owned(),
1508        "StopAsyncIteration" => ctx.exceptions.stop_async_iteration.to_owned(),
1509        "ArithmeticError" => ctx.exceptions.arithmetic_error.to_owned(),
1510        "FloatingPointError" => ctx.exceptions.floating_point_error.to_owned(),
1511        "OverflowError" => ctx.exceptions.overflow_error.to_owned(),
1512        "ZeroDivisionError" => ctx.exceptions.zero_division_error.to_owned(),
1513        "AssertionError" => ctx.exceptions.assertion_error.to_owned(),
1514        "AttributeError" => ctx.exceptions.attribute_error.to_owned(),
1515        "BufferError" => ctx.exceptions.buffer_error.to_owned(),
1516        "EOFError" => ctx.exceptions.eof_error.to_owned(),
1517        "ImportError" => ctx.exceptions.import_error.to_owned(),
1518        "ModuleNotFoundError" => ctx.exceptions.module_not_found_error.to_owned(),
1519        "LookupError" => ctx.exceptions.lookup_error.to_owned(),
1520        "IndexError" => ctx.exceptions.index_error.to_owned(),
1521        "KeyError" => ctx.exceptions.key_error.to_owned(),
1522        "MemoryError" => ctx.exceptions.memory_error.to_owned(),
1523        "NameError" => ctx.exceptions.name_error.to_owned(),
1524        "UnboundLocalError" => ctx.exceptions.unbound_local_error.to_owned(),
1525        "OSError" => ctx.exceptions.os_error.to_owned(),
1526        // OSError alias
1527        "IOError" => ctx.exceptions.os_error.to_owned(),
1528        "EnvironmentError" => ctx.exceptions.os_error.to_owned(),
1529        "BlockingIOError" => ctx.exceptions.blocking_io_error.to_owned(),
1530        "ChildProcessError" => ctx.exceptions.child_process_error.to_owned(),
1531        "ConnectionError" => ctx.exceptions.connection_error.to_owned(),
1532        "BrokenPipeError" => ctx.exceptions.broken_pipe_error.to_owned(),
1533        "ConnectionAbortedError" => ctx.exceptions.connection_aborted_error.to_owned(),
1534        "ConnectionRefusedError" => ctx.exceptions.connection_refused_error.to_owned(),
1535        "ConnectionResetError" => ctx.exceptions.connection_reset_error.to_owned(),
1536        "FileExistsError" => ctx.exceptions.file_exists_error.to_owned(),
1537        "FileNotFoundError" => ctx.exceptions.file_not_found_error.to_owned(),
1538        "InterruptedError" => ctx.exceptions.interrupted_error.to_owned(),
1539        "IsADirectoryError" => ctx.exceptions.is_a_directory_error.to_owned(),
1540        "NotADirectoryError" => ctx.exceptions.not_a_directory_error.to_owned(),
1541        "PermissionError" => ctx.exceptions.permission_error.to_owned(),
1542        "ProcessLookupError" => ctx.exceptions.process_lookup_error.to_owned(),
1543        "TimeoutError" => ctx.exceptions.timeout_error.to_owned(),
1544        "ReferenceError" => ctx.exceptions.reference_error.to_owned(),
1545        "RuntimeError" => ctx.exceptions.runtime_error.to_owned(),
1546        "PythonFinalizationError" => ctx.exceptions.python_finalization_error.to_owned(),
1547        "NotImplementedError" => ctx.exceptions.not_implemented_error.to_owned(),
1548        "RecursionError" => ctx.exceptions.recursion_error.to_owned(),
1549        "SyntaxError" =>  ctx.exceptions.syntax_error.to_owned(),
1550        "_IncompleteInputError" =>  ctx.exceptions.incomplete_input_error.to_owned(),
1551        "IndentationError" =>  ctx.exceptions.indentation_error.to_owned(),
1552        "TabError" =>  ctx.exceptions.tab_error.to_owned(),
1553        "SystemError" => ctx.exceptions.system_error.to_owned(),
1554        "TypeError" => ctx.exceptions.type_error.to_owned(),
1555        "ValueError" => ctx.exceptions.value_error.to_owned(),
1556        "UnicodeError" => ctx.exceptions.unicode_error.to_owned(),
1557        "UnicodeDecodeError" => ctx.exceptions.unicode_decode_error.to_owned(),
1558        "UnicodeEncodeError" => ctx.exceptions.unicode_encode_error.to_owned(),
1559        "UnicodeTranslateError" => ctx.exceptions.unicode_translate_error.to_owned(),
1560
1561        // Warnings
1562        "Warning" => ctx.exceptions.warning.to_owned(),
1563        "DeprecationWarning" => ctx.exceptions.deprecation_warning.to_owned(),
1564        "PendingDeprecationWarning" => ctx.exceptions.pending_deprecation_warning.to_owned(),
1565        "RuntimeWarning" => ctx.exceptions.runtime_warning.to_owned(),
1566        "SyntaxWarning" => ctx.exceptions.syntax_warning.to_owned(),
1567        "UserWarning" => ctx.exceptions.user_warning.to_owned(),
1568        "FutureWarning" => ctx.exceptions.future_warning.to_owned(),
1569        "ImportWarning" => ctx.exceptions.import_warning.to_owned(),
1570        "UnicodeWarning" => ctx.exceptions.unicode_warning.to_owned(),
1571        "BytesWarning" => ctx.exceptions.bytes_warning.to_owned(),
1572        "ResourceWarning" => ctx.exceptions.resource_warning.to_owned(),
1573        "EncodingWarning" => ctx.exceptions.encoding_warning.to_owned(),
1574    });
1575
1576    #[cfg(feature = "jit")]
1577    extend_module!(vm, module, {
1578        "JitError" => ctx.exceptions.jit_error.to_owned(),
1579    });
1580
1581    #[cfg(windows)]
1582    extend_module!(vm, module, {
1583        // OSError alias for Windows
1584        "WindowsError" => ctx.exceptions.os_error.to_owned(),
1585    });
1586}