Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
Prev Previous commit
ZeroArg
  • Loading branch information
youknowone committed Jan 11, 2026
commit 158d15e2f3675160de2a36f68ec64354d102290e
58 changes: 41 additions & 17 deletions crates/codegen/src/compile.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,6 @@ enum SuperCallType<'a> {
self_arg: &'a Expr,
},
/// super() - implicit 0-argument form (uses __class__ cell)
/// TODO: Enable after fixing __class__ cell handling in nested classes
#[allow(dead_code)]
ZeroArg,
}

Expand Down Expand Up @@ -715,14 +713,20 @@ impl Compiler {
return None;
}

// 6. "super" must be GlobalImplicit (not redefined locally)
// 6. "super" must be GlobalImplicit (not redefined locally or at module level)
let table = self.current_symbol_table();
if let Some(symbol) = table.lookup("super") {
if symbol.scope != SymbolScope::GlobalImplicit {
return None;
}
} else {
// super not found in symbol table - assume it's a global builtin
if let Some(symbol) = table.lookup("super")
&& symbol.scope != SymbolScope::GlobalImplicit
{
return None;
}
// Also check top-level scope to detect module-level shadowing.
// Only block if super is actually *bound* at module level (not just used).
if let Some(top_table) = self.symbol_table_stack.first()
&& let Some(sym) = top_table.lookup("super")
&& sym.scope != SymbolScope::GlobalImplicit
{
return None;
}

// 7. Check argument pattern
Expand All @@ -743,10 +747,26 @@ impl Compiler {
}
0 => {
// 0-arg: super() - need __class__ cell and first parameter
// TODO: 0-arg super() optimization is disabled due to __class__ cell issues
// The __class__ cell handling in nested class definitions needs more work.
// For now, fall back to regular super() call.
None
// Enclosing function should have at least one positional argument
let info = self.code_stack.last()?;
if info.metadata.argcount == 0 && info.metadata.posonlyargcount == 0 {
return None;
}

// Check if __class__ is available as a cell/free variable
// The scope must be Free (from enclosing class) or have FREE_CLASS flag
if let Some(symbol) = table.lookup("__class__") {
if symbol.scope != SymbolScope::Free
&& !symbol.flags.contains(SymbolFlags::FREE_CLASS)
{
return None;
}
} else {
// __class__ not in symbol table, optimization not possible
return None;
}

Some(SuperCallType::ZeroArg)
}
_ => None, // 1 or 3+ args - not optimizable
}
Expand Down Expand Up @@ -782,7 +802,9 @@ impl Compiler {
};
self.emit_arg(idx, Instruction::LoadDeref);

// Load first parameter (typically 'self')
// Load first parameter (typically 'self').
// Safety: can_optimize_super_call() ensures argcount > 0, and
// parameters are always added to varnames first (see symboltable.rs).
let first_param = {
let info = self.code_stack.last().unwrap();
info.metadata.varnames.first().cloned()
Expand Down Expand Up @@ -3494,12 +3516,14 @@ impl Compiler {
/// Determines if a variable should be CELL or FREE type
// = get_ref_type
fn get_ref_type(&self, name: &str) -> Result<SymbolScope, CodegenErrorType> {
let table = self.symbol_table_stack.last().unwrap();

// Special handling for __class__ and __classdict__ in class scope
if self.ctx.in_class && (name == "__class__" || name == "__classdict__") {
// This should only apply when we're actually IN a class body,
// not when we're in a method nested inside a class.
if table.typ == CompilerScope::Class && (name == "__class__" || name == "__classdict__") {
return Ok(SymbolScope::Cell);
}

let table = self.symbol_table_stack.last().unwrap();
match table.lookup(name) {
Some(symbol) => match symbol.scope {
SymbolScope::Cell => Ok(SymbolScope::Cell),
Expand Down
11 changes: 9 additions & 2 deletions crates/compiler-core/src/bytecode/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use crate::{
Arg, BinaryOperator, BorrowedConstant, BuildSliceArgCount, ComparisonOperator, Constant,
ConvertValueOparg, InstrDisplayContext, IntrinsicFunction1, IntrinsicFunction2, Invert,
Label, MakeFunctionFlags, NameIdx, OpArg, RaiseKind, UnpackExArgs, decode_load_attr_arg,
decode_load_super_attr_arg,
},
marshal::MarshalError,
};
Expand Down Expand Up @@ -244,7 +245,10 @@ pub enum Instruction {
arg: Arg<u32>,
} = 149,
// ===== LOAD_SUPER_* Pseudo Opcodes (136-138) =====
// These are converted to LoadSuperAttr during bytecode finalization
// These are converted to LoadSuperAttr during bytecode finalization.
// "Zero" variants are for 0-arg super() calls (has_class=false).
// Non-"Zero" variants are for 2-arg super(cls, self) calls (has_class=true).
/// 2-arg super(cls, self).method() - has_class=true, load_method=true
LoadSuperMethod {
idx: Arg<NameIdx>,
} = 136, // CPython uses pseudo-op 260
Expand Down Expand Up @@ -349,6 +353,10 @@ impl TryFrom<u8> for Instruction {
u8::from(Self::JumpIfNotExcMatch(Arg::marker())),
u8::from(Self::SetExcInfo),
u8::from(Self::Subscript),
// LOAD_SUPER_* pseudo opcodes (136-138)
u8::from(Self::LoadSuperMethod { idx: Arg::marker() }),
u8::from(Self::LoadZeroSuperAttr { idx: Arg::marker() }),
u8::from(Self::LoadZeroSuperMethod { idx: Arg::marker() }),
];

// Pseudo opcodes (252-255)
Expand Down Expand Up @@ -632,7 +640,6 @@ impl Instruction {
Self::LoadFromDictOrGlobals(_) => 0,
Self::SetUpdate { .. } => 0,
Self::MakeCell(_) => 0,
Self::LoadSuperAttr { .. } => 0,
Self::StoreFastStoreFast { .. } => 0,
Self::PopJumpIfNone { .. } => 0,
Self::PopJumpIfNotNone { .. } => 0,
Expand Down
3 changes: 2 additions & 1 deletion crates/vm/src/frame.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2535,7 +2535,8 @@ impl ExecutingFrame<'_> {
let (name_idx, load_method, has_class) = bytecode::decode_load_super_attr_arg(oparg);
let attr_name = self.code.names[name_idx as usize];

// Pop [super, class, self] from stack
// Stack layout (bottom to top): [super, class, self]
// Pop in LIFO order: self, class, super
let self_obj = self.pop_value();
let class = self.pop_value();
let global_super = self.pop_value();
Expand Down
Loading