Skip to main content

rustpython_vm/vm/
vm_new.rs

1use crate::{
2    AsObject, Py, PyObject, PyObjectRef, PyRef, PyResult,
3    builtins::{
4        PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef, PyModule, PyOSError, PyStrRef,
5        PyType, PyTypeRef,
6        builtin_func::PyNativeFunction,
7        descriptor::PyMethodDescriptor,
8        tuple::{IntoPyTuple, PyTupleRef},
9    },
10    convert::{ToPyException, ToPyObject},
11    exceptions::OSErrorBuilder,
12    function::{IntoPyNativeFn, PyMethodFlags},
13    scope::Scope,
14    vm::VirtualMachine,
15};
16use rustpython_common::wtf8::Wtf8Buf;
17use rustpython_compiler_core::SourceLocation;
18
19macro_rules! define_exception_fn {
20    (
21        fn $fn_name:ident, $attr:ident, $python_repr:ident
22    ) => {
23        #[doc = concat!(
24                    "Create a new python ",
25                    stringify!($python_repr),
26                    " object.\nUseful for raising errors from python functions implemented in rust."
27                )]
28        pub fn $fn_name(&self, msg: impl Into<Wtf8Buf>) -> PyBaseExceptionRef
29        {
30            let err = self.ctx.exceptions.$attr.to_owned();
31            self.new_exception_msg(err, msg.into())
32        }
33    };
34}
35
36/// Collection of object creation helpers
37impl VirtualMachine {
38    /// Create a new python object
39    pub fn new_pyobj(&self, value: impl ToPyObject) -> PyObjectRef {
40        value.to_pyobject(self)
41    }
42
43    pub fn new_tuple(&self, value: impl IntoPyTuple) -> PyTupleRef {
44        value.into_pytuple(self)
45    }
46
47    pub fn new_module(
48        &self,
49        name: &str,
50        dict: PyDictRef,
51        doc: Option<PyStrRef>,
52    ) -> PyRef<PyModule> {
53        let module = PyRef::new_ref(
54            PyModule::new(),
55            self.ctx.types.module_type.to_owned(),
56            Some(dict),
57        );
58        module.init_dict(self.ctx.intern_str(name), doc, self);
59        module
60    }
61
62    pub fn new_scope_with_builtins(&self) -> Scope {
63        Scope::with_builtins(None, self.ctx.new_dict(), self)
64    }
65
66    pub fn new_scope_with_main(&self) -> PyResult<Scope> {
67        let scope = self.new_scope_with_builtins();
68        let main_module = self.new_module("__main__", scope.globals.clone(), None);
69
70        self.sys_module.get_attr("modules", self)?.set_item(
71            "__main__",
72            main_module.into(),
73            self,
74        )?;
75
76        Ok(scope)
77    }
78
79    pub fn new_function<F, FKind>(&self, name: &'static str, f: F) -> PyRef<PyNativeFunction>
80    where
81        F: IntoPyNativeFn<FKind>,
82    {
83        let def = self
84            .ctx
85            .new_method_def(name, f, PyMethodFlags::empty(), None);
86        def.build_function(self)
87    }
88
89    pub fn new_method<F, FKind>(
90        &self,
91        name: &'static str,
92        class: &'static Py<PyType>,
93        f: F,
94    ) -> PyRef<PyMethodDescriptor>
95    where
96        F: IntoPyNativeFn<FKind>,
97    {
98        let def = self
99            .ctx
100            .new_method_def(name, f, PyMethodFlags::METHOD, None);
101        def.build_method(class, self)
102    }
103
104    /// Instantiate an exception with arguments.
105    /// This function should only be used with builtin exception types; if a user-defined exception
106    /// type is passed in, it may not be fully initialized; try using
107    /// [`vm.invoke_exception()`][Self::invoke_exception] or
108    /// [`exceptions::ExceptionCtor`][crate::exceptions::ExceptionCtor] instead.
109    pub fn new_exception(&self, exc_type: PyTypeRef, args: Vec<PyObjectRef>) -> PyBaseExceptionRef {
110        debug_assert_eq!(
111            exc_type.slots.basicsize,
112            core::mem::size_of::<PyBaseException>(),
113            "vm.new_exception() is only for exception types without additional payload. The given type '{}' is not allowed. Use vm.new_os_subtype_error() for OSError subtypes.",
114            exc_type.name()
115        );
116
117        PyRef::new_ref(
118            PyBaseException::new(args, self),
119            exc_type,
120            Some(self.ctx.new_dict()),
121        )
122    }
123
124    pub fn new_os_error(&self, msg: impl ToPyObject) -> PyRef<PyBaseException> {
125        self.new_os_subtype_error(self.ctx.exceptions.os_error.to_owned(), None, msg)
126            .upcast()
127    }
128
129    pub fn new_os_subtype_error(
130        &self,
131        exc_type: PyTypeRef,
132        errno: Option<i32>,
133        msg: impl ToPyObject,
134    ) -> PyRef<PyOSError> {
135        debug_assert_eq!(exc_type.slots.basicsize, core::mem::size_of::<PyOSError>());
136
137        OSErrorBuilder::with_subtype(exc_type, errno, msg, self).build(self)
138    }
139
140    /// Instantiate an exception with no arguments.
141    /// This function should only be used with builtin exception types; if a user-defined exception
142    /// type is passed in, it may not be fully initialized; try using
143    /// [`vm.invoke_exception()`][Self::invoke_exception] or
144    /// [`exceptions::ExceptionCtor`][crate::exceptions::ExceptionCtor] instead.
145    pub fn new_exception_empty(&self, exc_type: PyTypeRef) -> PyBaseExceptionRef {
146        self.new_exception(exc_type, vec![])
147    }
148
149    /// Instantiate an exception with `msg` as the only argument.
150    /// This function should only be used with builtin exception types; if a user-defined exception
151    /// type is passed in, it may not be fully initialized; try using
152    /// [`vm.invoke_exception()`][Self::invoke_exception] or
153    /// [`exceptions::ExceptionCtor`][crate::exceptions::ExceptionCtor] instead.
154    pub fn new_exception_msg(&self, exc_type: PyTypeRef, msg: Wtf8Buf) -> PyBaseExceptionRef {
155        self.new_exception(exc_type, vec![self.ctx.new_str(msg).into()])
156    }
157
158    /// Instantiate an exception with `msg` as the only argument and `dict` for object
159    /// This function should only be used with builtin exception types; if a user-defined exception
160    /// type is passed in, it may not be fully initialized; try using
161    /// [`vm.invoke_exception()`][Self::invoke_exception] or
162    /// [`exceptions::ExceptionCtor`][crate::exceptions::ExceptionCtor] instead.
163    pub fn new_exception_msg_dict(
164        &self,
165        exc_type: PyTypeRef,
166        msg: Wtf8Buf,
167        dict: PyDictRef,
168    ) -> PyBaseExceptionRef {
169        PyRef::new_ref(
170            // TODO: this constructor might be invalid, because multiple
171            // exception (even builtin ones) are using custom constructors,
172            // see `OSError` as an example:
173            PyBaseException::new(vec![self.ctx.new_str(msg).into()], self),
174            exc_type,
175            Some(dict),
176        )
177    }
178
179    pub fn new_no_attribute_error(&self, obj: PyObjectRef, name: PyStrRef) -> PyBaseExceptionRef {
180        let msg = format!(
181            "'{}' object has no attribute '{}'",
182            obj.class().name(),
183            name
184        );
185        let attribute_error = self.new_attribute_error(msg);
186
187        // Use existing set_attribute_error_context function
188        self.set_attribute_error_context(&attribute_error, obj, name);
189
190        attribute_error
191    }
192
193    pub fn new_name_error(&self, msg: impl Into<Wtf8Buf>, name: PyStrRef) -> PyBaseExceptionRef {
194        let name_error_type = self.ctx.exceptions.name_error.to_owned();
195        let name_error = self.new_exception_msg(name_error_type, msg.into());
196        name_error.as_object().set_attr("name", name, self).unwrap();
197        name_error
198    }
199
200    pub fn new_unsupported_unary_error(&self, a: &PyObject, op: &str) -> PyBaseExceptionRef {
201        self.new_type_error(format!(
202            "bad operand type for {}: '{}'",
203            op,
204            a.class().name()
205        ))
206    }
207
208    pub fn new_unsupported_bin_op_error(
209        &self,
210        a: &PyObject,
211        b: &PyObject,
212        op: &str,
213    ) -> PyBaseExceptionRef {
214        self.new_type_error(format!(
215            "unsupported operand type(s) for {}: '{}' and '{}'",
216            op,
217            a.class().name(),
218            b.class().name()
219        ))
220    }
221
222    pub fn new_unsupported_ternary_op_error(
223        &self,
224        a: &PyObject,
225        b: &PyObject,
226        c: &PyObject,
227        op: &str,
228    ) -> PyBaseExceptionRef {
229        self.new_type_error(format!(
230            "Unsupported operand types for '{}': '{}', '{}', and '{}'",
231            op,
232            a.class().name(),
233            b.class().name(),
234            c.class().name()
235        ))
236    }
237
238    /// Create a new OSError from the last OS error.
239    ///
240    /// On windows, windows-sys errors are expected to be handled by this function.
241    /// This is identical to `new_last_errno_error` on non-Windows platforms.
242    pub fn new_last_os_error(&self) -> PyBaseExceptionRef {
243        let err = std::io::Error::last_os_error();
244        err.to_pyexception(self)
245    }
246
247    /// Create a new OSError from the last POSIX errno.
248    ///
249    /// On windows, CRT errno are expected to be handled by this function.
250    /// This is identical to `new_last_os_error` on non-Windows platforms.
251    pub fn new_last_errno_error(&self) -> PyBaseExceptionRef {
252        let err = crate::common::os::errno_io_error();
253        err.to_pyexception(self)
254    }
255
256    pub fn new_errno_error(&self, errno: i32, msg: impl ToPyObject) -> PyRef<PyOSError> {
257        let exc_type = crate::exceptions::errno_to_exc_type(errno, self)
258            .unwrap_or(self.ctx.exceptions.os_error);
259
260        self.new_os_subtype_error(exc_type.to_owned(), Some(errno), msg)
261    }
262
263    pub fn new_unicode_decode_error_real(
264        &self,
265        encoding: PyStrRef,
266        object: PyBytesRef,
267        start: usize,
268        end: usize,
269        reason: PyStrRef,
270    ) -> PyBaseExceptionRef {
271        let start = self.ctx.new_int(start);
272        let end = self.ctx.new_int(end);
273        let exc = self.new_exception(
274            self.ctx.exceptions.unicode_decode_error.to_owned(),
275            vec![
276                encoding.clone().into(),
277                object.clone().into(),
278                start.clone().into(),
279                end.clone().into(),
280                reason.clone().into(),
281            ],
282        );
283        exc.as_object()
284            .set_attr("encoding", encoding, self)
285            .unwrap();
286        exc.as_object().set_attr("object", object, self).unwrap();
287        exc.as_object().set_attr("start", start, self).unwrap();
288        exc.as_object().set_attr("end", end, self).unwrap();
289        exc.as_object().set_attr("reason", reason, self).unwrap();
290        exc
291    }
292
293    pub fn new_unicode_encode_error_real(
294        &self,
295        encoding: PyStrRef,
296        object: PyStrRef,
297        start: usize,
298        end: usize,
299        reason: PyStrRef,
300    ) -> PyBaseExceptionRef {
301        let start = self.ctx.new_int(start);
302        let end = self.ctx.new_int(end);
303        let exc = self.new_exception(
304            self.ctx.exceptions.unicode_encode_error.to_owned(),
305            vec![
306                encoding.clone().into(),
307                object.clone().into(),
308                start.clone().into(),
309                end.clone().into(),
310                reason.clone().into(),
311            ],
312        );
313        exc.as_object()
314            .set_attr("encoding", encoding, self)
315            .unwrap();
316        exc.as_object().set_attr("object", object, self).unwrap();
317        exc.as_object().set_attr("start", start, self).unwrap();
318        exc.as_object().set_attr("end", end, self).unwrap();
319        exc.as_object().set_attr("reason", reason, self).unwrap();
320        exc
321    }
322
323    // TODO: don't take ownership should make the success path faster
324    pub fn new_key_error(&self, obj: PyObjectRef) -> PyBaseExceptionRef {
325        let key_error = self.ctx.exceptions.key_error.to_owned();
326        self.new_exception(key_error, vec![obj])
327    }
328
329    #[cfg(any(feature = "parser", feature = "compiler"))]
330    pub fn new_syntax_error_maybe_incomplete(
331        &self,
332        error: &crate::compiler::CompileError,
333        source: Option<&str>,
334        allow_incomplete: bool,
335    ) -> PyBaseExceptionRef {
336        let incomplete_or_syntax = |allow| -> &'static Py<crate::builtins::PyType> {
337            if allow {
338                self.ctx.exceptions.incomplete_input_error
339            } else {
340                self.ctx.exceptions.syntax_error
341            }
342        };
343
344        let syntax_error_type = match &error {
345            #[cfg(feature = "parser")]
346            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
347                error:
348                    ruff_python_parser::ParseErrorType::Lexical(
349                        ruff_python_parser::LexicalErrorType::IndentationError,
350                    ),
351                ..
352            }) => {
353                // Detect tab/space mixing to raise TabError instead of IndentationError.
354                // This checks both within a single line and across different lines.
355                let is_tab_error = source.is_some_and(|source| {
356                    let mut has_space_indent = false;
357                    let mut has_tab_indent = false;
358                    for line in source.lines() {
359                        let indent: Vec<u8> = line
360                            .bytes()
361                            .take_while(|&b| b == b' ' || b == b'\t')
362                            .collect();
363                        if indent.is_empty() {
364                            continue;
365                        }
366                        if indent.contains(&b' ') && indent.contains(&b'\t') {
367                            return true;
368                        }
369                        if indent.contains(&b' ') {
370                            has_space_indent = true;
371                        }
372                        if indent.contains(&b'\t') {
373                            has_tab_indent = true;
374                        }
375                    }
376                    has_space_indent && has_tab_indent
377                });
378                if is_tab_error {
379                    self.ctx.exceptions.tab_error
380                } else {
381                    self.ctx.exceptions.indentation_error
382                }
383            }
384            #[cfg(feature = "parser")]
385            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
386                error: ruff_python_parser::ParseErrorType::UnexpectedIndentation,
387                ..
388            }) => self.ctx.exceptions.indentation_error,
389            #[cfg(feature = "parser")]
390            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
391                error:
392                    ruff_python_parser::ParseErrorType::Lexical(
393                        ruff_python_parser::LexicalErrorType::Eof,
394                    ),
395                ..
396            }) => incomplete_or_syntax(allow_incomplete),
397            // Unclosed bracket errors (converted from Eof by from_ruff_parse_error)
398            #[cfg(feature = "parser")]
399            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
400                is_unclosed_bracket: true,
401                ..
402            }) => incomplete_or_syntax(allow_incomplete),
403            #[cfg(feature = "parser")]
404            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
405                error:
406                    ruff_python_parser::ParseErrorType::Lexical(
407                        ruff_python_parser::LexicalErrorType::FStringError(
408                            ruff_python_parser::InterpolatedStringErrorType::UnterminatedTripleQuotedString,
409                        ),
410                    ),
411                ..
412            }) => incomplete_or_syntax(allow_incomplete),
413            #[cfg(feature = "parser")]
414            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
415                error:
416                    ruff_python_parser::ParseErrorType::Lexical(
417                        ruff_python_parser::LexicalErrorType::UnclosedStringError,
418                    ),
419                raw_location,
420                ..
421            }) => {
422                if allow_incomplete {
423                    let mut is_incomplete = false;
424
425                    if let Some(source) = source {
426                        let loc = raw_location.start().to_usize();
427                        let mut iter = source.chars();
428                        if let Some(quote) = iter.nth(loc)
429                            && iter.next() == Some(quote)
430                            && iter.next() == Some(quote)
431                        {
432                            is_incomplete = true;
433                        }
434                    }
435
436                    incomplete_or_syntax(is_incomplete)
437                } else {
438                    self.ctx.exceptions.syntax_error
439                }
440            }
441            #[cfg(feature = "parser")]
442            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
443                error: ruff_python_parser::ParseErrorType::OtherError(s),
444                raw_location,
445                ..
446            }) => {
447                if s.starts_with("Expected an indented block after") {
448                    if allow_incomplete {
449                        // Check that all chars in the error are whitespace, if so, the source is
450                        // incomplete. Otherwise, we've found code that might violates
451                        // indentation rules.
452                        let mut is_incomplete = true;
453                        if let Some(source) = source {
454                            let start = raw_location.start().to_usize();
455                            let end = raw_location.end().to_usize();
456                            let mut iter = source.chars();
457                            iter.nth(start);
458                            for _ in start..end {
459                                if let Some(c) = iter.next() {
460                                    if !c.is_ascii_whitespace() {
461                                        is_incomplete = false;
462                                    }
463                                } else {
464                                    break;
465                                }
466                            }
467                        }
468
469                        if is_incomplete {
470                            self.ctx.exceptions.incomplete_input_error
471                        } else {
472                            self.ctx.exceptions.indentation_error // not syntax_error
473                        }
474                    } else {
475                        self.ctx.exceptions.indentation_error
476                    }
477                } else {
478                    self.ctx.exceptions.syntax_error
479                }
480            }
481            _ => self.ctx.exceptions.syntax_error,
482        }
483        .to_owned();
484
485        // TODO: replace to SourceCode
486        fn get_statement(source: &str, loc: Option<SourceLocation>) -> Option<String> {
487            let line = source
488                .split('\n')
489                .nth(loc?.line.to_zero_indexed())?
490                .trim_end_matches('\r')
491                .to_owned();
492            Some(line + "\n")
493        }
494
495        let statement = if let Some(source) = source {
496            get_statement(source, error.location())
497        } else {
498            None
499        };
500
501        let mut msg = error.to_string();
502        if let Some(msg) = msg.get_mut(..1) {
503            msg.make_ascii_lowercase();
504        }
505        let mut narrow_caret = false;
506        match error {
507            #[cfg(feature = "parser")]
508            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
509                error:
510                    ruff_python_parser::ParseErrorType::FStringError(
511                        ruff_python_parser::InterpolatedStringErrorType::UnterminatedString,
512                    )
513                    | ruff_python_parser::ParseErrorType::Lexical(
514                        ruff_python_parser::LexicalErrorType::FStringError(
515                            ruff_python_parser::InterpolatedStringErrorType::UnterminatedString,
516                        ),
517                    ),
518                ..
519            }) => {
520                msg = "unterminated f-string literal".to_owned();
521            }
522            #[cfg(feature = "parser")]
523            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
524                error:
525                    ruff_python_parser::ParseErrorType::FStringError(
526                        ruff_python_parser::InterpolatedStringErrorType::UnterminatedTripleQuotedString,
527                    )
528                    | ruff_python_parser::ParseErrorType::Lexical(
529                        ruff_python_parser::LexicalErrorType::FStringError(
530                            ruff_python_parser::InterpolatedStringErrorType::UnterminatedTripleQuotedString,
531                        ),
532                    ),
533                ..
534            }) => {
535                msg = "unterminated triple-quoted f-string literal".to_owned();
536            }
537            #[cfg(feature = "parser")]
538            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
539                error:
540                    ruff_python_parser::ParseErrorType::FStringError(_)
541                    | ruff_python_parser::ParseErrorType::Lexical(
542                        ruff_python_parser::LexicalErrorType::FStringError(_),
543                    ),
544                ..
545            }) => {
546                // Replace backticks with single quotes to match CPython's error messages
547                msg = msg.replace('`', "'");
548                msg.insert_str(0, "invalid syntax: ");
549            }
550            #[cfg(feature = "parser")]
551            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
552                error: ruff_python_parser::ParseErrorType::UnexpectedExpressionToken,
553                ..
554            }) => msg.insert_str(0, "invalid syntax: "),
555            #[cfg(feature = "parser")]
556            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
557                error:
558                    ruff_python_parser::ParseErrorType::Lexical(
559                        ruff_python_parser::LexicalErrorType::UnrecognizedToken { .. },
560                    )
561                    | ruff_python_parser::ParseErrorType::SimpleStatementsOnSameLine
562                    | ruff_python_parser::ParseErrorType::SimpleAndCompoundStatementOnSameLine
563                    | ruff_python_parser::ParseErrorType::ExpectedToken { .. }
564                    | ruff_python_parser::ParseErrorType::ExpectedExpression,
565                ..
566            }) => {
567                msg = "invalid syntax".to_owned();
568            }
569            #[cfg(feature = "parser")]
570            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
571                error: ruff_python_parser::ParseErrorType::InvalidStarredExpressionUsage,
572                ..
573            }) => {
574                msg = "invalid syntax".to_owned();
575                narrow_caret = true;
576            }
577            #[cfg(feature = "parser")]
578            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
579                error: ruff_python_parser::ParseErrorType::InvalidDeleteTarget,
580                ..
581            }) => {
582                msg = "invalid syntax".to_owned();
583            }
584            #[cfg(feature = "parser")]
585            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
586                error:
587                    ruff_python_parser::ParseErrorType::Lexical(
588                        ruff_python_parser::LexicalErrorType::LineContinuationError,
589                    ),
590                ..
591            }) => {
592                msg = "unexpected character after line continuation".to_owned();
593            }
594            #[cfg(feature = "parser")]
595            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
596                error:
597                    ruff_python_parser::ParseErrorType::Lexical(
598                        ruff_python_parser::LexicalErrorType::UnclosedStringError,
599                    ),
600                ..
601            }) => {
602                msg = "unterminated string".to_owned();
603            }
604            #[cfg(feature = "parser")]
605            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
606                error: ruff_python_parser::ParseErrorType::OtherError(s),
607                ..
608            }) if s.eq_ignore_ascii_case("bytes literal cannot be mixed with non-bytes literals") => {
609                msg = "cannot mix bytes and nonbytes literals".to_owned();
610            }
611            #[cfg(feature = "parser")]
612            crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
613                error: ruff_python_parser::ParseErrorType::OtherError(s),
614                ..
615            }) if s.starts_with("Expected an identifier, but found a keyword") => {
616                msg = "invalid syntax".to_owned();
617            }
618            _ => {}
619        }
620        if syntax_error_type.is(self.ctx.exceptions.tab_error) {
621            msg = "inconsistent use of tabs and spaces in indentation".to_owned();
622        }
623        let syntax_error = self.new_exception_msg(syntax_error_type, msg.into());
624        let (lineno, offset) = error.python_location();
625        let lineno = self.ctx.new_int(lineno);
626        let offset = self.ctx.new_int(offset);
627        syntax_error
628            .as_object()
629            .set_attr("lineno", lineno, self)
630            .unwrap();
631        syntax_error
632            .as_object()
633            .set_attr("offset", offset, self)
634            .unwrap();
635
636        // Set end_lineno and end_offset if available
637        if let Some((end_lineno, end_offset)) = error.python_end_location() {
638            let (end_lineno, end_offset) = if narrow_caret {
639                let (l, o) = error.python_location();
640                (l, o + 1)
641            } else {
642                (end_lineno, end_offset)
643            };
644            let end_lineno = self.ctx.new_int(end_lineno);
645            let end_offset = self.ctx.new_int(end_offset);
646            syntax_error
647                .as_object()
648                .set_attr("end_lineno", end_lineno, self)
649                .unwrap();
650            syntax_error
651                .as_object()
652                .set_attr("end_offset", end_offset, self)
653                .unwrap();
654        }
655
656        syntax_error
657            .as_object()
658            .set_attr("text", statement.to_pyobject(self), self)
659            .unwrap();
660        syntax_error
661            .as_object()
662            .set_attr("filename", self.ctx.new_str(error.source_path()), self)
663            .unwrap();
664
665        // Set _metadata for keyword typo suggestions in traceback module.
666        // Format: (start_line, col_offset, source_code)
667        // start_line=0 means "include all lines from beginning" which provides
668        // full context needed by _find_keyword_typos to compile-check suggestions.
669        if let Some(source) = source {
670            let metadata = self.ctx.new_tuple(vec![
671                self.ctx.new_int(0).into(),
672                self.ctx.new_int(0).into(),
673                self.ctx.new_str(source).into(),
674            ]);
675            syntax_error
676                .as_object()
677                .set_attr("_metadata", metadata, self)
678                .unwrap();
679        }
680
681        syntax_error
682    }
683
684    #[cfg(any(feature = "parser", feature = "compiler"))]
685    pub fn new_syntax_error(
686        &self,
687        error: &crate::compiler::CompileError,
688        source: Option<&str>,
689    ) -> PyBaseExceptionRef {
690        self.new_syntax_error_maybe_incomplete(error, source, false)
691    }
692
693    pub fn new_import_error(
694        &self,
695        msg: impl Into<Wtf8Buf>,
696        name: impl Into<PyStrRef>,
697    ) -> PyBaseExceptionRef {
698        let import_error = self.ctx.exceptions.import_error.to_owned();
699        let exc = self.new_exception_msg(import_error, msg.into());
700        exc.as_object().set_attr("name", name.into(), self).unwrap();
701        exc
702    }
703
704    pub fn new_stop_iteration(&self, value: Option<PyObjectRef>) -> PyBaseExceptionRef {
705        let dict = self.ctx.new_dict();
706        let args = if let Some(value) = value {
707            // manually set `value` attribute like StopIteration.__init__
708            dict.set_item("value", value.clone(), self)
709                .expect("dict.__setitem__ never fails");
710            vec![value]
711        } else {
712            Vec::new()
713        };
714
715        PyRef::new_ref(
716            PyBaseException::new(args, self),
717            self.ctx.exceptions.stop_iteration.to_owned(),
718            Some(dict),
719        )
720    }
721
722    fn new_downcast_error(
723        &self,
724        msg: &'static str,
725        error_type: &'static Py<PyType>,
726        class: &Py<PyType>,
727        obj: &PyObject, // the impl Borrow allows to pass PyObjectRef or &PyObject
728    ) -> PyBaseExceptionRef {
729        let actual_class = obj.class();
730        let actual_type = &*actual_class.name();
731        let expected_type = &*class.name();
732        let msg = format!("Expected {msg} '{expected_type}' but '{actual_type}' found.");
733        #[cfg(debug_assertions)]
734        let msg = if class.get_id() == actual_class.get_id() {
735            let mut msg = msg;
736            msg += " It might mean this type doesn't support subclassing very well. e.g. Did you forget to add `#[pyclass(with(Constructor))]`?";
737            msg
738        } else {
739            msg
740        };
741        self.new_exception_msg(error_type.to_owned(), msg.into())
742    }
743
744    pub(crate) fn new_downcast_runtime_error(
745        &self,
746        class: &Py<PyType>,
747        obj: &impl AsObject,
748    ) -> PyBaseExceptionRef {
749        self.new_downcast_error(
750            "payload",
751            self.ctx.exceptions.runtime_error,
752            class,
753            obj.as_object(),
754        )
755    }
756
757    pub(crate) fn new_downcast_type_error(
758        &self,
759        class: &Py<PyType>,
760        obj: &impl AsObject,
761    ) -> PyBaseExceptionRef {
762        self.new_downcast_error(
763            "type",
764            self.ctx.exceptions.type_error,
765            class,
766            obj.as_object(),
767        )
768    }
769
770    define_exception_fn!(fn new_lookup_error, lookup_error, LookupError);
771    define_exception_fn!(fn new_eof_error, eof_error, EOFError);
772    define_exception_fn!(fn new_attribute_error, attribute_error, AttributeError);
773    define_exception_fn!(fn new_type_error, type_error, TypeError);
774    define_exception_fn!(fn new_system_error, system_error, SystemError);
775
776    // TODO: remove & replace with new_unicode_decode_error_real
777    define_exception_fn!(fn new_unicode_decode_error, unicode_decode_error, UnicodeDecodeError);
778
779    // TODO: remove & replace with new_unicode_encode_error_real
780    define_exception_fn!(fn new_unicode_encode_error, unicode_encode_error, UnicodeEncodeError);
781
782    define_exception_fn!(fn new_value_error, value_error, ValueError);
783
784    define_exception_fn!(fn new_buffer_error, buffer_error, BufferError);
785    define_exception_fn!(fn new_index_error, index_error, IndexError);
786    define_exception_fn!(
787     fn new_not_implemented_error,
788        not_implemented_error,
789        NotImplementedError
790    );
791    define_exception_fn!(fn new_recursion_error, recursion_error, RecursionError);
792    define_exception_fn!(fn new_zero_division_error, zero_division_error, ZeroDivisionError);
793    define_exception_fn!(fn new_overflow_error, overflow_error, OverflowError);
794    define_exception_fn!(fn new_runtime_error, runtime_error, RuntimeError);
795    define_exception_fn!(fn new_python_finalization_error, python_finalization_error, PythonFinalizationError);
796    define_exception_fn!(fn new_memory_error, memory_error, MemoryError);
797}