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 Lib/ctypes/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,7 @@ def __repr__(self):
return super().__repr__()
except ValueError:
return "%s(<NULL>)" % type(self).__name__
__class_getitem__ = classmethod(_types.GenericAlias)
_check_size(py_object, "P")

class c_short(_SimpleCData):
Expand Down
1 change: 0 additions & 1 deletion Lib/test/test_dataclasses/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ def test_field_recursive_repr(self):

self.assertIn(",type=...,", repr_output)

@unittest.expectedFailure # TODO: RUSTPYTHON; recursive annotation type not shown as ...
def test_recursive_annotation(self):
class C:
pass
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_genericalias.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,6 @@ class BaseTest(unittest.TestCase):
if Event is not None:
generic_types.append(Event)

@unittest.expectedFailure # TODO: RUSTPYTHON; memoryview, Template, Interpolation, py_object not subscriptable
def test_subscriptable(self):
for t in self.generic_types:
if t is None:
Expand Down Expand Up @@ -507,7 +506,6 @@ def test_dir(self):
with self.subTest(entry=entry):
getattr(ga, entry) # must not raise `AttributeError`

@unittest.expectedFailure # TODO: RUSTPYTHON; memoryview, Template, Interpolation, py_object not subscriptable
def test_weakref(self):
for t in self.generic_types:
if t is None:
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_type_annotations.py
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,6 @@ class Nested: ...
self.assertEqual(Outer.meth.__annotations__, {"x": Outer.Nested})
self.assertEqual(Outer.__annotations__, {"x": Outer.Nested})

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_no_exotic_expressions(self):
preludes = [
"",
Expand All @@ -431,7 +430,6 @@ def test_no_exotic_expressions(self):
check_syntax_error(self, prelude + "def func(x: {y async for y in x}): ...", "asynchronous comprehension outside of an asynchronous function")
check_syntax_error(self, prelude + "def func(x: {y: y async for y in x}): ...", "asynchronous comprehension outside of an asynchronous function")

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_no_exotic_expressions_in_unevaluated_annotations(self):
preludes = [
"",
Expand Down
2 changes: 0 additions & 2 deletions Lib/test/test_type_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,6 @@ def test_disallowed_expressions(self):
check_syntax_error(self, "def f[T: [(x := 3) for _ in range(2)]](): pass")
check_syntax_error(self, "type T = [(x := 3) for _ in range(2)]")

@unittest.expectedFailure # TODO: RUSTPYTHON; AssertionError: "\(MRO\) for bases object, Generic" does not match "Unable to find mro order which keeps local precedence ordering"
def test_incorrect_mro_explicit_object(self):
with self.assertRaisesRegex(TypeError, r"\(MRO\) for bases object, Generic"):
class My[X](object): ...
Expand Down Expand Up @@ -1215,7 +1214,6 @@ def test_pickling_functions(self):
pickled = pickle.dumps(thing, protocol=proto)
self.assertEqual(pickle.loads(pickled), thing)

@unittest.expectedFailure # TODO: RUSTPYTHON
def test_pickling_classes(self):
things_to_test = [
Class1,
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 @@ -4209,7 +4209,6 @@ class P(Protocol):
Alias2 = typing.Union[P, typing.Iterable]
self.assertEqual(Alias, Alias2)

@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Generic() takes no arguments
def test_protocols_pickleable(self):
global P, CP # pickle wants to reference the class by name
T = TypeVar('T')
Expand Down Expand Up @@ -5287,7 +5286,6 @@ def test_all_repr_eq_any(self):
self.assertNotEqual(repr(base), '')
self.assertEqual(base, base)

@unittest.expectedFailure # TODO: RUSTPYTHON; TypeError: Generic() takes no arguments
def test_pickle(self):
global C # pickle wants to reference the class by name
T = TypeVar('T')
Expand Down
57 changes: 47 additions & 10 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1226,21 +1226,35 @@ impl Compiler {
}

/// Exit annotation scope - similar to exit_scope but restores annotation_block to parent
fn exit_annotation_scope(&mut self) -> CodeObject {
fn exit_annotation_scope(&mut self, saved_ctx: CompileContext) -> CodeObject {
self.pop_annotation_symbol_table();
self.ctx = saved_ctx;

let pop = self.code_stack.pop();
let stack_top = compiler_unwrap_option(self, pop);
unwrap_internal(self, stack_top.finalize_code(&self.opts))
}

/// Enter annotation scope using the symbol table's annotation_block
/// Returns false if no annotation_block exists
fn enter_annotation_scope(&mut self, _func_name: &str) -> CompileResult<bool> {
/// Enter annotation scope using the symbol table's annotation_block.
/// Returns None if no annotation_block exists.
/// On success, returns the saved CompileContext to pass to exit_annotation_scope.
fn enter_annotation_scope(
&mut self,
_func_name: &str,
) -> CompileResult<Option<CompileContext>> {
if !self.push_annotation_symbol_table() {
return Ok(false);
return Ok(None);
}

// Annotation scopes are never async (even inside async functions)
let saved_ctx = self.ctx;
self.ctx = CompileContext {
loop_data: None,
in_class: saved_ctx.in_class,
func: FunctionContext::Function,
in_async_scope: false,
};

let key = self.symbol_table_stack.len() - 1;
let lineno = self.get_source_line_number().get();
self.enter_scope(
Expand All @@ -1261,7 +1275,7 @@ impl Compiler {
// VALUE_WITH_FAKE_GLOBALS = 2 (from annotationlib.Format)
self.emit_format_validation()?;

Ok(true)
Ok(Some(saved_ctx))
}

/// Emit format parameter validation for annotation scope
Expand Down Expand Up @@ -2477,6 +2491,10 @@ impl Compiler {
// 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());
Comment on lines +2494 to +2497
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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the relevant code sections to understand the context
cd crates/codegen/src && cat -n compile.rs | sed -n '2490,2500p'

Repository: RustPython/RustPython

Length of output: 787


🏁 Script executed:

# Check the other mentioned locations
cd crates/codegen/src && cat -n compile.rs | sed -n '2530,2545p'

Repository: RustPython/RustPython

Length of output: 1028


🏁 Script executed:

# And the third location
cd crates/codegen/src && cat -n compile.rs | sed -n '2680,2695p'

Repository: RustPython/RustPython

Length of output: 703


🏁 Script executed:

# Now let's find the emit_format_validation function
cd crates/codegen/src && rg -A 20 "fn emit_format_validation"

Repository: RustPython/RustPython

Length of output: 1004


🏁 Script executed:

# Check what IndexSet::insert does - search for IndexSet usage and documentation
rg "IndexSet" crates/codegen/src/compile.rs | head -20

Repository: RustPython/RustPython

Length of output: 822


🏁 Script executed:

# Let's check the type definition of varnames to confirm it's an IndexSet
cd crates/codegen/src && rg "varnames.*IndexSet" -A 2 -B 2

Repository: RustPython/RustPython

Length of output: 672


🏁 Script executed:

# Let's also check if there's a method to get local var index by name
cd crates/codegen/src && rg "get_local_var_index" -A 5

Repository: RustPython/RustPython

Length of output: 738


🏁 Script executed:

# Let's check the enter_scope implementation to see if varnames is initialized fresh
cd crates/codegen/src && rg "fn enter_scope" -A 30

Repository: RustPython/RustPython

Length of output: 4782


🏁 Script executed:

# Let's also check if CodeInfo is initialized fresh when entering a scope
cd crates/codegen/src && rg "struct CodeInfo" -A 20

Repository: RustPython/RustPython

Length of output: 834


🏁 Script executed:

# Check what happens to metadata when entering a new scope - look for initialization
cd crates/codegen/src && rg "metadata.*CodeMetadata" -B 2 -A 2

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Let's look at the complete context around the first occurrence to see the scope entry
cd crates/codegen/src && cat -n compile.rs | sed -n '2485,2505p'

Repository: RustPython/RustPython

Length of output: 1376


🏁 Script executed:

# Let's check if there are any other variables added to varnames in these scopes before format
cd crates/codegen/src && cat -n compile.rs | sed -n '2680,2710p'

Repository: RustPython/RustPython

Length of output: 1296


🏁 Script executed:

# Let's check how the symbol table is built for TypeParams scopes and if "format" is already in the symbol table
cd crates/codegen/src && rg "TypeParams" symboltable.rs -B 5 -A 10

Repository: RustPython/RustPython

Length of output: 7690


🏁 Script executed:

# Let's look at how varnames are populated in the symbol table during the first pass
cd crates/codegen/src && rg "fn scan_type_param" symboltable.rs -A 30

Repository: RustPython/RustPython

Length of output: 2926


🏁 Script executed:

# Check for "format" parameter being added to symbol table during analysis phase
rg "\"format\"" crates/codegen/src/symboltable.rs -B 3 -A 3

Repository: RustPython/RustPython

Length of output: 1573


🏁 Script executed:

# Let's trace through the complete enter_scope to see how metadata.varnames is initialized
cd crates/codegen/src && cat -n compile.rs | sed -n '2200,2320p'

Repository: RustPython/RustPython

Length of output: 5711


🏁 Script executed:

# Check what happens to varnames from the symbol table when CodeInfo is created
cd crates/codegen/src && rg "CodeUnitMetadata" -A 15 | head -60

Repository: RustPython/RustPython

Length of output: 2709


🏁 Script executed:

# Let's see the complete enter_scope implementation to find where varnames is initialized
cd crates/codegen/src && cat -n compile.rs | sed -n '655,780p'

Repository: RustPython/RustPython

Length of output: 5474


🏁 Script executed:

# Look for where CodeInfo is created with the varname_cache
cd crates/codegen/src && rg "varname_cache" compile.rs -B 5 -A 5

Repository: RustPython/RustPython

Length of output: 949


🏁 Script executed:

# Let's check if there's code that pushes the CodeInfo with initialized metadata
cd crates/codegen/src && cat -n compile.rs | sed -n '780,850p'

Repository: RustPython/RustPython

Length of output: 3090


🏁 Script executed:

# Let's look at the SymbolTableEntry structure to understand what varnames contains
cd crates/codegen/src && rg "struct SymbolTableEntry" symboltable.rs -A 10

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Check what register_name does with parameters
cd crates/codegen/src && rg "fn register_name" symboltable.rs -A 20

Repository: RustPython/RustPython

Length of output: 853


🏁 Script executed:

# Let's verify that "format" is actually in the symbol table for these TypeParams scopes
# by checking what varnames ste contains when enter_scope is called
cd crates/codegen/src && cat -n compile.rs | sed -n '2470,2505p'

Repository: RustPython/RustPython

Length of output: 2232


🏁 Script executed:

# Check if there are any other parameters or locals that might be added to these TypeParams scopes before format
cd crates/codegen/src && rg "enter_scope.*TypeParams" -B 15 -A 30 | head -100

Repository: RustPython/RustPython

Length of output: 5578


🏁 Script executed:

# Let's verify the complete flow by checking what symbol table entries are registered for one of these TypeParams scopes
# Look at the specific case: the inner TypeAlias scope at line 2490
cd crates/codegen/src && rg -B 50 'self.enter_scope.*"TypeAlias".*TypeParams' symboltable.rs | grep -A 50 "compile_type_alias" | head -80

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Let's check if there's any code that modifies varnames after enter_scope and before emit_format_validation
cd crates/codegen/src && cat -n compile.rs | sed -n '2488,2502p'

Repository: RustPython/RustPython

Length of output: 1079


🏁 Script executed:

# Check IndexSet behavior - does insert return early if already present?
web_search
IndexSet insert behavior - does it append or return if already present

Repository: RustPython/RustPython

Length of output: 162


🌐 Web query:

indexmap IndexSet insert behavior documentation

💡 Result:

In indexmap::IndexSet, insert behaves like this:

  • IndexSet::insert(&mut self, value: T) -> bool inserts value only if an equivalent value is not already present.
    • Returns true if it inserted a new element.
    • Returns false if the set already contained an equivalent element; in that case it keeps the original stored value and does not change its insertion order. [1]

Related ordering note:

  • IndexSet iteration order is determined by the history of insertions/removals, and a re-insertion attempt of an already-present element does not affect the order or stored value. [2]

If you also need the index:

  • IndexSet::insert_full(&mut self, value: T) -> (usize, bool) returns (index, inserted?) with the same “no replace, no reorder on duplicates” behavior. [3]

Sources:
[1] https://docs.rs/indexmap/latest/indexmap/set/struct.IndexSet.html (method insert)
[2] https://docs.rs/indexmap/latest/indexmap/set/struct.IndexSet.html (section “Order”)
[3] https://docs.rs/indexmap/latest/indexmap/set/struct.IndexSet.html (method insert_full)


🏁 Script executed:

# Let's check the documentation and implementation by looking at what methods are available on IndexSet
# and verify the behavior of insert
cd crates/codegen/src && rg "IndexSet<String>" compile.rs -B 2 -A 2 | head -30

Repository: RustPython/RustPython

Length of output: 479


🏁 Script executed:

# Let's trace through what happens:
# 1. enter_scope initializes metadata.varnames from symbol table
# 2. Then we manually call insert("format")
# Let's check if "format" would already be in varnames from the symbol table
cd crates/codegen/src && cat -n compile.rs | sed -n '2440,2520p'

Repository: RustPython/RustPython

Length of output: 4561


🏁 Script executed:

# Check the symbol_table_stack to understand what key is passed to enter_scope
cd crates/codegen/src && cat -n compile.rs | sed -n '2410,2495p'

Repository: RustPython/RustPython

Length of output: 4487


🏁 Script executed:

# Let's verify that "format" is indeed registered in the symbol table during analysis
# by checking the flow for the inner TypeAlias scope case
cd crates/codegen/src && rg "scan_expression.*TypeAlias\|StmtTypeAlias" symboltable.rs -B 5 -A 20

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Let's check how StmtTypeAlias is scanned in the symbol table
cd crates/codegen/src && rg "StmtTypeAlias" symboltable.rs -B 2 -A 20

Repository: RustPython/RustPython

Length of output: 937


🏁 Script executed:

# Check if the inner closure's symbol table is created during analysis with "format" already registered
cd crates/codegen/src && rg "scan_type_alias\|visit_type_alias" symboltable.rs -B 5 -A 30

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Let's get the complete StmtTypeAlias scanning logic
cd crates/codegen/src && rg "Stmt::TypeAlias" symboltable.rs -A 50

Repository: RustPython/RustPython

Length of output: 2089


🏁 Script executed:

# Let's check the specific part where the value scope is created for TypeAlias
cd crates/codegen/src && cat -n symboltable.rs | sed -n '1900,2050p'

Repository: RustPython/RustPython

Length of output: 7510


🏁 Script executed:

# Let's verify the complete picture by checking what register_name does with Parameters
cd crates/codegen/src && rg "register_name.*Parameter" symboltable.rs -B 2 -A 5

Repository: RustPython/RustPython

Length of output: 1189


🏁 Script executed:

# Check what SymbolUsage::Parameter means and how it's processed
cd crates/codegen/src && rg "enum SymbolUsage\|SymbolUsage::Parameter" symboltable.rs -B 2 -A 15

Repository: RustPython/RustPython

Length of output: 47


🏁 Script executed:

# Find the SymbolUsage enum definition
cd crates/codegen/src && rg "enum SymbolUsage" -A 20

Repository: RustPython/RustPython

Length of output: 829


🏁 Script executed:

# Let's also check how register_name processes parameters
cd crates/codegen/src && rg "fn register_name" symboltable.rs -A 40 | head -60

Repository: RustPython/RustPython

Length of output: 1714


🏁 Script executed:

# Let's verify the key question: after enter_scope, is "format" already in varnames from the symbol table?
# Check what happens in leave_scope during symbol table analysis
cd crates/codegen/src && cat -n symboltable.rs | sed -n '785,810p'

Repository: RustPython/RustPython

Length of output: 1380


🏁 Script executed:

# Let's trace through: when register_name is called with Parameter, where does it go?
cd crates/codegen/src && rg "fn register_name" symboltable.rs -A 70 | head -100

Repository: RustPython/RustPython

Length of output: 3150


🏁 Script executed:

# Let's check if register_name adds parameters to current_varnames
cd crates/codegen/src && rg "current_varnames" symboltable.rs -B 3 -A 3

Repository: RustPython/RustPython

Length of output: 4291


🏁 Script executed:

# Let's find where parameters are added to varnames during symbol table analysis
cd crates/codegen/src && rg "SymbolUsage::Parameter" symboltable.rs -B 5 -A 10 | head -50

Repository: RustPython/RustPython

Length of output: 1977


🏁 Script executed:

# Check the complete register_name function to see what happens with Parameter role
cd crates/codegen/src && cat -n symboltable.rs | sed -n '275,400p'

Repository: RustPython/RustPython

Length of output: 5569


🏁 Script executed:

# Let me verify the order: is "format" the FIRST parameter registered in these TypeParams scopes?
cd crates/codegen/src && cat -n symboltable.rs | sed -n '1865,1895p'

Repository: RustPython/RustPython

Length of output: 1558


🏁 Script executed:

# Also check for the bound/default case
cd crates/codegen/src && cat -n symboltable.rs | sed -n '2034,2048p'

Repository: RustPython/RustPython

Length of output: 911


Use get_local_var_index() to load format parameter instead of hardcoding index 0.

During symbol table analysis, "format" is registered as a parameter in these TypeParams scopes before any other symbols, so it correctly appears at index 0. However, relying on this implicit ordering is fragile. Use get_local_var_index("format") in emit_format_validation() to explicitly resolve the parameter's index, making the intent clear and the code more resilient to future changes.

🛠️ Suggested fix in emit_format_validation
-        emit!(self, Instruction::LoadFast(0));
+        let format_idx = self.get_local_var_index("format")?;
+        emit!(self, Instruction::LoadFast(format_idx));

Also applies to: 2535-2538, 2684-2687

🤖 Prompt for AI Agents
In `@crates/codegen/src/compile.rs` around lines 2494 - 2497, The code currently
inserts "format" into current_code_info().metadata.varnames and later assumes
its local slot is 0; update emit_format_validation() (and the similar spots at
the other occurrences) to resolve the local slot via
get_local_var_index("format") instead of hardcoding 0: use
get_local_var_index("format") to obtain the parameter index and emit
loads/validation against that index, leaving the varnames insertion as-is but
making the load robust to symbol ordering changes.

self.emit_format_validation()?;
self.compile_expression(value)?;
emit!(self, Instruction::ReturnValue);
Expand Down Expand Up @@ -2514,6 +2532,10 @@ impl Compiler {
// 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()?;

let prev_ctx = self.ctx;
Expand Down Expand Up @@ -2659,6 +2681,10 @@ impl Compiler {
// 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()?;

Expand Down Expand Up @@ -3787,10 +3813,10 @@ impl Compiler {
parameters: &ast::Parameters,
returns: Option<&ast::Expr>,
) -> CompileResult<bool> {
// Try to enter annotation scope - returns false if no annotation_block exists
if !self.enter_annotation_scope(func_name)? {
// Try to enter annotation scope - returns None if no annotation_block exists
let Some(saved_ctx) = self.enter_annotation_scope(func_name)? else {
return Ok(false);
}
};

// Count annotations
let parameters_iter = core::iter::empty()
Expand Down Expand Up @@ -3842,7 +3868,7 @@ impl Compiler {
emit!(self, Instruction::ReturnValue);

// Exit the annotation scope and get the code object
let annotate_code = self.exit_annotation_scope();
let annotate_code = self.exit_annotation_scope(saved_ctx);

// Make a closure from the code object
self.make_closure(annotate_code, bytecode::MakeFunctionFlags::empty())?;
Expand Down Expand Up @@ -3935,6 +3961,15 @@ impl Compiler {
return Ok(false);
}

// Annotation scopes are never async (even inside async functions)
let saved_ctx = self.ctx;
self.ctx = CompileContext {
loop_data: None,
in_class: saved_ctx.in_class,
func: FunctionContext::Function,
in_async_scope: false,
};

// Enter annotation scope for code generation
let key = self.symbol_table_stack.len() - 1;
let lineno = self.get_source_line_number().get();
Expand Down Expand Up @@ -4031,6 +4066,8 @@ impl Compiler {
.last_mut()
.expect("no module symbol table")
.annotation_block = Some(Box::new(annotation_table));
// Restore context
self.ctx = saved_ctx;
// Exit code scope
let pop = self.code_stack.pop();
let annotate_code = unwrap_internal(
Expand Down
43 changes: 34 additions & 9 deletions crates/codegen/src/symboltable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1026,6 +1026,26 @@ impl SymbolTableBuilder {
.insert(SymbolFlags::REFERENCED | SymbolFlags::FREE_CLASS);
}

/// Walk up the scope chain to determine if we're inside an async function.
/// Annotation and TypeParams scopes act as async barriers (always non-async).
/// Comprehension scopes are transparent (inherit parent's async context).
fn is_in_async_context(&self) -> bool {
for table in self.tables.iter().rev() {
match table.typ {
CompilerScope::AsyncFunction => return true,
CompilerScope::Function
| CompilerScope::Lambda
| CompilerScope::Class
| CompilerScope::Module
| CompilerScope::Annotation
| CompilerScope::TypeParams => return false,
// Comprehension inherits parent's async context
CompilerScope::Comprehension => continue,
}
}
false
}

fn line_index_start(&self, range: TextRange) -> u32 {
self.source_file
.to_source_code()
Expand Down Expand Up @@ -1128,15 +1148,6 @@ impl SymbolTableBuilder {

self.leave_annotation_scope();

// Module scope: re-scan to register symbols (builtins like str, int)
// Class scope: do NOT re-scan to preserve class-local symbol resolution
if matches!(current_scope, Some(CompilerScope::Module)) {
let was_in_annotation = self.in_annotation;
self.in_annotation = true;
let _ = self.scan_expression(annotation, ExpressionContext::Load);
self.in_annotation = was_in_annotation;
}

result
}

Expand Down Expand Up @@ -1939,6 +1950,20 @@ impl SymbolTableBuilder {
range: TextRange,
is_generator: bool,
) -> SymbolTableResult {
// Check for async comprehension outside async function
// (list/set/dict comprehensions only, not generator expressions)
let has_async_gen = generators.iter().any(|g| g.is_async);
if has_async_gen && !is_generator && !self.is_in_async_context() {
return Err(SymbolTableError {
error: "asynchronous comprehension outside of an asynchronous function".to_owned(),
location: Some(
self.source_file
.to_source_code()
.source_location(range.start(), PositionEncoding::Utf8),
),
});
}

// Comprehensions are compiled as functions, so create a scope for them:
self.enter_scope(
scope_name,
Expand Down
42 changes: 23 additions & 19 deletions crates/vm/src/builtins/type.rs
Original file line number Diff line number Diff line change
Expand Up @@ -605,10 +605,10 @@ impl PyType {
static_f: impl FnOnce(&'static str) -> R,
heap_f: impl FnOnce(&'a HeapTypeExt) -> R,
) -> R {
if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
static_f(self.slots.name)
if let Some(ref ext) = self.heaptype_ext {
heap_f(ext)
} else {
heap_f(self.heaptype_ext.as_ref().unwrap())
static_f(self.slots.name)
}
}

Expand Down Expand Up @@ -849,13 +849,7 @@ impl PyType {

#[pygetset(setter)]
fn set___qualname__(&self, value: PySetterValue, vm: &VirtualMachine) -> PyResult<()> {
// TODO: we should replace heaptype flag check to immutable flag check
if !self.slots.flags.has_feature(PyTypeFlags::HEAPTYPE) {
return Err(vm.new_type_error(format!(
"cannot set '__qualname__' attribute of immutable type '{}'",
self.name()
)));
};
self.check_set_special_type_attr(identifier!(vm, __qualname__), vm)?;
let value = value.ok_or_else(|| {
vm.new_type_error(format!(
"cannot delete '__qualname__' attribute of immutable type '{}'",
Expand All @@ -865,10 +859,12 @@ impl PyType {

let str_value = downcast_qualname(value, vm)?;

let heap_type = self
.heaptype_ext
.as_ref()
.expect("HEAPTYPE should have heaptype_ext");
let heap_type = self.heaptype_ext.as_ref().ok_or_else(|| {
vm.new_type_error(format!(
"cannot set '__qualname__' attribute of immutable type '{}'",
self.name()
))
})?;

// Use std::mem::replace to swap the new value in and get the old value out,
// then drop the old value after releasing the lock
Expand Down Expand Up @@ -1160,10 +1156,17 @@ impl PyType {
}
name.ensure_valid_utf8(vm)?;

let heap_type = self.heaptype_ext.as_ref().ok_or_else(|| {
vm.new_type_error(format!(
"cannot set '__name__' attribute of immutable type '{}'",
self.slot_name()
))
})?;

// Use std::mem::replace to swap the new value in and get the old value out,
// then drop the old value after releasing the lock (similar to CPython's Py_SETREF)
// then drop the old value after releasing the lock
let _old_name = {
let mut name_guard = self.heaptype_ext.as_ref().unwrap().name.write();
let mut name_guard = heap_type.name.write();
core::mem::replace(&mut *name_guard, name)
};
// old_name is dropped here, outside the lock scope
Expand Down Expand Up @@ -2129,9 +2132,10 @@ fn linearise_mro(mut bases: Vec<Vec<PyTypeRef>>) -> Result<Vec<PyTypeRef>, Strin
// We start at index 1 to skip direct bases.
// This will not catch duplicate bases, but such a thing is already tested for.
if later_mro[1..].iter().any(|cls| cls.is(base)) {
return Err(
"Unable to find mro order which keeps local precedence ordering".to_owned(),
);
return Err(format!(
"Cannot create a consistent method resolution order (MRO) for bases {}",
bases.iter().map(|x| x.first().unwrap()).format(", ")
));
}
}
}
Expand Down
Loading
Loading