mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Py 3.14 changes fix (#6755)
* marshal v5 * conditional blclk * fix jit don't use lossy string * add varname * symboltable takes responsibility of __debug__
This commit is contained in:
1 parent
892116c009
commit
6df3753229
9 files changed
+197
-89
No files matched your search
Generated
+1
@@ -3104,6 +3104,7 @@ dependencies = [
|
||||
"num-traits",
|
||||
"rustpython-compiler-core",
|
||||
"rustpython-derive",
|
||||
"rustpython-wtf8",
|
||||
"thiserror 2.0.17",
|
||||
]
|
||||
|
||||
|
||||
-3
@@ -459,9 +459,6 @@ class AnnotationsFutureTestCase(unittest.TestCase):
|
||||
self.assertAnnotationEqual("('inf', 1e1000, 'infxxx', 1e1000j)", expected=f"('inf', {inf}, 'infxxx', {infj})")
|
||||
self.assertAnnotationEqual("(1e1000, (1e1000j,))", expected=f"({inf}, ({infj},))")
|
||||
|
||||
# TODO: RUSTPYTHON
|
||||
# AssertionError: SyntaxError not raised
|
||||
@unittest.expectedFailure
|
||||
def test_annotation_with_complex_target(self):
|
||||
with self.assertRaises(SyntaxError):
|
||||
exec(
|
||||
|
||||
@@ -110,14 +110,6 @@ enum NameUsage {
|
||||
Store,
|
||||
Delete,
|
||||
}
|
||||
|
||||
fn is_forbidden_name(name: &str) -> bool {
|
||||
// See https://docs.python.org/3/library/constants.html#built-in-constants
|
||||
const BUILTIN_CONSTANTS: &[&str] = &["__debug__"];
|
||||
|
||||
BUILTIN_CONSTANTS.contains(&name)
|
||||
}
|
||||
|
||||
/// Main structure holding the state of compilation.
|
||||
struct Compiler {
|
||||
code_stack: Vec<ir::CodeInfo>,
|
||||
@@ -130,10 +122,6 @@ struct Compiler {
|
||||
ctx: CompileContext,
|
||||
opts: CompileOpts,
|
||||
in_annotation: bool,
|
||||
// PEP 649: Track if we're inside a conditional block (if/for/while/etc.)
|
||||
in_conditional_block: bool,
|
||||
// PEP 649: Next index for conditional annotation tracking
|
||||
next_conditional_annotation_index: u32,
|
||||
}
|
||||
|
||||
enum DoneWithFuture {
|
||||
@@ -424,6 +412,8 @@ impl Compiler {
|
||||
in_inlined_comp: false,
|
||||
fblock: Vec::with_capacity(MAXBLOCKS),
|
||||
symbol_table_index: 0, // Module is always the first symbol table
|
||||
in_conditional_block: 0,
|
||||
next_conditional_annotation_index: 0,
|
||||
};
|
||||
Self {
|
||||
code_stack: vec![module_code],
|
||||
@@ -441,8 +431,6 @@ impl Compiler {
|
||||
},
|
||||
opts,
|
||||
in_annotation: false,
|
||||
in_conditional_block: false,
|
||||
next_conditional_annotation_index: 0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1045,6 +1033,8 @@ impl Compiler {
|
||||
in_inlined_comp: false,
|
||||
fblock: Vec::with_capacity(MAXBLOCKS),
|
||||
symbol_table_index: key,
|
||||
in_conditional_block: 0,
|
||||
next_conditional_annotation_index: 0,
|
||||
};
|
||||
|
||||
// Push the old compiler unit on the stack (like PyCapsule)
|
||||
@@ -1525,11 +1515,7 @@ impl Compiler {
|
||||
self._name_inner(name, |i| &mut i.metadata.names)
|
||||
}
|
||||
fn varname(&mut self, name: &str) -> CompileResult<bytecode::NameIdx> {
|
||||
if Self::is_forbidden_arg_name(name) {
|
||||
return Err(self.error(CodegenErrorType::SyntaxError(format!(
|
||||
"cannot assign to {name}",
|
||||
))));
|
||||
}
|
||||
// Note: __debug__ checks are now handled in symboltable phase
|
||||
Ok(self._name_inner(name, |i| &mut i.metadata.varnames))
|
||||
}
|
||||
fn _name_inner(
|
||||
@@ -1814,15 +1800,6 @@ impl Compiler {
|
||||
symboltable::mangle_name(private, name)
|
||||
}
|
||||
|
||||
fn check_forbidden_name(&mut self, name: &str, usage: NameUsage) -> CompileResult<()> {
|
||||
let msg = match usage {
|
||||
NameUsage::Store if is_forbidden_name(name) => "cannot assign to",
|
||||
NameUsage::Delete if is_forbidden_name(name) => "cannot delete",
|
||||
_ => return Ok(()),
|
||||
};
|
||||
Err(self.error(CodegenErrorType::SyntaxError(format!("{msg} {name}"))))
|
||||
}
|
||||
|
||||
// = compiler_nameop
|
||||
fn compile_name(&mut self, name: &str, usage: NameUsage) -> CompileResult<()> {
|
||||
enum NameOp {
|
||||
@@ -1834,7 +1811,6 @@ impl Compiler {
|
||||
}
|
||||
|
||||
let name = self.mangle(name);
|
||||
self.check_forbidden_name(&name, usage)?;
|
||||
|
||||
// Special handling for __debug__
|
||||
if NameUsage::Load == usage && name == "__debug__" {
|
||||
@@ -2135,6 +2111,7 @@ impl Compiler {
|
||||
elif_else_clauses,
|
||||
..
|
||||
}) => {
|
||||
self.enter_conditional_block();
|
||||
match elif_else_clauses.as_slice() {
|
||||
// Only if
|
||||
[] => {
|
||||
@@ -2182,6 +2159,7 @@ impl Compiler {
|
||||
self.switch_to_block(after_block);
|
||||
}
|
||||
}
|
||||
self.leave_conditional_block();
|
||||
}
|
||||
Stmt::While(StmtWhile {
|
||||
test, body, orelse, ..
|
||||
@@ -2228,11 +2206,13 @@ impl Compiler {
|
||||
is_star,
|
||||
..
|
||||
}) => {
|
||||
self.enter_conditional_block();
|
||||
if *is_star {
|
||||
self.compile_try_star_except(body, handlers, orelse, finalbody)?
|
||||
} else {
|
||||
self.compile_try_statement(body, handlers, orelse, finalbody)?
|
||||
}
|
||||
self.leave_conditional_block();
|
||||
}
|
||||
Stmt::FunctionDef(StmtFunctionDef {
|
||||
name,
|
||||
@@ -2432,7 +2412,6 @@ impl Compiler {
|
||||
match &expression {
|
||||
Expr::Name(ExprName { id, .. }) => self.compile_name(id.as_str(), NameUsage::Delete)?,
|
||||
Expr::Attribute(ExprAttribute { value, attr, .. }) => {
|
||||
self.check_forbidden_name(attr.as_str(), NameUsage::Delete)?;
|
||||
self.compile_expression(value)?;
|
||||
let idx = self.name(attr.as_str());
|
||||
emit!(self, Instruction::DeleteAttr { idx });
|
||||
@@ -3451,10 +3430,6 @@ impl Compiler {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_forbidden_arg_name(name: &str) -> bool {
|
||||
is_forbidden_name(name)
|
||||
}
|
||||
|
||||
/// Compile default arguments
|
||||
// = compiler_default_arguments
|
||||
fn compile_default_arguments(
|
||||
@@ -4485,6 +4460,8 @@ impl Compiler {
|
||||
}
|
||||
|
||||
fn compile_while(&mut self, test: &Expr, body: &[Stmt], orelse: &[Stmt]) -> CompileResult<()> {
|
||||
self.enter_conditional_block();
|
||||
|
||||
let while_block = self.new_block();
|
||||
let else_block = self.new_block();
|
||||
let after_block = self.new_block();
|
||||
@@ -4513,6 +4490,8 @@ impl Compiler {
|
||||
// Note: PopBlock is no longer emitted for loops
|
||||
self.compile_statements(orelse)?;
|
||||
self.switch_to_block(after_block);
|
||||
|
||||
self.leave_conditional_block();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4522,6 +4501,8 @@ impl Compiler {
|
||||
body: &[Stmt],
|
||||
is_async: bool,
|
||||
) -> CompileResult<()> {
|
||||
self.enter_conditional_block();
|
||||
|
||||
// Python 3.12+ style with statement:
|
||||
//
|
||||
// BEFORE_WITH # TOS: ctx_mgr -> [__exit__, __enter__ result]
|
||||
@@ -4734,6 +4715,7 @@ impl Compiler {
|
||||
// ===== After block =====
|
||||
self.switch_to_block(after_block);
|
||||
|
||||
self.leave_conditional_block();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -4745,6 +4727,8 @@ impl Compiler {
|
||||
orelse: &[Stmt],
|
||||
is_async: bool,
|
||||
) -> CompileResult<()> {
|
||||
self.enter_conditional_block();
|
||||
|
||||
// Start loop
|
||||
let for_block = self.new_block();
|
||||
let else_block = self.new_block();
|
||||
@@ -4812,6 +4796,7 @@ impl Compiler {
|
||||
|
||||
self.switch_to_block(after_block);
|
||||
|
||||
self.leave_conditional_block();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5784,8 +5769,10 @@ impl Compiler {
|
||||
}
|
||||
|
||||
fn compile_match(&mut self, subject: &Expr, cases: &[MatchCase]) -> CompileResult<()> {
|
||||
self.enter_conditional_block();
|
||||
let mut pattern_context = PatternContext::new();
|
||||
self.compile_match_inner(subject, cases, &mut pattern_context)?;
|
||||
self.leave_conditional_block();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -5946,16 +5933,22 @@ impl Compiler {
|
||||
} else {
|
||||
// PEP 649: Handle conditional annotations
|
||||
if self.current_symbol_table().has_conditional_annotations {
|
||||
// Allocate an index for every annotation when has_conditional_annotations
|
||||
// This keeps indices aligned with compile_module_annotate's enumeration
|
||||
let code_info = self.current_code_info();
|
||||
let annotation_index = code_info.next_conditional_annotation_index;
|
||||
code_info.next_conditional_annotation_index += 1;
|
||||
|
||||
// Determine if this annotation is conditional
|
||||
let is_module = self.current_symbol_table().typ == CompilerScope::Module;
|
||||
let is_conditional = is_module || self.in_conditional_block;
|
||||
// Module and Class scopes both need all annotations tracked
|
||||
let scope_type = self.current_symbol_table().typ;
|
||||
let in_conditional_block = self.current_code_info().in_conditional_block > 0;
|
||||
let is_conditional =
|
||||
matches!(scope_type, CompilerScope::Module | CompilerScope::Class)
|
||||
|| in_conditional_block;
|
||||
|
||||
// Only add to __conditional_annotations__ set if actually conditional
|
||||
if is_conditional {
|
||||
// Get the current annotation index and increment
|
||||
let annotation_index = self.next_conditional_annotation_index;
|
||||
self.next_conditional_annotation_index += 1;
|
||||
|
||||
// Add index to __conditional_annotations__ set
|
||||
let cond_annotations_name = self.name("__conditional_annotations__");
|
||||
emit!(self, Instruction::LoadName(cond_annotations_name));
|
||||
self.emit_load_const(ConstantData::Integer {
|
||||
@@ -5980,7 +5973,6 @@ impl Compiler {
|
||||
self.compile_subscript(value, slice, *ctx)?;
|
||||
}
|
||||
Expr::Attribute(ExprAttribute { value, attr, .. }) => {
|
||||
self.check_forbidden_name(attr.as_str(), NameUsage::Store)?;
|
||||
self.compile_expression(value)?;
|
||||
let idx = self.name(attr.as_str());
|
||||
emit!(self, Instruction::StoreAttr { idx });
|
||||
@@ -6075,7 +6067,6 @@ impl Compiler {
|
||||
}
|
||||
Expr::Attribute(ExprAttribute { value, attr, .. }) => {
|
||||
let attr = attr.as_str();
|
||||
self.check_forbidden_name(attr, NameUsage::Store)?;
|
||||
self.compile_expression(value)?;
|
||||
emit!(self, Instruction::Copy { index: 1_u32 });
|
||||
let idx = self.name(attr);
|
||||
@@ -6903,12 +6894,6 @@ impl Compiler {
|
||||
let (size, unpack) = self.gather_elements(additional_positional, &arguments.args)?;
|
||||
let has_double_star = arguments.keywords.iter().any(|k| k.arg.is_none());
|
||||
|
||||
for keyword in &arguments.keywords {
|
||||
if let Some(name) = &keyword.arg {
|
||||
self.check_forbidden_name(name.as_str(), NameUsage::Store)?;
|
||||
}
|
||||
}
|
||||
|
||||
if unpack || has_double_star {
|
||||
// Create a tuple with positional args:
|
||||
if unpack {
|
||||
@@ -7511,6 +7496,19 @@ impl Compiler {
|
||||
self.code_stack.last_mut().expect("no code on stack")
|
||||
}
|
||||
|
||||
/// Enter a conditional block (if/for/while/match/try/with)
|
||||
/// PEP 649: Track conditional annotation context
|
||||
fn enter_conditional_block(&mut self) {
|
||||
self.current_code_info().in_conditional_block += 1;
|
||||
}
|
||||
|
||||
/// Leave a conditional block
|
||||
fn leave_conditional_block(&mut self) {
|
||||
let code_info = self.current_code_info();
|
||||
debug_assert!(code_info.in_conditional_block > 0);
|
||||
code_info.in_conditional_block -= 1;
|
||||
}
|
||||
|
||||
/// Compile break or continue statement with proper fblock cleanup.
|
||||
/// compiler_break, compiler_continue
|
||||
/// This handles unwinding through With blocks and exception handlers.
|
||||
|
||||
@@ -145,6 +145,14 @@ pub struct CodeInfo {
|
||||
|
||||
// Reference to the symbol table for this scope
|
||||
pub symbol_table_index: usize,
|
||||
|
||||
// PEP 649: Track nesting depth inside conditional blocks (if/for/while/etc.)
|
||||
// u_in_conditional_block
|
||||
pub in_conditional_block: u32,
|
||||
|
||||
// PEP 649: Next index for conditional annotation tracking
|
||||
// u_next_conditional_annotation_index
|
||||
pub next_conditional_annotation_index: u32,
|
||||
}
|
||||
|
||||
impl CodeInfo {
|
||||
@@ -171,6 +179,8 @@ impl CodeInfo {
|
||||
in_inlined_comp: _,
|
||||
fblock: _,
|
||||
symbol_table_index: _,
|
||||
in_conditional_block: _,
|
||||
next_conditional_annotation_index: _,
|
||||
} = self;
|
||||
|
||||
let CodeUnitMetadata {
|
||||
|
||||
@@ -780,6 +780,8 @@ struct SymbolTableBuilder {
|
||||
source_file: SourceFile,
|
||||
// Current scope's varnames being collected (temporary storage)
|
||||
current_varnames: Vec<String>,
|
||||
// Stack to preserve parent varnames when entering nested scopes
|
||||
varnames_stack: Vec<Vec<String>>,
|
||||
// Track if we're inside an iterable definition expression (for nested comprehensions)
|
||||
in_iter_def_exp: bool,
|
||||
// Track if we're inside an annotation (yield/await/named expr not allowed)
|
||||
@@ -815,6 +817,7 @@ impl SymbolTableBuilder {
|
||||
future_annotations: false,
|
||||
source_file,
|
||||
current_varnames: Vec::new(),
|
||||
varnames_stack: Vec::new(),
|
||||
in_iter_def_exp: false,
|
||||
in_annotation: false,
|
||||
in_type_alias: false,
|
||||
@@ -845,8 +848,9 @@ impl SymbolTableBuilder {
|
||||
.unwrap_or(false);
|
||||
let table = SymbolTable::new(name.to_owned(), typ, line_number, is_nested);
|
||||
self.tables.push(table);
|
||||
// Clear current_varnames for the new scope
|
||||
self.current_varnames.clear();
|
||||
// Save parent's varnames and start fresh for the new scope
|
||||
self.varnames_stack
|
||||
.push(core::mem::take(&mut self.current_varnames));
|
||||
}
|
||||
|
||||
fn enter_type_param_block(&mut self, name: &str, line_number: u32) -> SymbolTableResult {
|
||||
@@ -880,6 +884,8 @@ impl SymbolTableBuilder {
|
||||
// Save the collected varnames to the symbol table
|
||||
table.varnames = core::mem::take(&mut self.current_varnames);
|
||||
self.tables.last_mut().unwrap().sub_tables.push(table);
|
||||
// Restore parent's varnames
|
||||
self.current_varnames = self.varnames_stack.pop().unwrap_or_default();
|
||||
}
|
||||
|
||||
/// Enter annotation scope (PEP 649)
|
||||
@@ -907,7 +913,10 @@ impl SymbolTableBuilder {
|
||||
// Take the annotation block and push to stack for processing
|
||||
let annotation_table = current.annotation_block.take().unwrap();
|
||||
self.tables.push(*annotation_table);
|
||||
self.current_varnames.clear();
|
||||
// Save parent's varnames and seed with existing annotation varnames (e.g., "format")
|
||||
self.varnames_stack
|
||||
.push(core::mem::take(&mut self.current_varnames));
|
||||
self.current_varnames = self.tables.last().unwrap().varnames.clone();
|
||||
|
||||
if can_see_class_scope && !self.future_annotations {
|
||||
self.add_classdict_freevar();
|
||||
@@ -927,6 +936,8 @@ impl SymbolTableBuilder {
|
||||
// Store back to parent's annotation_block (not sub_tables)
|
||||
let parent = self.tables.last_mut().unwrap();
|
||||
parent.annotation_block = Some(Box::new(table));
|
||||
// Restore parent's varnames
|
||||
self.current_varnames = self.varnames_stack.pop().unwrap_or_default();
|
||||
}
|
||||
|
||||
fn add_classdict_freevar(&mut self) {
|
||||
@@ -977,6 +988,12 @@ impl SymbolTableBuilder {
|
||||
}
|
||||
|
||||
fn scan_parameter(&mut self, parameter: &Parameter) -> SymbolTableResult {
|
||||
self.check_name(
|
||||
parameter.name.as_str(),
|
||||
ExpressionContext::Store,
|
||||
parameter.name.range,
|
||||
)?;
|
||||
|
||||
let usage = if parameter.annotation.is_some() {
|
||||
SymbolUsage::AnnotationParameter
|
||||
} else {
|
||||
@@ -1239,6 +1256,7 @@ impl SymbolTableBuilder {
|
||||
for name in names {
|
||||
if let Some(alias) = &name.asname {
|
||||
// `import my_module as my_alias`
|
||||
self.check_name(alias.as_str(), ExpressionContext::Store, alias.range)?;
|
||||
self.register_ident(alias, SymbolUsage::Imported)?;
|
||||
} else if name.name.as_str() == "*" {
|
||||
// Star imports are only allowed at module level
|
||||
@@ -1253,12 +1271,10 @@ impl SymbolTableBuilder {
|
||||
}
|
||||
// Don't register star imports as symbols
|
||||
} else {
|
||||
// `import module`
|
||||
self.register_name(
|
||||
name.name.split('.').next().unwrap(),
|
||||
SymbolUsage::Imported,
|
||||
name.name.range,
|
||||
)?;
|
||||
// `import module` or `from x import name`
|
||||
let imported_name = name.name.split('.').next().unwrap();
|
||||
self.check_name(imported_name, ExpressionContext::Store, name.name.range)?;
|
||||
self.register_name(imported_name, SymbolUsage::Imported, name.name.range)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1295,7 +1311,11 @@ impl SymbolTableBuilder {
|
||||
// https://github.com/python/cpython/blob/main/Python/symtable.c#L1233
|
||||
match &**target {
|
||||
Expr::Name(ast::ExprName { id, .. }) if *simple => {
|
||||
self.register_name(id.as_str(), SymbolUsage::AnnotationAssigned, *range)?;
|
||||
let id_str = id.as_str();
|
||||
|
||||
self.check_name(id_str, ExpressionContext::Store, *range)?;
|
||||
|
||||
self.register_name(id_str, SymbolUsage::AnnotationAssigned, *range)?;
|
||||
// PEP 649: Register annotate function in module/class scope
|
||||
let current_scope = self.tables.last().map(|t| t.typ);
|
||||
match current_scope {
|
||||
@@ -1512,8 +1532,9 @@ impl SymbolTableBuilder {
|
||||
self.scan_expression(slice, ExpressionContext::Load)?;
|
||||
}
|
||||
Expr::Attribute(ExprAttribute {
|
||||
value, range: _, ..
|
||||
value, attr, range, ..
|
||||
}) => {
|
||||
self.check_name(attr.as_str(), context, *range)?;
|
||||
self.scan_expression(value, ExpressionContext::Load)?;
|
||||
}
|
||||
Expr::Dict(ExprDict {
|
||||
@@ -1657,11 +1678,17 @@ impl SymbolTableBuilder {
|
||||
|
||||
self.scan_expressions(&arguments.args, ExpressionContext::Load)?;
|
||||
for keyword in &arguments.keywords {
|
||||
if let Some(arg) = &keyword.arg {
|
||||
self.check_name(arg.as_str(), ExpressionContext::Store, keyword.range)?;
|
||||
}
|
||||
self.scan_expression(&keyword.value, ExpressionContext::Load)?;
|
||||
}
|
||||
}
|
||||
Expr::Name(ExprName { id, range, .. }) => {
|
||||
let id = id.as_str();
|
||||
|
||||
self.check_name(id, context, *range)?;
|
||||
|
||||
// Determine the contextual usage of this symbol:
|
||||
match context {
|
||||
ExpressionContext::Delete => {
|
||||
@@ -1785,6 +1812,7 @@ impl SymbolTableBuilder {
|
||||
// propagate inner names.
|
||||
if let Expr::Name(ExprName { id, .. }) = &**target {
|
||||
let id = id.as_str();
|
||||
self.check_name(id, ExpressionContext::Store, *range)?;
|
||||
let table = self.tables.last().unwrap();
|
||||
if table.typ == CompilerScope::Comprehension {
|
||||
self.register_name(
|
||||
@@ -2150,6 +2178,37 @@ impl SymbolTableBuilder {
|
||||
self.register_name(ident.as_str(), role, ident.range)
|
||||
}
|
||||
|
||||
fn check_name(
|
||||
&self,
|
||||
name: &str,
|
||||
context: ExpressionContext,
|
||||
range: TextRange,
|
||||
) -> SymbolTableResult {
|
||||
if name == "__debug__" {
|
||||
let location = Some(
|
||||
self.source_file
|
||||
.to_source_code()
|
||||
.source_location(range.start(), PositionEncoding::Utf8),
|
||||
);
|
||||
match context {
|
||||
ExpressionContext::Store | ExpressionContext::Iter => {
|
||||
return Err(SymbolTableError {
|
||||
error: "cannot assign to __debug__".to_owned(),
|
||||
location,
|
||||
});
|
||||
}
|
||||
ExpressionContext::Delete => {
|
||||
return Err(SymbolTableError {
|
||||
error: "cannot delete __debug__".to_owned(),
|
||||
location,
|
||||
});
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn register_name(
|
||||
&mut self,
|
||||
name: &str,
|
||||
@@ -2162,18 +2221,7 @@ impl SymbolTableBuilder {
|
||||
.source_location(range.start(), PositionEncoding::Utf8);
|
||||
let location = Some(location);
|
||||
|
||||
// Check for forbidden names like __debug__
|
||||
if name == "__debug__"
|
||||
&& matches!(
|
||||
role,
|
||||
SymbolUsage::Parameter | SymbolUsage::AnnotationParameter | SymbolUsage::Assigned
|
||||
)
|
||||
{
|
||||
return Err(SymbolTableError {
|
||||
error: "cannot assign to __debug__".to_owned(),
|
||||
location,
|
||||
});
|
||||
}
|
||||
// Note: __debug__ checks are handled by check_name function, so no check needed here.
|
||||
|
||||
let scope_depth = self.tables.len();
|
||||
let table = self.tables.last_mut().unwrap();
|
||||
|
||||
@@ -4,7 +4,7 @@ use malachite_bigint::{BigInt, Sign};
|
||||
use num_complex::Complex64;
|
||||
use rustpython_wtf8::Wtf8;
|
||||
|
||||
pub const FORMAT_VERSION: u32 = 4;
|
||||
pub const FORMAT_VERSION: u32 = 5;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum MarshalError {
|
||||
@@ -65,6 +65,7 @@ enum Type {
|
||||
// Unknown = b'?',
|
||||
Set = b'<',
|
||||
FrozenSet = b'>',
|
||||
Slice = b':', // Added in version 5
|
||||
Ascii = b'a',
|
||||
// AsciiInterned = b'A',
|
||||
// SmallTuple = b')',
|
||||
@@ -101,6 +102,7 @@ impl TryFrom<u8> for Type {
|
||||
// b'?' => Unknown,
|
||||
b'<' => Set,
|
||||
b'>' => FrozenSet,
|
||||
b':' => Slice,
|
||||
b'a' => Ascii,
|
||||
// b'A' => AsciiInterned,
|
||||
// b')' => SmallTuple,
|
||||
@@ -466,6 +468,12 @@ pub fn deserialize_value<R: Read, Bag: MarshalBag>(rdr: &mut R, bag: Bag) -> Res
|
||||
bag.make_bytes(value)
|
||||
}
|
||||
Type::Code => bag.make_code(deserialize_code(rdr, bag.constant_bag())?),
|
||||
Type::Slice => {
|
||||
// Slice constants are not yet supported in RustPython
|
||||
// This would require adding a Slice variant to ConstantData enum
|
||||
// For now, return an error if we encounter a slice in marshal data
|
||||
return Err(MarshalError::BadType);
|
||||
}
|
||||
};
|
||||
Ok(value)
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ cranelift-module = "0.127"
|
||||
|
||||
[dev-dependencies]
|
||||
rustpython-derive = { workspace = true }
|
||||
rustpython-wtf8 = { workspace = true }
|
||||
|
||||
approx = "0.5.1"
|
||||
|
||||
|
||||
+50
-11
@@ -3,19 +3,20 @@ use rustpython_compiler_core::bytecode::{
|
||||
CodeObject, ConstantData, Instruction, OpArg, OpArgState,
|
||||
};
|
||||
use rustpython_jit::{CompiledCode, JitType};
|
||||
use rustpython_wtf8::{Wtf8, Wtf8Buf};
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Function {
|
||||
code: Box<CodeObject>,
|
||||
annotations: HashMap<String, StackValue>,
|
||||
annotations: HashMap<Wtf8Buf, StackValue>,
|
||||
}
|
||||
|
||||
impl Function {
|
||||
pub fn compile(self) -> CompiledCode {
|
||||
let mut arg_types = Vec::new();
|
||||
for arg in self.code.arg_names().args {
|
||||
let arg_type = match self.annotations.get(arg) {
|
||||
let arg_type = match self.annotations.get(AsRef::<Wtf8>::as_ref(arg.as_str())) {
|
||||
Some(StackValue::String(annotation)) => match annotation.as_str() {
|
||||
"int" => JitType::Int,
|
||||
"float" => JitType::Float,
|
||||
@@ -27,7 +28,7 @@ impl Function {
|
||||
arg_types.push(arg_type);
|
||||
}
|
||||
|
||||
let ret_type = match self.annotations.get("return") {
|
||||
let ret_type = match self.annotations.get(AsRef::<Wtf8>::as_ref("return")) {
|
||||
Some(StackValue::String(annotation)) => match annotation.as_str() {
|
||||
"int" => Some(JitType::Int),
|
||||
"float" => Some(JitType::Float),
|
||||
@@ -45,7 +46,7 @@ impl Function {
|
||||
enum StackValue {
|
||||
String(String),
|
||||
None,
|
||||
Map(HashMap<String, StackValue>),
|
||||
Map(HashMap<Wtf8Buf, StackValue>),
|
||||
Code(Box<CodeObject>),
|
||||
Function(Function),
|
||||
}
|
||||
@@ -66,7 +67,7 @@ impl From<ConstantData> for StackValue {
|
||||
/// Extract annotations from an annotate function's bytecode.
|
||||
/// The annotate function uses BUILD_MAP with key-value pairs loaded before it.
|
||||
/// Keys are parameter names (from LOAD_CONST), values are type names (from LOAD_NAME/LOAD_GLOBAL).
|
||||
fn extract_annotations_from_annotate_code(code: &CodeObject) -> HashMap<String, StackValue> {
|
||||
fn extract_annotations_from_annotate_code(code: &CodeObject) -> HashMap<Wtf8Buf, StackValue> {
|
||||
let mut annotations = HashMap::new();
|
||||
let mut stack: Vec<(bool, usize)> = Vec::new(); // (is_const, index)
|
||||
let mut op_arg_state = OpArgState::default();
|
||||
@@ -91,17 +92,55 @@ fn extract_annotations_from_annotate_code(code: &CodeObject) -> HashMap<String,
|
||||
for chunk in pairs.chunks(2) {
|
||||
if chunk.len() == 2 {
|
||||
let (key_is_const, key_idx) = chunk[0];
|
||||
let (_val_is_const, val_idx) = chunk[1];
|
||||
let (val_is_const, val_idx) = chunk[1];
|
||||
|
||||
// Key should be a const string (parameter name)
|
||||
if key_is_const
|
||||
&& let ConstantData::Str { value } = &code.constants[key_idx]
|
||||
{
|
||||
let param_name = value.to_string_lossy().into_owned();
|
||||
// Value should be a name (type name)
|
||||
if let Some(type_name) = code.names.get(val_idx) {
|
||||
let param_name = value;
|
||||
// Value can be a name (type ref) or a const string (forward ref)
|
||||
let type_name = if val_is_const {
|
||||
match code.constants.get(val_idx) {
|
||||
Some(ConstantData::Str { value }) => {
|
||||
Some(value.as_str().map(|s| s.to_owned()).unwrap_or_else(
|
||||
|_| value.to_string_lossy().into_owned(),
|
||||
))
|
||||
}
|
||||
Some(other) => {
|
||||
eprintln!(
|
||||
"Warning: Malformed annotation for '{:?}': expected string constant at index {}, got {:?}",
|
||||
param_name, val_idx, other
|
||||
);
|
||||
None
|
||||
}
|
||||
None => {
|
||||
eprintln!(
|
||||
"Warning: Malformed annotation for '{:?}': constant index {} out of bounds (len={})",
|
||||
param_name,
|
||||
val_idx,
|
||||
code.constants.len()
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match code.names.get(val_idx) {
|
||||
Some(name) => Some(name.clone()),
|
||||
None => {
|
||||
eprintln!(
|
||||
"Warning: Malformed annotation for '{}': name index {} out of bounds (len={})",
|
||||
param_name,
|
||||
val_idx,
|
||||
code.names.len()
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
if let Some(type_name) = type_name {
|
||||
annotations
|
||||
.insert(param_name, StackValue::String(type_name.clone()));
|
||||
.insert(param_name.clone(), StackValue::String(type_name));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -183,7 +222,7 @@ impl StackMachine {
|
||||
for _ in 0..size.get(arg) {
|
||||
let value = self.stack.pop().unwrap();
|
||||
let name = if let Some(StackValue::String(name)) = self.stack.pop() {
|
||||
name
|
||||
Wtf8Buf::from(name)
|
||||
} else {
|
||||
unimplemented!("no string keys isn't yet supported in py_function!")
|
||||
};
|
||||
|
||||
@@ -21,6 +21,12 @@ raisesSyntaxError("", "__debug__ := 1")
|
||||
raisesSyntaxError("", "del __debug__")
|
||||
raisesSyntaxError("", "(a, __debug__, c) = (1, 2, 3)")
|
||||
raisesSyntaxError("", "(a, *__debug__, c) = (1, 2, 3)")
|
||||
raisesSyntaxError("", "__debug__ : int")
|
||||
raisesSyntaxError("", "__debug__ : int = 1")
|
||||
|
||||
# TODO:
|
||||
# raisesSyntaxError("", "__debug__ : int")
|
||||
# Import statements
|
||||
raisesSyntaxError("import sys as __debug__")
|
||||
raisesSyntaxError("from sys import path as __debug__")
|
||||
|
||||
# Comprehension iteration targets
|
||||
raisesSyntaxError("[x for __debug__ in range(5)]")
|
||||
Reference in new issue
Block a user