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
1 change: 1 addition & 0 deletions .cspell.dict/cpython.txt
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ CLASSDEREF
classdict
cmpop
codedepth
constevaluator
CODEUNIT
CONVFUNC
convparam
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_re.py
Original file line number Diff line number Diff line change
Expand Up @@ -2107,7 +2107,6 @@ def test_issue17998(self):
self.assertEqual(re.compile(pattern, re.S).findall(b'xyz'),
[b'xyz'], msg=pattern)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_match_repr(self):
for string in '[abracadabra]', S('[abracadabra]'):
m = re.search(r'(.+)(.*?)\1', string)
Expand Down
10 changes: 0 additions & 10 deletions Lib/test/test_type_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,6 @@ def inner[X]():
"""
check_syntax_error(self, code)

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised
def test_nonlocal_disallowed_02(self):
code = """
def outer2[T]():
Expand All @@ -174,7 +173,6 @@ def inner1():
"""
check_syntax_error(self, textwrap.dedent(code))

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised
def test_nonlocal_disallowed_03(self):
code = """
class Cls[T]:
Expand Down Expand Up @@ -268,7 +266,6 @@ def func[A]():
with self.assertRaisesRegex(NameError, "name 'A' is not defined"):
run_code(code)

@unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'x' is not defined
def test_method_access_01(self):
ns = run_code("""
class ClassA:
Expand Down Expand Up @@ -320,7 +317,6 @@ def funcB[B](self): ...
with self.assertRaisesRegex(NameError, "name 'B' is not defined"):
run_code(code)

@unittest.expectedFailure # TODO: RUSTPYTHON; NameError: name 'x' is not defined
def test_class_scope_interaction_01(self):
ns = run_code("""
class C:
Expand Down Expand Up @@ -486,7 +482,6 @@ class Inner[U](make_base([T for _ in (1,)]), make_base(T)):
self.assertEqual(base1.__arg__, [ns["C"].__type_params__[0]])
self.assertEqual(base2.__arg__, "class")

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: ~T != 'class'
def test_gen_exp_in_generic_method(self):
code = """
class C[T]:
Expand Down Expand Up @@ -1374,13 +1369,11 @@ def test_starred_typevartuple(self):
Ts, = ns["Alias"].__type_params__
self.assertEqual(Ts.__default__, next(iter(ns["default"])))

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: SyntaxError not raised
def test_nondefault_after_default(self):
check_syntax_error(self, "def func[T=int, U](): pass", "non-default type parameter 'U' follows default type parameter")
check_syntax_error(self, "class C[T=int, U]: pass", "non-default type parameter 'U' follows default type parameter")
check_syntax_error(self, "type A[T=int, U] = int", "non-default type parameter 'U' follows default type parameter")

@unittest.expectedFailure # TODO: RUSTPYTHON; + defined
def test_lazy_evaluation(self):
ns = run_code("""
type Alias[T = Undefined, *U = Undefined, **V = Undefined] = int
Expand Down Expand Up @@ -1431,7 +1424,6 @@ def test_symtable_key_regression_name(self):


class TestEvaluateFunctions(unittest.TestCase):
@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'TypeAliasType' object has no attribute 'evaluate_value'
def test_general(self):
type Alias = int
Alias2 = TypeAliasType("Alias2", int)
Expand Down Expand Up @@ -1459,7 +1451,6 @@ def f[T: int = int, **P = int, *Ts = int](): pass
self.assertIs(annotationlib.call_evaluate_function(case, annotationlib.Format.FORWARDREF), int)
self.assertEqual(annotationlib.call_evaluate_function(case, annotationlib.Format.STRING), 'int')

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_constraints(self):
def f[T: (int, str)](): pass
T, = f.__type_params__
Expand All @@ -1471,7 +1462,6 @@ def f[T: (int, str)](): pass
self.assertEqual(annotationlib.call_evaluate_function(case.evaluate_constraints, annotationlib.Format.FORWARDREF), (int, str))
self.assertEqual(annotationlib.call_evaluate_function(case.evaluate_constraints, annotationlib.Format.STRING), '(int, str)')

@unittest.expectedFailure # TODO: RUSTPYTHON; AttributeError: 'TypeVar' object has no attribute 'evaluate_bound'
def test_const_evaluator(self):
T = TypeVar("T", bound=int)
self.assertEqual(repr(T.evaluate_bound), "<constevaluator <class 'int'>>")
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_typing.py
Original file line number Diff line number Diff line change
Expand Up @@ -8437,7 +8437,6 @@ class Bar(NamedTuple):
self.assertIsInstance(bar.attr, Vanilla)
self.assertEqual(bar.attr.name, "attr")

@unittest.expectedFailure # TODO: RUSTPYTHON; + Error calling __set_name__ on 'Annoying' instance attr in 'NamedTupleClass'
def test_setname_raises_the_same_as_on_other_classes(self):
class CustomException(BaseException): pass

Expand Down Expand Up @@ -9433,7 +9432,6 @@ def test_repr(self):
self.assertEqual(repr(Match[str]), 'typing.Match[str]')
self.assertEqual(repr(Match[bytes]), 'typing.Match[bytes]')

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "type 're\.Match' is not an acceptable base type" does not match "type '_sre.Match' is not an acceptable base type"
def test_cannot_subclass(self):
with self.assertRaisesRegex(
TypeError,
Expand Down
14 changes: 14 additions & 0 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2461,6 +2461,10 @@ impl Compiler {
self.push_symbol_table()?;
let inner_key = self.symbol_table_stack.len() - 1;
self.enter_scope("TypeAlias", CompilerScope::TypeParams, inner_key, lineno)?;
// Evaluator takes a positional-only format parameter
self.current_code_info().metadata.argcount = 1;
self.current_code_info().metadata.posonlyargcount = 1;
self.emit_format_validation()?;
self.compile_expression(value)?;
Comment on lines +2464 to 2468
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add the format local before emit_format_validation() to keep LoadFast(0) valid.

emit_format_validation() uses LoadFast(0), but these evaluator scopes only set argcount/posonlyargcount and don’t insert "format" into varnames. That can misread a different local or panic if the varnames set is empty. Add "format" to varnames in these evaluator scopes (mirroring enter_annotation_scope).

✅ Suggested fix
@@
-                    // Evaluator takes a positional-only format parameter
+                    // Evaluator takes a positional-only format parameter
                     self.current_code_info().metadata.argcount = 1;
                     self.current_code_info().metadata.posonlyargcount = 1;
+                    self.current_code_info()
+                        .metadata
+                        .varnames
+                        .insert("format".to_owned());
                     self.emit_format_validation()?;
@@
-                    // Evaluator takes a positional-only format parameter
+                    // Evaluator takes a positional-only format parameter
                     self.current_code_info().metadata.argcount = 1;
                     self.current_code_info().metadata.posonlyargcount = 1;
+                    self.current_code_info()
+                        .metadata
+                        .varnames
+                        .insert("format".to_owned());
                     self.emit_format_validation()?;
@@
-        // Evaluator takes a positional-only format parameter
+        // Evaluator takes a positional-only format parameter
         self.current_code_info().metadata.argcount = 1;
         self.current_code_info().metadata.posonlyargcount = 1;
+        self.current_code_info()
+            .metadata
+            .varnames
+            .insert("format".to_owned());
 
         self.emit_format_validation()?;

As per coding guidelines, follow Rust best practices for error handling and memory management.

Also applies to: 2501-2505, 2646-2651

🤖 Prompt for AI Agents
In `@crates/codegen/src/compile.rs` around lines 2464 - 2468, The evaluator scopes
set argcount/posonlyargcount then call emit_format_validation(), but they don't
register the "format" local in the current code object's varnames so
emit_format_validation()'s LoadFast(0) can read the wrong slot or panic; update
the relevant evaluator scope blocks (the ones around the calls to
self.current_code_info().metadata.argcount/posonlyargcount and
emit_format_validation(), including the similar blocks at the other locations
noted) to push "format" into self.current_code_info().metadata.varnames
(mirroring enter_annotation_scope's behavior) before calling
emit_format_validation(), ensuring varnames contains "format" and preserving
Rust error handling conventions when modifying the metadata.

emit!(self, Instruction::ReturnValue);
let value_code = self.exit_scope();
Expand Down Expand Up @@ -2494,6 +2498,10 @@ impl Compiler {
let key = self.symbol_table_stack.len() - 1;
let lineno = self.get_source_line_number().get().to_u32();
self.enter_scope("TypeAlias", CompilerScope::TypeParams, key, lineno)?;
// Evaluator takes a positional-only format parameter
self.current_code_info().metadata.argcount = 1;
self.current_code_info().metadata.posonlyargcount = 1;
self.emit_format_validation()?;

let prev_ctx = self.ctx;
self.ctx = CompileContext {
Expand Down Expand Up @@ -2635,6 +2643,12 @@ impl Compiler {
// Enter scope with the type parameter name
self.enter_scope(name, CompilerScope::TypeParams, key, lineno)?;

// Evaluator takes a positional-only format parameter
self.current_code_info().metadata.argcount = 1;
self.current_code_info().metadata.posonlyargcount = 1;

self.emit_format_validation()?;

// TypeParams scope is function-like
let prev_ctx = self.ctx;
self.ctx = CompileContext {
Expand Down
54 changes: 48 additions & 6 deletions crates/codegen/src/symboltable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -528,7 +528,6 @@ impl SymbolTableAnalyzer {
if !self.tables.as_ref().is_empty() {
let scope_depth = self.tables.as_ref().len();
// check if the name is already defined in any outer scope
// therefore
if scope_depth < 2
|| self.found_in_outer_scope(&symbol.name, st_typ)
!= Some(SymbolScope::Free)
Expand All @@ -539,6 +538,25 @@ impl SymbolTableAnalyzer {
location: None,
});
}
// Check if the nonlocal binding refers to a type parameter
if symbol.flags.contains(SymbolFlags::NONLOCAL) {
for (symbols, _typ) in self.tables.iter().rev() {
if let Some(sym) = symbols.get(&symbol.name) {
if sym.flags.contains(SymbolFlags::TYPE_PARAM) {
return Err(SymbolTableError {
error: format!(
"nonlocal binding not allowed for type parameter '{}'",
symbol.name
),
location: None,
});
}
if sym.is_bound() {
break;
}
}
}
}
} else {
return Err(SymbolTableError {
error: format!(
Expand Down Expand Up @@ -933,7 +951,8 @@ impl SymbolTableBuilder {
/// Creates or reuses the annotation block for the current scope
fn enter_annotation_scope(&mut self, line_number: u32) {
let current = self.tables.last_mut().unwrap();
let can_see_class_scope = current.typ == CompilerScope::Class;
let can_see_class_scope =
current.typ == CompilerScope::Class || current.can_see_class_scope;
let has_conditional = current.has_conditional_annotations;

// Create annotation block if not exists
Expand Down Expand Up @@ -1485,6 +1504,8 @@ impl SymbolTableBuilder {
CompilerScope::TypeParams,
self.line_index_start(value.range()),
);
// Evaluator takes a format parameter
self.register_name("format", SymbolUsage::Parameter, TextRange::default())?;
if in_class {
if let Some(table) = self.tables.last_mut() {
table.can_see_class_scope = true;
Expand Down Expand Up @@ -1990,6 +2011,8 @@ impl SymbolTableBuilder {
let in_class = self.tables.last().is_some_and(|t| t.can_see_class_scope);
let line_number = self.line_index_start(expr.range());
self.enter_scope(scope_name, CompilerScope::TypeParams, line_number);
// Evaluator takes a format parameter
self.register_name("format", SymbolUsage::Parameter, TextRange::default())?;

if in_class {
if let Some(table) = self.tables.last_mut() {
Expand All @@ -2015,11 +2038,15 @@ impl SymbolTableBuilder {
fn scan_type_params(&mut self, type_params: &ast::TypeParams) -> SymbolTableResult {
// Check for duplicate type parameter names
let mut seen_names: IndexSet<&str> = IndexSet::default();
// Check for non-default type parameter after default type parameter
let mut default_seen = false;
for type_param in &type_params.type_params {
let (name, range) = match type_param {
ast::TypeParam::TypeVar(tv) => (tv.name.as_str(), tv.range),
ast::TypeParam::ParamSpec(ps) => (ps.name.as_str(), ps.range),
ast::TypeParam::TypeVarTuple(tvt) => (tvt.name.as_str(), tvt.range),
let (name, range, has_default) = match type_param {
ast::TypeParam::TypeVar(tv) => (tv.name.as_str(), tv.range, tv.default.is_some()),
ast::TypeParam::ParamSpec(ps) => (ps.name.as_str(), ps.range, ps.default.is_some()),
ast::TypeParam::TypeVarTuple(tvt) => {
(tvt.name.as_str(), tvt.range, tvt.default.is_some())
}
};
if !seen_names.insert(name) {
return Err(SymbolTableError {
Expand All @@ -2031,6 +2058,21 @@ impl SymbolTableBuilder {
),
});
}
if has_default {
default_seen = true;
} else if default_seen {
return Err(SymbolTableError {
error: format!(
"non-default type parameter '{}' follows default type parameter",
name
),
location: Some(
self.source_file
.to_source_code()
.source_location(range.start(), PositionEncoding::Utf8),
),
});
}
}

// Register .type_params as a type parameter (automatically becomes cell variable)
Expand Down
10 changes: 9 additions & 1 deletion crates/vm/src/builtins/interpolation.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
use super::{PyStr, PyStrRef, PyTupleRef, PyType, tuple::IntoPyTuple};
use super::{
PyStr, PyStrRef, PyTupleRef, PyType, PyTypeRef, genericalias::PyGenericAlias,
tuple::IntoPyTuple,
};
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
class::PyClassImpl,
Expand Down Expand Up @@ -135,6 +138,11 @@ impl PyInterpolation {
self.format_spec.clone()
}

#[pyclassmethod]
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
PyGenericAlias::from_args(cls, args, vm)
}

#[pymethod]
fn __reduce__(zelf: PyRef<Self>, vm: &VirtualMachine) -> PyTupleRef {
let cls = zelf.class().to_owned();
Expand Down
3 changes: 1 addition & 2 deletions crates/vm/src/builtins/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -552,8 +552,7 @@ impl Py<PyMemoryView> {
flags(SEQUENCE)
)]
impl PyMemoryView {
// TODO: Uncomment when Python adds __class_getitem__ to memoryview
// #[pyclassmethod]
#[pyclassmethod]
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
PyGenericAlias::from_args(cls, args, vm)
}
Expand Down
9 changes: 7 additions & 2 deletions crates/vm/src/builtins/template.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use super::{PyStr, PyTupleRef, PyType};
use crate::common::lock::LazyLock;
use super::{PyStr, PyTupleRef, PyType, PyTypeRef, genericalias::PyGenericAlias};
use crate::{
AsObject, Context, Py, PyObject, PyObjectRef, PyPayload, PyRef, PyResult, VirtualMachine,
atomic_func,
class::PyClassImpl,
common::lock::LazyLock,
function::{FuncArgs, PyComparisonValue},
protocol::{PyIterReturn, PySequenceMethods},
types::{
Expand Down Expand Up @@ -178,6 +178,11 @@ impl PyTemplate {
self.concat(&other, vm)
}

#[pyclassmethod]
fn __class_getitem__(cls: PyTypeRef, args: PyObjectRef, vm: &VirtualMachine) -> PyGenericAlias {
PyGenericAlias::from_args(cls, args, vm)
}

#[pymethod]
fn __reduce__(&self, vm: &VirtualMachine) -> PyResult<PyTupleRef> {
// Import string.templatelib._template_unpickle
Expand Down
2 changes: 1 addition & 1 deletion crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1629,7 +1629,7 @@ impl Constructor for PyType {
// PEP 678: Add a note to the original exception instead of wrapping it
// (Python 3.12+, gh-77757)
let note = format!(
"Error calling __set_name__ on '{}' instance {} in '{}'",
"Error calling __set_name__ on '{}' instance '{}' in '{}'",
obj.class().name(),
name,
typ.name()
Expand Down
4 changes: 2 additions & 2 deletions crates/vm/src/stdlib/sre.rs
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ mod _sre {
}

#[pyattr]
#[pyclass(name = "Pattern")]
#[pyclass(module = "re", name = "Pattern")]
#[derive(Debug, PyPayload)]
pub(crate) struct Pattern {
pub pattern: PyObjectRef,
Expand Down Expand Up @@ -597,7 +597,7 @@ mod _sre {
}

#[pyattr]
#[pyclass(name = "Match")]
#[pyclass(module = "re", name = "Match")]
#[derive(Debug, PyPayload)]
pub(crate) struct Match {
string: PyObjectRef,
Expand Down
Loading
Loading