1#[cfg(feature = "parser")]
2use ruff_python_ast::token::TokenKind;
3
4use ruff_python_parser::{InterpolatedStringErrorType, LexicalErrorType, ParseErrorType};
5
6use rustpython_common::wtf8::Wtf8Buf;
7use rustpython_compiler_core::SourceLocation;
8
9use core::ops::RangeInclusive;
10
11#[cfg(feature = "parser")]
12use rustpython_compiler::{CompileError, ParseError, is_blank_python_source};
13
14use crate::{
15 AsObject, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult,
16 builtins::{
17 PyAttributeError, PyBaseException, PyBaseExceptionRef, PyBytesRef, PyDictRef,
18 PyImportError, PyMemoryError, PyModule, PyNameError, PyOSError, PyStopIteration, PyStrRef,
19 PySyntaxError, PySystemExit, PyType, PyTypeRef,
20 builtin_func::PyNativeFunction,
21 descriptor::PyMethodDescriptor,
22 tuple::{IntoPyTuple, PyTupleRef},
23 },
24 convert::{ToPyException, ToPyObject},
25 exceptions::OSErrorBuilder,
26 function::{
27 FuncArgs, IntoPyNativeFn, PyMethodFlags, arity_message, unexpected_keyword_message,
28 },
29 scope::Scope,
30 set_attrs,
31 types::{Constructor, Initializer},
32 vm::VirtualMachine,
33};
34
35macro_rules! define_exception_fn {
36 (
37 fn $fn_name:ident, $attr:ident, $python_repr:ident
38 ) => {
39 #[doc = concat!(
40 "Create a new python ",
41 stringify!($python_repr),
42 " object.\nUseful for raising errors from python functions implemented in rust."
43 )]
44 pub fn $fn_name(&self, msg: impl Into<Wtf8Buf>) -> $crate::builtins::PyBaseExceptionRef {
45 let err = self.ctx.exceptions.$attr.to_owned();
46 self.new_simple_exception(err, vec![self.ctx.new_str(msg.into()).into()])
47 }
48 };
49}
50
51#[derive(Clone, Debug)]
52struct SyntaxErrorInfo {
53 msg: String,
54 narrow_caret: bool,
55}
56
57impl SyntaxErrorInfo {
58 #[must_use]
59 const fn new(msg: String, narrow_caret: bool) -> Self {
60 Self { msg, narrow_caret }
61 }
62
63 #[cfg(feature = "parser")]
64 #[must_use]
65 const fn handle_expected_token(expected: TokenKind, found: TokenKind) -> &'static str {
66 match (expected, found) {
67 (TokenKind::Colon, TokenKind::Newline) => "expected ':'",
68
69 (TokenKind::Lpar, _) => "expected '('",
70
71 (TokenKind::Else, y) if !matches!(y, TokenKind::Colon) => {
72 "expected 'else' after 'if' expression"
73 }
74
75 _ => "invalid syntax",
76 }
77 }
78
79 #[cfg(feature = "parser")]
80 fn analyze_compile_error(&mut self, compile_error: &CompileError) {
81 let CompileError::Parse(ParseError {
82 error, location, ..
83 }) = compile_error
84 else {
85 return;
86 };
87
88 let msg = match error {
89 ParseErrorType::FStringError(InterpolatedStringErrorType::UnterminatedString)
90 | ParseErrorType::Lexical(LexicalErrorType::FStringError(
91 InterpolatedStringErrorType::UnterminatedString,
92 )) => "unterminated f-string literal".into(),
93
94 ParseErrorType::TStringError(InterpolatedStringErrorType::UnterminatedString)
95 | ParseErrorType::Lexical(LexicalErrorType::TStringError(
96 InterpolatedStringErrorType::UnterminatedString,
97 )) => "unterminated t-string literal".into(),
98
99 ParseErrorType::FStringError(
100 InterpolatedStringErrorType::UnterminatedTripleQuotedString,
101 )
102 | ParseErrorType::Lexical(LexicalErrorType::FStringError(
103 InterpolatedStringErrorType::UnterminatedTripleQuotedString,
104 )) => "unterminated triple-quoted f-string literal".into(),
105
106 ParseErrorType::TStringError(
107 InterpolatedStringErrorType::UnterminatedTripleQuotedString,
108 )
109 | ParseErrorType::Lexical(LexicalErrorType::TStringError(
110 InterpolatedStringErrorType::UnterminatedTripleQuotedString,
111 )) => "unterminated triple-quoted t-string literal".into(),
112
113 ParseErrorType::FStringError(_)
116 | ParseErrorType::TStringError(_)
117 | ParseErrorType::Lexical(
118 LexicalErrorType::FStringError(_) | LexicalErrorType::TStringError(_),
119 ) => self.msg.replace('`', "'"),
120
121 ParseErrorType::UnexpectedExpressionToken => "invalid syntax".into(),
122
123 ParseErrorType::ExpectedToken { expected, found } => {
124 Self::handle_expected_token(*expected, *found).into()
125 }
126
127 ParseErrorType::InvalidStarredExpressionUsage => {
128 self.narrow_caret = true;
129 "invalid syntax".into()
130 }
131
132 ParseErrorType::InvalidDeleteTarget => "invalid syntax".into(),
133
134 ParseErrorType::Lexical(LexicalErrorType::LineContinuationError) => {
135 "unexpected character after line continuation character".into()
136 }
137
138 ParseErrorType::Lexical(LexicalErrorType::UnclosedStringError) => {
139 format!(
140 "unterminated string literal (detected at line {})",
141 location.line
142 )
143 }
144
145 ParseErrorType::EmptyTypeParams => "Type parameter list cannot be empty".into(),
146
147 ParseErrorType::InvalidStarPatternUsage => {
148 self.narrow_caret = true;
149 "cannot use starred expression here".into()
150 }
151
152 ParseErrorType::ExpectedKeywordParam => "named arguments must follow bare *".into(),
153
154 ParseErrorType::EmptyImportNames => "Expected one or more names after 'import'".into(),
155
156 ParseErrorType::UnparenthesizedGeneratorExpression => {
157 "Generator expression must be parenthesized".into()
158 }
159
160 ParseErrorType::NonDefaultParamAfterDefaultParam => {
161 "parameter without a default follows parameter with a default".into()
162 }
163
164 ParseErrorType::VarParameterWithDefault => {
165 "var-positional argument cannot have default value".into()
166 }
167
168 ParseErrorType::PositionalAfterKeywordArgument => {
169 "positional argument follows keyword argument".into()
170 }
171
172 ParseErrorType::PositionalAfterKeywordUnpacking => {
173 "positional argument follows keyword argument unpacking".into()
174 }
175
176 ParseErrorType::InvalidArgumentUnpackingOrder => {
177 "iterable argument unpacking follows keyword argument unpacking".into()
178 }
179
180 ParseErrorType::ParamAfterVarKeywordParam => {
181 "arguments cannot follow var-keyword argument".into()
182 }
183
184 ParseErrorType::InvalidAnnotatedAssignmentTarget => {
185 "illegal target for annotation".into()
186 }
187
188 ParseErrorType::Lexical(LexicalErrorType::UnrecognizedToken { .. })
189 | ParseErrorType::SimpleStatementsOnSameLine
190 | ParseErrorType::SimpleAndCompoundStatementOnSameLine
191 | ParseErrorType::ExpectedExpression => "invalid syntax".into(),
192
193 ParseErrorType::OtherError(s) if s.starts_with("Expected an identifier") => {
194 "invalid syntax".into()
195 }
196
197 ParseErrorType::OtherError(s)
200 if matches!(
201 s.as_str(),
202 "Expected a statement"
203 | "Expected an `elif` or `else` clause, or the end of the `if` statement."
204 | "Expected an `except` or `finally` clause or the end of the `try` statement."
205 | "The keyword is not allowed as a variable declaration name"
206 | "Expected an assignment target"
207 | "Expected a type parameter or the end of the type parameter list"
208 | "Expected an import name or a ')'"
209 | "Expected an import name"
210 | "Expected an expression or the end of the slice list"
211 | "Expected an expression or a ']'"
212 | "Expected an expression or a '}'"
213 | "Expected an expression or a ')'"
214 | "Expected an expression"
215 | "Expected a pattern or the end of the sequence pattern"
216 | "Expected a mapping pattern or the end of the mapping pattern"
217 | "Expected a pattern or a ')'"
218 | "Expected a delete target"
219 | "Expected a parameter or the end of the parameter list"
220 | "Expected an expression or the end of the with item list"
221 ) =>
222 {
223 "invalid syntax".into()
224 }
225
226 ParseErrorType::OtherError(s)
227 if s.eq_ignore_ascii_case(
228 "bytes literal cannot be mixed with non-bytes literals",
229 ) =>
230 {
231 "cannot mix bytes and nonbytes literals".into()
232 }
233
234 ParseErrorType::OtherError(s)
235 if s.eq_ignore_ascii_case("positional patterns cannot follow keyword patterns") =>
236 {
237 "positional patterns follow keyword patterns".into()
238 }
239
240 ParseErrorType::OtherError(s)
241 if s.eq_ignore_ascii_case("boolean 'not' expression cannot be used here") =>
242 {
243 "'not' after an operator must be parenthesized".into()
244 }
245
246 ParseErrorType::OtherError(s)
247 if s.eq_ignore_ascii_case("trailing comma not allowed") =>
248 {
249 "trailing comma not allowed without surrounding parentheses".into()
250 }
251
252 ParseErrorType::OtherError(s)
253 if s.eq_ignore_ascii_case(
254 "multiple exception types must be parenthesized when using `as`",
255 ) =>
256 {
257 "multiple exception types must be parenthesized when using 'as'".into()
258 }
259
260 ParseErrorType::OtherError(s)
261 if s.eq_ignore_ascii_case(
262 "position-only parameter separator not allowed as first parameter",
263 ) =>
264 {
265 "at least one argument must precede /".into()
266 }
267
268 ParseErrorType::OtherError(s)
269 if s.eq_ignore_ascii_case("only one '/' separator allowed") =>
270 {
271 "/ may appear only once".into()
272 }
273
274 ParseErrorType::OtherError(s)
275 if s.eq_ignore_ascii_case("'/' parameter must appear before '*' parameter") =>
276 {
277 "/ must be ahead of *".into()
278 }
279
280 ParseErrorType::OtherError(s)
281 if s.eq_ignore_ascii_case("expected `except` or `finally` after `try` block") =>
282 {
283 "expected 'except' or 'finally' block".into()
284 }
285
286 ParseErrorType::OtherError(s)
287 if s.eq_ignore_ascii_case("only one '*' parameter allowed") =>
288 {
289 "* argument may appear only once".into()
290 }
291
292 ParseErrorType::OtherError(s)
293 if s.eq_ignore_ascii_case(
294 r#"cannot have both 'except' and 'except*' on the same 'try'"#,
295 ) =>
296 {
297 r#"cannot have both 'except' and 'except*' on the same 'try'"#.into()
298 }
299
300 _ => return,
301 };
302
303 self.msg = msg;
304 }
305}
306
307impl VirtualMachine {
309 pub fn new_pyobj(&self, value: impl ToPyObject) -> PyObjectRef {
311 value.to_pyobject(self)
312 }
313
314 pub fn new_tuple(&self, value: impl IntoPyTuple) -> PyTupleRef {
315 value.into_pytuple(self)
316 }
317
318 pub fn new_module(
319 &self,
320 name: &str,
321 dict: PyDictRef,
322 doc: Option<PyStrRef>,
323 ) -> PyRef<PyModule> {
324 let module = PyRef::new_ref(
325 PyModule::new(),
326 self.ctx.types.module_type.to_owned(),
327 Some(dict),
328 );
329 module.init_dict(self.ctx.intern_str(name), doc, self);
330 module
331 }
332
333 pub fn new_scope_with_builtins(&self) -> Scope {
334 Scope::with_builtins(None, self.ctx.new_dict(), self)
335 }
336
337 pub fn new_scope_with_main(&self) -> PyResult<Scope> {
338 let scope = self.new_scope_with_builtins();
339 let main_module = self.new_module("__main__", scope.globals.clone(), None);
340
341 self.sys_module.get_attr("modules", self)?.set_item(
342 "__main__",
343 main_module.into(),
344 self,
345 )?;
346 self.set_main_builtin_importer(&scope.globals)?;
347
348 Ok(scope)
349 }
350
351 pub fn ensure_main_module(&self) -> PyResult<PyRef<PyModule>> {
353 let sys_modules = self.sys_module.get_attr("modules", self)?;
354 let module = if let Ok(existing) = sys_modules.get_item("__main__", self)
355 && let Ok(module) = existing.downcast::<PyModule>()
356 {
357 module
358 } else {
359 let dict = self.ctx.new_dict();
360 let main_module = self.new_module("__main__", dict, None);
361 sys_modules.set_item("__main__", main_module.clone().into(), self)?;
362 main_module
363 };
364 self.set_main_builtin_importer(&module.dict())?;
365 Ok(module)
366 }
367
368 pub fn set_main_builtin_importer(
370 &self,
371 module_dict: &Py<crate::builtins::PyDict>,
372 ) -> PyResult<()> {
373 if let Ok(loader) = module_dict.get_item("__loader__", self)
374 && !self.is_none(&loader)
375 {
376 return Ok(());
377 }
378 let sys_modules = self.sys_module.get_attr("modules", self)?;
379 let Ok(importlib) = sys_modules.get_item("_frozen_importlib", self) else {
380 return Ok(());
381 };
382 let loader = importlib.get_attr("BuiltinImporter", self)?;
383 module_dict.set_item("__loader__", loader, self)?;
384 Ok(())
385 }
386
387 pub fn main_namespace(&self) -> PyResult<PyDictRef> {
389 let main = self.ensure_main_module()?;
390 Ok(main.dict())
391 }
392
393 pub fn new_function<F, FKind>(&self, name: &'static str, f: F) -> PyRef<PyNativeFunction>
394 where
395 F: IntoPyNativeFn<FKind>,
396 {
397 let def = self.ctx.new_method_def(
398 name,
399 f,
400 PyMethodFlags::empty(),
401 crate::function::ItemDoc::NONE,
402 );
403 def.build_function(self, None)
404 }
405
406 pub fn new_method<F, FKind>(
407 &self,
408 name: &'static str,
409 class: &'static Py<PyType>,
410 f: F,
411 ) -> PyRef<PyMethodDescriptor>
412 where
413 F: IntoPyNativeFn<FKind>,
414 {
415 let def = self.ctx.new_method_def(
416 name,
417 f,
418 PyMethodFlags::METHOD,
419 crate::function::ItemDoc::NONE,
420 );
421 def.build_method(class, self)
422 }
423
424 pub fn new_simple_exception(
431 &self,
432 exc_type: PyTypeRef,
433 args: Vec<PyObjectRef>,
434 ) -> PyBaseExceptionRef {
435 debug_assert_eq!(
436 exc_type.slots.basicsize,
437 crate::object::SIZEOF_PYOBJECT_HEAD + core::mem::size_of::<PyBaseException>(),
438 "vm.new_simple_exception() requires a BaseException-sized type, got {}",
439 exc_type.name()
440 );
441 PyBaseException::new(args, self)
442 .into_ref_with_type_lazy_dict(self, exc_type)
443 .expect("vm.new_simple_exception() called with an invalid exception type")
444 }
445
446 pub fn new_exception(&self, exc_type: PyTypeRef, args: Vec<PyObjectRef>) -> PyBaseExceptionRef {
455 if exc_type.slots.basicsize
456 == crate::object::SIZEOF_PYOBJECT_HEAD + core::mem::size_of::<PyBaseException>()
457 {
458 self.new_simple_exception(exc_type, args)
459 } else {
460 self.invoke_exception(&exc_type, args).unwrap_or_else(|e| e)
461 }
462 }
463
464 pub fn new_payload_exception<T>(&self, cls: PyTypeRef, args: FuncArgs) -> PyResult<PyRef<T>>
468 where
469 T: Constructor<Args = FuncArgs> + Initializer,
470 {
471 debug_assert_eq!(
472 cls.slots.basicsize,
473 crate::object::SIZEOF_PYOBJECT_HEAD + size_of::<T>(),
474 "vm.new_payload_exception::<{}>() called with mismatched type '{}'",
475 core::any::type_name::<T>(),
476 cls.name()
477 );
478 let obj = T::slot_new(cls, args.clone(), self)?;
479 T::slot_init(&obj, args, self)?;
480 obj.downcast().map_err(|obj| {
481 self.new_type_error(format!(
482 "payload constructor returned '{}'",
483 obj.class().name()
484 ))
485 })
486 }
487
488 pub fn new_os_error(&self, msg: impl ToPyObject) -> PyRef<PyBaseException> {
489 self.new_os_subtype_error(self.ctx.exceptions.os_error.to_owned(), None, msg)
490 .upcast()
491 }
492
493 pub fn new_os_subtype_error(
494 &self,
495 exc_type: PyTypeRef,
496 errno: Option<i32>,
497 msg: impl ToPyObject,
498 ) -> PyRef<PyOSError> {
499 debug_assert_eq!(
500 exc_type.slots.basicsize,
501 crate::object::SIZEOF_PYOBJECT_HEAD + core::mem::size_of::<PyOSError>()
502 );
503
504 OSErrorBuilder::with_subtype(exc_type, errno, msg, self).build(self)
505 }
506
507 pub fn new_exception_empty(&self, exc_type: PyTypeRef) -> PyBaseExceptionRef {
509 self.new_exception(exc_type, vec![])
510 }
511
512 pub fn new_exception_msg(&self, exc_type: PyTypeRef, msg: Wtf8Buf) -> PyBaseExceptionRef {
514 self.new_exception(exc_type, vec![self.ctx.new_str(msg).into()])
515 }
516
517 pub fn new_exception_msg_dict(
523 &self,
524 exc_type: PyTypeRef,
525 msg: Wtf8Buf,
526 dict: PyDictRef,
527 ) -> PyBaseExceptionRef {
528 PyRef::new_ref(
529 PyBaseException::new(vec![self.ctx.new_str(msg).into()], self),
533 exc_type,
534 Some(dict),
535 )
536 }
537
538 pub fn new_no_attribute_error(&self, obj: PyObjectRef, name: PyStrRef) -> PyBaseExceptionRef {
539 let msg = format!(
540 "'{}' object has no attribute '{}'",
541 obj.class().slot_name(),
542 name
543 );
544 let attribute_error = self.new_attribute_error(msg);
545
546 self.set_attribute_error_context(&attribute_error, obj, name);
548
549 attribute_error
550 }
551
552 pub fn new_name_error(&self, msg: impl Into<Wtf8Buf>, name: PyStrRef) -> PyBaseExceptionRef {
553 let name_error = self
554 .new_payload_exception::<PyNameError>(
555 self.ctx.exceptions.name_error.to_owned(),
556 vec![self.ctx.new_str(msg.into()).into()].into(),
557 )
558 .expect("NameError construction from internal args is infallible");
559 set_attrs!(
560 name_error.as_object(), self, unwrap,
561 "name" => name,
562 );
563
564 name_error.upcast()
565 }
566
567 pub fn new_arity_type_error(
571 &self,
572 func_name: &str,
573 arity: RangeInclusive<usize>,
574 num_given: usize,
575 ) -> PyBaseExceptionRef {
576 let too_few = num_given < *arity.start();
577 self.new_type_error(arity_message(Some(func_name), &arity, too_few, num_given))
578 }
579
580 pub fn new_unexpected_keyword_type_error(
584 &self,
585 func_name: Option<&str>,
586 keyword: &str,
587 ) -> PyBaseExceptionRef {
588 self.new_type_error(unexpected_keyword_message(func_name, keyword))
589 }
590
591 pub fn new_unsupported_unary_error(&self, a: &PyObject, op: &str) -> PyBaseExceptionRef {
592 self.new_type_error(format!(
593 "bad operand type for {}: '{}'",
594 op,
595 a.class().slot_name()
596 ))
597 }
598
599 pub fn new_unsupported_bin_op_error(
600 &self,
601 a: &PyObject,
602 b: &PyObject,
603 op: &str,
604 ) -> PyBaseExceptionRef {
605 self.new_type_error(format!(
606 "unsupported operand type(s) for {}: '{}' and '{}'",
607 op,
608 a.class().slot_name(),
609 b.class().slot_name()
610 ))
611 }
612
613 pub fn new_unsupported_ternary_op_error(
614 &self,
615 a: &PyObject,
616 b: &PyObject,
617 c: &PyObject,
618 op: &str,
619 ) -> PyBaseExceptionRef {
620 self.new_type_error(format!(
621 "unsupported operand type(s) for {}: '{}', '{}', '{}'",
622 op,
623 a.class().slot_name(),
624 b.class().slot_name(),
625 c.class().slot_name()
626 ))
627 }
628
629 pub fn new_last_os_error(&self) -> PyBaseExceptionRef {
634 let err = std::io::Error::last_os_error();
635 err.to_pyexception(self)
636 }
637
638 pub fn new_last_errno_error(&self) -> PyBaseExceptionRef {
643 let err = crate::host_env::os::errno_io_error();
644 err.to_pyexception(self)
645 }
646
647 pub fn new_errno_error(&self, errno: i32, msg: impl ToPyObject) -> PyRef<PyOSError> {
648 let exc_type = crate::exceptions::errno_to_exc_type(errno, self)
649 .unwrap_or(self.ctx.exceptions.os_error);
650
651 self.new_os_subtype_error(exc_type.to_owned(), Some(errno), msg)
652 }
653
654 pub fn new_unicode_decode_error(
655 &self,
656 encoding: PyStrRef,
657 object: PyBytesRef,
658 start: usize,
659 end: usize,
660 reason: PyStrRef,
661 ) -> PyBaseExceptionRef {
662 let start = self.ctx.new_int(start);
663 let end = self.ctx.new_int(end);
664 self.invoke_exception(
665 self.ctx.exceptions.unicode_decode_error,
666 vec![
667 encoding.into(),
668 object.into(),
669 start.into(),
670 end.into(),
671 reason.into(),
672 ],
673 )
674 .expect("UnicodeDecodeError constructor")
675 }
676
677 pub fn new_unicode_encode_error(
678 &self,
679 encoding: PyStrRef,
680 object: PyStrRef,
681 start: usize,
682 end: usize,
683 reason: PyStrRef,
684 ) -> PyBaseExceptionRef {
685 let start = self.ctx.new_int(start);
686 let end = self.ctx.new_int(end);
687 self.invoke_exception(
688 self.ctx.exceptions.unicode_encode_error,
689 vec![
690 encoding.into(),
691 object.into(),
692 start.into(),
693 end.into(),
694 reason.into(),
695 ],
696 )
697 .expect("UnicodeEncodeError constructor")
698 }
699
700 #[cfg(feature = "parser")]
701 fn source_has_mixed_tabs_and_spaces(source: Option<&str>, error_line: usize) -> bool {
702 source.is_some_and(|source| {
703 let mut has_space_indent = false;
704 let mut has_tab_indent = false;
705 for (i, line) in source.lines().enumerate() {
706 if i + 1 > error_line {
707 break;
708 }
709 let rest = line.trim_start_matches([' ', '\t']);
710 if rest.is_empty() || rest.starts_with('#') {
712 continue;
713 }
714 let indent = &line.as_bytes()[..line.len() - rest.len()];
715 if indent.is_empty() {
716 continue;
717 }
718 if indent.contains(&b' ') && indent.contains(&b'\t') {
719 return true;
720 }
721 if indent.contains(&b' ') {
722 has_space_indent = true;
723 }
724 if indent.contains(&b'\t') {
725 has_tab_indent = true;
726 }
727 }
728 has_space_indent && has_tab_indent
729 })
730 }
731
732 pub fn new_key_error(&self, obj: PyObjectRef) -> PyBaseExceptionRef {
734 let key_error = self.ctx.exceptions.key_error.to_owned();
735 self.new_simple_exception(key_error, vec![obj])
736 }
737
738 #[cfg(any(feature = "parser", feature = "compiler"))]
739 pub fn new_syntax_error_maybe_incomplete(
740 &self,
741 error: &crate::compiler::CompileError,
742 source: Option<&str>,
743 allow_incomplete: bool,
744 ) -> PyBaseExceptionRef {
745 if matches!(
746 error,
747 crate::compiler::CompileError::Codegen(crate::compiler::codegen::error::CodegenError {
748 error: crate::compiler::codegen::error::CodegenErrorType::RecursionError,
749 ..
750 })
751 ) {
752 return self.new_recursion_error(error.to_string());
753 }
754
755 let incomplete_or_syntax = |allow| -> &'static Py<crate::builtins::PyType> {
756 if allow {
757 self.ctx.exceptions.incomplete_input_error
758 } else {
759 self.ctx.exceptions.syntax_error
760 }
761 };
762
763 let syntax_error_type = match &error {
764 #[cfg(feature = "parser")]
765 crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
766 error:
767 ruff_python_parser::ParseErrorType::Lexical(
768 ruff_python_parser::LexicalErrorType::IndentationError,
769 )
770 | ruff_python_parser::ParseErrorType::UnexpectedIndentation,
771 location,
772 ..
773 }) => {
774 if Self::source_has_mixed_tabs_and_spaces(source, location.line.get()) {
775 self.ctx.exceptions.tab_error
776 } else {
777 self.ctx.exceptions.indentation_error
778 }
779 }
780 #[cfg(feature = "parser")]
781 crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
782 error:
783 ruff_python_parser::ParseErrorType::Lexical(
784 ruff_python_parser::LexicalErrorType::Eof,
785 ),
786 ..
787 }) => incomplete_or_syntax(allow_incomplete),
788 #[cfg(feature = "parser")]
790 crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
791 is_unclosed_bracket: true,
792 ..
793 }) => incomplete_or_syntax(allow_incomplete),
794 #[cfg(feature = "parser")]
795 crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
796 is_unclosed_string: true,
797 ..
798 }) => incomplete_or_syntax(allow_incomplete),
799 #[cfg(feature = "parser")]
800 crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
801 error:
802 ruff_python_parser::ParseErrorType::Lexical(
803 ruff_python_parser::LexicalErrorType::FStringError(
804 ruff_python_parser::InterpolatedStringErrorType::UnterminatedTripleQuotedString,
805 )
806 | ruff_python_parser::LexicalErrorType::TStringError(
807 ruff_python_parser::InterpolatedStringErrorType::UnterminatedTripleQuotedString,
808 ),
809 ),
810 ..
811 }) => incomplete_or_syntax(allow_incomplete),
812 #[cfg(feature = "parser")]
813 crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
814 error:
815 ruff_python_parser::ParseErrorType::Lexical(
816 ruff_python_parser::LexicalErrorType::UnclosedStringError,
817 ),
818 ..
819 }) => {
820 if allow_incomplete {
821 incomplete_or_syntax(source.is_some_and(unclosed_string_is_incomplete))
822 } else {
823 self.ctx.exceptions.syntax_error
824 }
825 }
826 #[cfg(feature = "parser")]
827 crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
828 error: ruff_python_parser::ParseErrorType::OtherError(s),
829 raw_location,
830 ..
831 }) => {
832 if s.starts_with("Expected an indented block after")
833 || s.starts_with("expected an indented block after")
834 {
835 if allow_incomplete {
836 let mut is_incomplete = true;
840 if let Some(source) = source {
841 let start = raw_location.start().to_usize();
842 let end = raw_location.end().to_usize();
843 let mut iter = source.chars();
844 iter.nth(start);
845 for _ in start..end {
846 if let Some(c) = iter.next() {
847 if !c.is_ascii_whitespace() {
848 is_incomplete = false;
849 }
850 } else {
851 break;
852 }
853 }
854 }
855
856 if is_incomplete {
857 self.ctx.exceptions.incomplete_input_error
858 } else {
859 self.ctx.exceptions.indentation_error }
861 } else {
862 self.ctx.exceptions.indentation_error
863 }
864 } else if allow_incomplete
865 && (s == "incomplete input"
866 || (s == "unexpected EOF while parsing"
867 && source.is_some_and(|source| {
868 raw_location.end().to_usize() >= source.len()
869 && !source.ends_with('\n')
870 })))
871 {
872 self.ctx.exceptions.incomplete_input_error
873 } else {
874 self.ctx.exceptions.syntax_error
875 }
876 }
877 _ => self.ctx.exceptions.syntax_error,
878 };
879 let syntax_error_type = if allow_incomplete && source.is_some_and(is_blank_python_source) {
880 self.ctx.exceptions.incomplete_input_error
881 } else {
882 syntax_error_type
883 }
884 .to_owned();
885
886 fn get_statement(source: &str, loc: Option<SourceLocation>) -> Option<String> {
888 let line = source
889 .split('\n')
890 .nth(loc?.line.to_zero_indexed())?
891 .trim_end_matches('\r')
892 .to_owned();
893 Some(line + "\n")
894 }
895
896 let mut statement = if matches!(error, crate::compiler::CompileError::Codegen(_)) {
900 self.program_text(error.source_path(), error.python_location().0)
901 } else {
902 source.and_then(|src| get_statement(src, error.location()))
903 };
904
905 let mut msg = error.to_string();
906 if !msg.starts_with("Exceeds the limit ")
907 && !msg.starts_with("Did you mean ")
908 && !msg.starts_with("Invalid star expression")
909 && !msg.starts_with("Function parameters cannot be parenthesized")
910 && !msg.starts_with("Lambda expression parameters cannot be parenthesized")
911 && !msg.starts_with("Cannot have two type comments on def")
912 && !msg.starts_with("Variable annotation syntax is")
913 && !msg.starts_with("The '@' operator is")
914 && !msg.starts_with("Async functions are")
915 && !msg.starts_with("Async comprehensions are")
916 && !msg.starts_with("Async for loops are")
917 && !msg.starts_with("Async with statements are")
918 && !msg.starts_with("Exception groups are")
919 && !msg.starts_with("Positional-only parameters are")
920 && !msg.starts_with("Pattern matching is")
921 && !msg.starts_with("Type statement is")
922 && !msg.starts_with("Type parameter lists are")
923 && !msg.starts_with("Type parameter defaults are")
924 && !msg.starts_with("Assignment expressions are")
925 && !msg.starts_with("Await expressions are")
926 && !msg.starts_with("Underscores in numeric literals are")
927 && !msg.starts_with("Missing parentheses")
928 && let Some(msg) = msg.get_mut(..1)
929 {
930 msg.make_ascii_lowercase();
931 }
932
933 cfg_select! {
934 feature = "parser" => {
935 let mut syntax_error_info = SyntaxErrorInfo::new(msg, false);
936 syntax_error_info.analyze_compile_error(error);
937 }
938 _ => {
939 let syntax_error_info = SyntaxErrorInfo::new(msg, false);
940 }
941 };
942
943 if syntax_error_type.is(self.ctx.exceptions.tab_error) {
944 syntax_error_info.msg =
945 String::from("inconsistent use of tabs and spaces in indentation");
946 } else if syntax_error_type.is(self.ctx.exceptions.incomplete_input_error) {
947 syntax_error_info.msg = String::from("incomplete input");
948 }
949
950 let SyntaxErrorInfo { msg, narrow_caret } = syntax_error_info;
951 let unterminated_triple_quoted_string = msg.starts_with("unterminated triple-quoted");
952 let unexpected_eof_error = msg == "unexpected EOF while parsing";
953 if unterminated_triple_quoted_string
954 && let Some(statement) = statement.as_mut()
955 && statement.ends_with('\n')
956 {
957 statement.pop();
959 }
960 let check_version_suite_error = msg.starts_with("Async functions are")
961 || msg.starts_with("Async for loops are")
962 || msg.starts_with("Async with statements are")
963 || msg.starts_with("Exception groups are")
964 || msg.starts_with("except expressions without parentheses are")
965 || msg.starts_with("Pattern matching is");
966 let line_end_binary_operator_error = msg.starts_with("The '@' operator is");
967 let unclosed_bracket_error = cfg_select! {
968 feature = "parser" => {
969 matches!(
970 error,
971 crate::compiler::CompileError::Parse(rustpython_compiler::ParseError {
972 is_unclosed_bracket: true,
973 ..
974 })
975 )
976 }
977 _ => false,
978 };
979
980 let syntax_error: PyBaseExceptionRef = self
981 .new_payload_exception::<PySyntaxError>(
982 syntax_error_type,
983 vec![self.ctx.new_str(msg).into()].into(),
984 )
985 .expect("SyntaxError construction from internal args is infallible")
986 .upcast();
987
988 let (lineno_raw, offset_raw) = error.python_location();
989 let lineno = self.ctx.new_int(lineno_raw);
990 let offset = self.ctx.new_int(offset_raw);
991 set_attrs!(
992 syntax_error.as_object(), self, unwrap,
993 "lineno" => lineno,
994 "offset" => offset,
995 );
996
997 if let Some((end_lineno, end_offset)) = error.python_end_location() {
999 let no_end_offset = unexpected_eof_error
1001 || (check_version_suite_error
1002 && statement
1003 .as_deref()
1004 .and_then(|line| line.chars().next())
1005 .is_some_and(|ch| ch.is_ascii_whitespace()));
1006 let (end_lineno, end_offset) = if no_end_offset {
1007 (end_lineno, -1)
1008 } else if unclosed_bracket_error {
1009 (end_lineno, 0)
1012 } else if line_end_binary_operator_error && end_offset == offset_raw {
1013 (end_lineno, (end_offset + 1) as isize)
1014 } else if narrow_caret {
1015 let (l, o) = error.python_location();
1016 (l, (o + 1) as isize)
1017 } else {
1018 (end_lineno, end_offset as isize)
1019 };
1020 let end_lineno = self.ctx.new_int(end_lineno);
1021 let end_offset = self.ctx.new_int(end_offset);
1022 set_attrs!(
1023 syntax_error.as_object(), self, unwrap,
1024 "end_lineno" => end_lineno,
1025 "end_offset" => end_offset,
1026 );
1027 }
1028
1029 set_attrs!(
1030 syntax_error.as_object(), self, unwrap,
1031 "text" => statement.to_pyobject(self),
1032 "filename" => self.ctx.new_str(error.source_path())
1033 );
1034
1035 if let Some(source) = source {
1040 let metadata = self.ctx.new_tuple(vec![
1041 self.ctx.new_int(0).into(),
1042 self.ctx.new_int(0).into(),
1043 self.ctx.new_str(source).into(),
1044 ]);
1045 set_attrs!(
1046 syntax_error.as_object(), self, unwrap,
1047 "_metadata" => metadata,
1048 );
1049 }
1050
1051 syntax_error
1052 }
1053
1054 #[cfg(any(feature = "parser", feature = "compiler"))]
1055 pub fn new_syntax_error(
1056 &self,
1057 error: &crate::compiler::CompileError,
1058 source: Option<&str>,
1059 ) -> PyBaseExceptionRef {
1060 self.new_syntax_error_maybe_incomplete(error, source, false)
1061 }
1062
1063 pub fn new_import_error(
1064 &self,
1065 msg: impl Into<Wtf8Buf>,
1066 name: impl Into<PyStrRef>,
1067 ) -> PyBaseExceptionRef {
1068 let exc = self
1069 .new_payload_exception::<PyImportError>(
1070 self.ctx.exceptions.import_error.to_owned(),
1071 vec![self.ctx.new_str(msg.into()).into()].into(),
1072 )
1073 .expect("ImportError construction from internal args is infallible");
1074 set_attrs!(
1075 exc.as_object(), self, unwrap,
1076 "name" => name.into(),
1077 );
1078
1079 exc.upcast()
1080 }
1081
1082 pub fn new_system_exit(&self, args: FuncArgs) -> PyBaseExceptionRef {
1083 self.new_payload_exception::<PySystemExit>(self.ctx.exceptions.system_exit.to_owned(), args)
1084 .expect("SystemExit construction from internal args is infallible")
1085 .upcast()
1086 }
1087
1088 pub fn new_stop_iteration(&self, value: Option<PyObjectRef>) -> PyBaseExceptionRef {
1089 let args: FuncArgs = match value {
1092 Some(v) if !self.is_none(&v) => vec![v].into(),
1093 _ => Vec::<PyObjectRef>::new().into(),
1094 };
1095 self.new_payload_exception::<PyStopIteration>(
1096 self.ctx.exceptions.stop_iteration.to_owned(),
1097 args,
1098 )
1099 .expect("StopIteration construction from internal args is infallible")
1100 .upcast()
1101 }
1102
1103 fn new_downcast_error(
1104 &self,
1105 msg: &'static str,
1106 error_type: &'static Py<PyType>,
1107 class: &Py<PyType>,
1108 obj: &PyObject, ) -> PyBaseExceptionRef {
1110 let actual_class = obj.class();
1111 let actual_type = &*actual_class.name();
1112 let expected_type = &*class.name();
1113 let msg = format!("Expected {msg} '{expected_type}' but '{actual_type}' found.");
1114 #[cfg(debug_assertions)]
1115 let msg = if class.get_id() == actual_class.get_id() {
1116 let mut msg = msg;
1117 msg += " It might mean this type doesn't support subclassing very well. e.g. Did you forget to add `#[pyclass(with(Constructor))]`?";
1118 msg
1119 } else {
1120 msg
1121 };
1122 self.new_exception_msg(error_type.to_owned(), msg.into())
1123 }
1124
1125 pub(crate) fn new_downcast_runtime_error(
1126 &self,
1127 class: &Py<PyType>,
1128 obj: &impl AsObject,
1129 ) -> PyBaseExceptionRef {
1130 self.new_downcast_error(
1131 "payload",
1132 self.ctx.exceptions.runtime_error,
1133 class,
1134 obj.as_object(),
1135 )
1136 }
1137
1138 pub(crate) fn new_downcast_type_error(
1139 &self,
1140 class: &Py<PyType>,
1141 obj: &impl AsObject,
1142 ) -> PyBaseExceptionRef {
1143 self.new_downcast_error(
1144 "type",
1145 self.ctx.exceptions.type_error,
1146 class,
1147 obj.as_object(),
1148 )
1149 }
1150
1151 pub fn no_memory_error(&self) -> PyBaseExceptionRef {
1157 let exc: PyObjectRef = PyMemoryError::empty(self)
1158 .into_ref_with_type_lazy_dict(self, self.ctx.exceptions.memory_error.to_owned())
1159 .expect("MemoryError is a static type whose payload size matches PyMemoryError")
1160 .into();
1161 exc.downcast()
1162 .expect("PyMemoryError payload downcasts to PyBaseException")
1163 }
1164
1165 define_exception_fn!(fn new_lookup_error, lookup_error, LookupError);
1166 define_exception_fn!(fn new_eof_error, eof_error, EOFError);
1167 pub fn new_attribute_error(&self, msg: impl Into<Wtf8Buf>) -> PyBaseExceptionRef {
1168 self.new_payload_exception::<PyAttributeError>(
1169 self.ctx.exceptions.attribute_error.to_owned(),
1170 vec![self.ctx.new_str(msg.into()).into()].into(),
1171 )
1172 .expect("AttributeError construction from internal args is infallible")
1173 .upcast()
1174 }
1175
1176 define_exception_fn!(fn new_type_error, type_error, TypeError);
1177 define_exception_fn!(fn new_system_error, system_error, SystemError);
1178
1179 define_exception_fn!(fn new_value_error, value_error, ValueError);
1180
1181 define_exception_fn!(fn new_buffer_error, buffer_error, BufferError);
1182 define_exception_fn!(fn new_index_error, index_error, IndexError);
1183 define_exception_fn!(
1184 fn new_not_implemented_error,
1185 not_implemented_error,
1186 NotImplementedError
1187 );
1188 define_exception_fn!(fn new_recursion_error, recursion_error, RecursionError);
1189 define_exception_fn!(fn new_zero_division_error, zero_division_error, ZeroDivisionError);
1190 define_exception_fn!(fn new_overflow_error, overflow_error, OverflowError);
1191 define_exception_fn!(fn new_runtime_error, runtime_error, RuntimeError);
1192 define_exception_fn!(fn new_python_finalization_error, python_finalization_error, PythonFinalizationError);
1193 define_exception_fn!(fn new_memory_error, memory_error, MemoryError);
1194 define_exception_fn!(fn new_assertion_error, assertion_error, AssertionError);
1195 pub fn new_unbound_local_error(&self, msg: impl Into<Wtf8Buf>) -> PyBaseExceptionRef {
1196 self.new_payload_exception::<PyNameError>(
1197 self.ctx.exceptions.unbound_local_error.to_owned(),
1198 vec![self.ctx.new_str(msg.into()).into()].into(),
1199 )
1200 .expect("UnboundLocalError construction from internal args is infallible")
1201 .upcast()
1202 }
1203}
1204
1205#[cfg(feature = "parser")]
1206enum QuotedStringScan {
1207 Closed(usize),
1208 Unclosed {
1209 triple: bool,
1210 unescaped_newline: bool,
1211 },
1212}
1213
1214#[cfg(feature = "parser")]
1217fn unclosed_string_is_incomplete(source: &str) -> bool {
1218 let bytes = source.as_bytes();
1219 let mut index = 0;
1220 while index < bytes.len() {
1221 match bytes[index] {
1222 b'#' => {
1223 while index < bytes.len() && bytes[index] != b'\n' {
1224 index += 1;
1225 }
1226 }
1227 b'\'' | b'"' => match scan_quoted_string_for_incomplete(bytes, index) {
1228 QuotedStringScan::Closed(end) => index = end,
1229 QuotedStringScan::Unclosed {
1230 triple,
1231 unescaped_newline,
1232 } => {
1233 if !triple && interpolated_string_prefix_at(bytes, index) {
1235 return false;
1236 }
1237 return triple || !unescaped_newline;
1238 }
1239 },
1240 _ => index += 1,
1241 }
1242 }
1243 false
1244}
1245
1246#[cfg(feature = "parser")]
1247fn interpolated_string_prefix_at(bytes: &[u8], quote: usize) -> bool {
1248 let Some(&prev) = quote.checked_sub(1).and_then(|index| bytes.get(index)) else {
1249 return false;
1250 };
1251 let lower = prev.to_ascii_lowercase();
1252 let marker_index = if matches!(lower, b'f' | b't') {
1253 if quote >= 2 && bytes[quote - 2].eq_ignore_ascii_case(&b'r') {
1254 quote - 2
1255 } else {
1256 quote - 1
1257 }
1258 } else if lower == b'r'
1259 && quote >= 2
1260 && matches!(bytes[quote - 2].to_ascii_lowercase(), b'f' | b't')
1261 {
1262 quote - 2
1263 } else {
1264 return false;
1265 };
1266 marker_index == 0 || !identifier_continue_before(bytes, marker_index)
1267}
1268
1269#[cfg(feature = "parser")]
1270fn identifier_continue_before(bytes: &[u8], index: usize) -> bool {
1271 if index == 0 {
1272 return false;
1273 }
1274 if bytes[index - 1].is_ascii() {
1275 return bytes[index - 1] == b'_' || bytes[index - 1].is_ascii_alphanumeric();
1276 }
1277 let mut start = index - 1;
1278 while start > 0 && bytes[start] & 0b1100_0000 == 0b1000_0000 {
1279 start -= 1;
1280 }
1281 ::core::str::from_utf8(&bytes[start..index])
1282 .ok()
1283 .and_then(|text| text.chars().next_back())
1284 .is_some_and(|ch| ch == '_' || ch.is_alphanumeric())
1285}
1286
1287#[cfg(feature = "parser")]
1288fn scan_quoted_string_for_incomplete(bytes: &[u8], quote_index: usize) -> QuotedStringScan {
1289 let quote = bytes[quote_index];
1290 let triple =
1291 bytes.get(quote_index + 1) == Some("e) && bytes.get(quote_index + 2) == Some("e);
1292 let mut index = quote_index + if triple { 3 } else { 1 };
1293 while index < bytes.len() {
1294 if bytes[index] == b'\\' {
1295 index = (index + 2).min(bytes.len());
1296 continue;
1297 }
1298 if triple {
1299 if bytes.get(index) == Some("e)
1300 && bytes.get(index + 1) == Some("e)
1301 && bytes.get(index + 2) == Some("e)
1302 {
1303 return QuotedStringScan::Closed(index + 3);
1304 }
1305 } else if bytes[index] == quote {
1306 return QuotedStringScan::Closed(index + 1);
1307 } else if bytes[index] == b'\n' {
1308 return QuotedStringScan::Unclosed {
1309 triple: false,
1310 unescaped_newline: true,
1311 };
1312 }
1313 index += 1;
1314 }
1315 QuotedStringScan::Unclosed {
1316 triple,
1317 unescaped_newline: false,
1318 }
1319}