Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions Lib/test/test_inspect/test_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -5987,7 +5987,6 @@ def _strip_non_python_syntax(self, input,
self.assertEqual(computed_clean_signature, clean_signature)
self.assertEqual(computed_self_parameter, self_parameter)

@unittest.expectedFailure # TODO: RUSTPYTHON; + (module, /, path, mode, *, dir_fd=None, effective_ids=False, follow_symlinks=True)
def test_signature_strip_non_python_syntax(self):
self._strip_non_python_syntax(
"($module, /, path, mode, *, dir_fd=None, " +
Expand Down Expand Up @@ -6318,7 +6317,6 @@ def test_weakref_module_has_signatures(self):
no_signature = {'ReferenceType', 'ref'}
self._test_module_has_signatures(weakref, no_signature)

@unittest.expectedFailure # TODO: RUSTPYTHON; ValueError: <function TestSignatureDefinitions.test_python_function_override_signature.<locals>.func at 0xa4c07a580> builtin has invalid signature
def test_python_function_override_signature(self):
def func(*args, **kwargs):
pass
Expand Down
6 changes: 2 additions & 4 deletions Lib/test/test_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -1290,7 +1290,7 @@ def test_post_mortem_chained():
... except Exception as e:
... pdb._post_mortem(e, instance)

>>> with PdbTestInput([ # TODO: RUSTPYTHON # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE +EXPECTED_FAILURE
>>> with PdbTestInput([ # TODO: RUSTPYTHON # doctest: +ELLIPSIS, +NORMALIZE_WHITESPACE
... 'exceptions',
... 'exceptions 0',
... '$_exception',
Expand Down Expand Up @@ -2133,7 +2133,7 @@ def test_pdb_asynctask():
>>> def test_function():
... asyncio.run(test(), loop_factory=asyncio.EventLoop)

>>> with PdbTestInput([ # TODO: RUSTPYTHON # doctest: +ELLIPSIS +EXPECTED_FAILURE
>>> with PdbTestInput([ # doctest: +ELLIPSIS
... '$_asynctask',
... 'continue',
... ]):
Expand Down Expand Up @@ -4185,7 +4185,6 @@ def test_blocks_at_first_code_line(self):
self.assertTrue(any("__main__.py(4)<module>()"
in l for l in stdout.splitlines()), stdout)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_file_modified_after_execution(self):
script = """
print("hello")
Expand Down Expand Up @@ -4259,7 +4258,6 @@ def test_file_modified_after_execution_with_multiple_instances(self):
self.assertIn("WARNING:", stdout)
self.assertIn("was edited", stdout)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_file_modified_after_execution_with_restart(self):
script = """
import random
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_pydoc/test_pydoc.py
Original file line number Diff line number Diff line change
Expand Up @@ -1708,7 +1708,6 @@ def test_bound_builtin_classmethod_unrepresentable_default(self):
"classmeth(a, b=<x>) class method of "
"_testcapi.DocStringUnrepresentableSignatureTest")

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_overridden_text_signature(self):
class C:
def meth(*args, **kwargs):
Expand Down
3 changes: 0 additions & 3 deletions Lib/test/test_re.py
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,6 @@ def test_named_unicode_escapes(self):
self.checkPatternError(br'\N{LESS-THAN SIGN}', r'bad escape \N', 0)
self.checkPatternError(br'[\N{LESS-THAN SIGN}]', r'bad escape \N', 1)

@unittest.expectedFailure # TODO: RUSTPYTHON; re.search(r"\B", "") now returns a match in CPython 3.14
def test_word_boundaries(self):
# See http://bugs.python.org/issue10713
self.assertEqual(re.search(r"\b(abc)\b", "abc").group(1), "abc")
Expand Down Expand Up @@ -2493,7 +2492,6 @@ def test_search_anchor_at_beginning(self):
# With optimization -- 0.0003 seconds.
self.assertLess(stopwatch.seconds, 0.1)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_possessive_quantifiers(self):
"""Test Possessive Quantifiers
Test quantifiers of the form @+ for some repetition operator @,
Expand Down Expand Up @@ -2647,7 +2645,6 @@ def test_bug_gh100061(self):
self.assertEqual(re.match("(?>(?:ab?c){1,3})", "aca").span(), (0, 2))
self.assertEqual(re.match("(?:ab?c){1,3}+", "aca").span(), (0, 2))

@unittest.expectedFailure # TODO: RUSTPYTHON; self.assertEqual(re.match('((x)|y|z){3}+', 'xyz').groups(), ('z', 'x'))\n AssertionError: Tuples differ: ('x', 'x') != ('z', 'x')
def test_bug_gh101955(self):
# Possessive quantifier with nested alternative with capture groups
self.assertEqual(re.match('((x)|y|z)*+', 'xyz').groups(), ('z', 'x'))
Expand Down
1 change: 1 addition & 0 deletions Lib/test/test_unittest/testmock/testhelpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,7 @@ def check_data_descriptor(mock_attr):
check_data_descriptor(foo.desc)


@unittest.expectedFailure # TODO: RUSTPYTHON; time.ctime has an auto-generated __text_signature__, so inspect.signature() succeeds instead of raising ValueError
def test_autospec_on_bound_builtin_function(self):
meth = types.MethodType(time.ctime, time.time())
self.assertIsInstance(meth(), str)
Expand Down
9 changes: 8 additions & 1 deletion crates/codegen/src/symboltable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3356,7 +3356,14 @@ impl SymbolTableBuilder {
flags.insert(SymbolFlags::USE);
}
SymbolUsage::Iter => {
flags.insert(SymbolFlags::ITER | SymbolFlags::DEF_COMP_ITER);
// CPython symtable_add_def_helper() records an inlined
// comprehension target as a local definition as well as a
// comprehension iterator. Keep ITER as the internal
// re-assignment check marker; DEF_LOCAL is part of the public
// ste_symbols flags exposed by _symtable.
flags.insert(
SymbolFlags::DEF_LOCAL | SymbolFlags::ITER | SymbolFlags::DEF_COMP_ITER,
);
}
SymbolUsage::TypeParam => {
flags.insert(SymbolFlags::DEF_LOCAL | SymbolFlags::DEF_TYPE_PARAM);
Expand Down
21 changes: 21 additions & 0 deletions crates/host_env/src/os.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
use crate::crt_fd;
#[cfg(windows)]
use crate::fs;
#[cfg(windows)]
pub use crate::posix::rename;
#[cfg(any(unix, target_os = "wasi"))]
pub use crate::posix_unix_like::rename;
#[cfg(any(unix, windows))]
use core::ffi::CStr;
use core::str::Utf8Error;
Expand All @@ -28,6 +32,23 @@ use {
},
};

#[cfg(not(any(unix, windows, target_os = "wasi")))]
pub fn rename(
from: impl AsRef<std::path::Path>,
from_fd: Option<crt_fd::Borrowed<'_>>,
to: impl AsRef<std::path::Path>,
to_fd: Option<crt_fd::Borrowed<'_>>,
) -> io::Result<()> {
if from_fd.is_none() && to_fd.is_none() {
std::fs::rename(from, to)
} else {
Err(io::Error::new(
io::ErrorKind::Unsupported,
"renameat is not available on this platform",
))
}
}

/// Convert exit code to std::process::ExitCode
///
/// On Windows, this supports the full u32 range including STATUS_CONTROL_C_EXIT (0xC000013A).
Expand Down
28 changes: 25 additions & 3 deletions crates/sre_engine/src/engine.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,21 @@ impl Marks {
self.marks_stack.pop();
}

fn stack_depth(&self) -> usize {
self.marks_stack.len()
}

fn discard_to(&mut self, depth: usize) {
self.marks_stack.truncate(depth);
}

fn restore_to(&mut self, depth: usize) {
let (marks, last_index) = self.marks_stack[depth].clone();
self.marks = marks;
self.last_index = last_index;
self.marks_stack.truncate(depth);
}

fn clear(&mut self) {
self.last_index = -1;
self.marks.clear();
Expand Down Expand Up @@ -144,6 +159,7 @@ impl State {
jump: Jump::OpCode,
repeat_ctx_id: usize::MAX,
count: -1,
marks_stack_base: usize::MAX,
};
_match(req, self, ctx)
}
Expand All @@ -165,6 +181,7 @@ impl State {
jump: Jump::OpCode,
repeat_ctx_id: usize::MAX,
count: -1,
marks_stack_base: usize::MAX,
};

if ctx.peek_code(&req, 0) == SreOpcode::INFO as u32 {
Expand Down Expand Up @@ -483,6 +500,7 @@ fn _match<S: StrDrive>(req: &Request<'_, S>, state: &mut State, mut ctx: MatchCo
}
Jump::PossessiveRepeat2 => {
if popped_result {
ctx.cursor = state.cursor;
ctx.count += 1;
ctx.jump = Jump::PossessiveRepeat1;
continue 'context;
Expand All @@ -495,6 +513,7 @@ fn _match<S: StrDrive>(req: &Request<'_, S>, state: &mut State, mut ctx: MatchCo
if ((ctx.count as usize) < max_count || max_count == MAXREPEAT)
&& ctx.cursor.position != state.cursor.position
{
ctx.marks_stack_base = state.marks.stack_depth();
state.marks.push();
ctx.cursor = state.cursor;
let mut next = ctx.next_offset(4, Jump::PossessiveRepeat4);
Expand All @@ -507,12 +526,12 @@ fn _match<S: StrDrive>(req: &Request<'_, S>, state: &mut State, mut ctx: MatchCo
}
Jump::PossessiveRepeat4 => {
if popped_result {
state.marks.pop_discard();
state.marks.discard_to(ctx.marks_stack_base);
ctx.count += 1;
ctx.jump = Jump::PossessiveRepeat3;
continue 'context;
}
state.marks.pop();
state.marks.restore_to(ctx.marks_stack_base);
state.cursor = ctx.cursor;
ctx.skip_code_from(req, 1);
ctx.skip_code(1);
Expand Down Expand Up @@ -1057,6 +1076,7 @@ struct MatchContext {
jump: Jump,
repeat_ctx_id: usize,
count: isize,
marks_stack_base: usize,
}

impl MatchContext {
Expand Down Expand Up @@ -1147,7 +1167,9 @@ impl MatchContext {
mut word_checker: F,
) -> bool {
if self.at_beginning() && self.at_end(req) {
return false;
// Python 3.14 changed `\B` to match an empty input. Keep the
// boundary predicate false there, but its negation true.
return true;
}
let that = !self.at_beginning() && word_checker(self.back_peek_char::<S>());
let this = !self.at_end(req) && word_checker(self.peek_char::<S>());
Expand Down
54 changes: 53 additions & 1 deletion crates/sre_engine/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
#[cfg(test)]
mod tests {
use rustpython_sre_engine::{Request, State, StrDrive};
use rustpython_wtf8::Wtf8Buf;

struct Pattern {
#[expect(unused, reason = "Needed for automated script")]
Expand Down Expand Up @@ -44,7 +45,7 @@ mod tests {
#[rustfmt::skip] let big_b = Pattern { pattern: "\\B", code: &[14, 4, 0, 0, 0, 6, 11, 1] };
// END GENERATED
let (req, mut state) = big_b.state("");
assert!(!state.search(req));
assert!(state.search(req));
}

#[test]
Expand Down Expand Up @@ -169,6 +170,40 @@ mod tests {
assert!(!state.py_match(&req));
}

#[test]
fn possessive_repeat_keeps_last_capture() {
use optional::Optioned;

let single_code = &[17, 0, 24, 6, 0, 1, 16, 101, 1, 17, 1, 1];
let req = Request::new("eeea", 3, usize::MAX, single_code, false);
let mut single_state = State::default();
assert!(single_state.py_match(&req));
assert_eq!(
single_state.marks.get(0),
(Optioned::some(3), Optioned::some(3))
);

// (e?){2,4}+a: the fourth successful iteration is empty, so group 1
// must retain its final empty span rather than the previous "e".
#[rustfmt::skip] let optional = Pattern {
pattern: "(e?){2,4}+a",
code: &[14, 4, 0, 1, 5, 28, 14, 2, 4, 17, 0, 24, 6, 0, 1, 16, 101, 1, 17, 1, 1, 16, 97, 1],
};
let (req, mut state) = optional.state("eeea");
assert!(state.py_match(&req));
assert_eq!(state.marks.get(0), (Optioned::some(3), Optioned::some(3)));

// ((x)|y|z){3}+: group 1 is the final "z"; group 2 retains "x".
#[rustfmt::skip] let alternation = Pattern {
pattern: "((x)|y|z){3}+",
code: &[14, 4, 0, 3, 3, 28, 28, 3, 3, 17, 0, 7, 9, 17, 2, 16, 120, 17, 3, 15, 12, 5, 16, 121, 15, 7, 5, 16, 122, 15, 2, 0, 17, 1, 1, 1],
};
let (req, mut state) = alternation.state("xyz");
assert!(state.py_match(&req));
assert_eq!(state.marks.get(0), (Optioned::some(2), Optioned::some(3)));
assert_eq!(state.marks.get(1), (Optioned::some(0), Optioned::some(1)));
}

#[test]
fn bug_20998() {
// pattern p = re.compile('[a-c]+', re.I)
Expand All @@ -181,6 +216,23 @@ mod tests {
assert_eq!(state.cursor.position, 3);
}

#[test]
fn ascii_ignore_keeps_nonascii_range_literal() {
// pattern p = re.compile(r'[\u0430-\u045f]', re.I | re.A)
//
// ASCII-only case folding must not discard an exact non-ASCII range:
// U+0450 lies in the compiled U+0430..U+045F interval.
#[rustfmt::skip] let p = Pattern {
pattern: "[\\u0430-\\u045f]",
code: &[14, 8, 4, 1, 1, 22, 1072, 1119, 0, 13, 5, 22, 1072, 1119, 0, 1],
};
let (req, mut state) = p.state("\u{0450}");
assert!(state.py_match(&req));
let subject = Wtf8Buf::from("\u{0450}");
let (req, mut state) = p.state(subject.as_ref());
assert!(state.py_match(&req));
}

#[test]
fn bigcharset() {
// pattern p = re.compile('[a-z]*', re.I)
Expand Down
4 changes: 3 additions & 1 deletion crates/stdlib/src/_tokenize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,9 @@ mod _tokenize {
}

let raw_type = token_kind_value(kind);
let token_type = if extra_tokens && raw_type > TOKEN_DEDENT && raw_type < TOKEN_OP {
let token_type = if extra_tokens
&& (kind == TokenKind::Unknown || (raw_type > TOKEN_DEDENT && raw_type < TOKEN_OP))
{
TOKEN_OP
} else {
raw_type
Expand Down
Loading