GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — Implement more typing by youknowone · Pull Request #7078 · RustPython/RustPython

Implement more typing #7078

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
youknowone merged 4 commits into RustPython:main from youknowone:typing
Feb 11, 2026

Uh oh!

There was an error while loading. Please reload this page.

Merged

Implement more typing #7078

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
4 commits
Select commit Hold shift + click to select a range
d1e8122
Add _ConstEvaluator, evaluate_* getters, format validation
youknowone Feb 11, 2026
c91f27c
Fix nonlocal type param check, __class_getitem__, __set_name__ note, …
youknowone Feb 11, 2026
6a6e375
Propagate can_see_class_scope to annotation scopes in type param blocks
youknowone Feb 11, 2026
5242dd4
Rename constevaluator_alloc to const_evaluator_alloc, add to cspell dict
youknowone Feb 11, 2026
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

1 change: 1 addition & 0 deletions 1 .cspell.dict/cpython.txt
Show comments
View file
Open in desktop
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 1 Lib/test/test_re.py
Show comments
View file
Open in desktop
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 10 Lib/test/test_type_params.py
Show comments
View file
Open in desktop
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 2 Lib/test/test_typing.py
Show comments
View file
Open in desktop
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 14 crates/codegen/src/compile.rs
Show comments
View file
Open in desktop
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 thread
Comment on lines +2464 to 2468

@coderabbitai coderabbitai Bot Feb 11, 2026

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

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.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
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 54 crates/codegen/src/symboltable.rs
Show comments
View file
Open in desktop
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 10 crates/vm/src/builtins/interpolation.rs
Show comments
View file
Open in desktop
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 3 crates/vm/src/builtins/memory.rs
Show comments
View file
Open in desktop
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 9 crates/vm/src/builtins/template.rs
Show comments
View file
Open in desktop
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 2 crates/vm/src/builtins/type.rs
Show comments
View file
Open in desktop
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 4 crates/vm/src/stdlib/sre.rs
Show comments
View file
Open in desktop
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
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Loading
Oops, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402503 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.