@@ -318,6 +318,34 @@ partial def callValueDispatch (callee : Value) (args : List Value)
318318 let fd ← heapGetFunc ref
319319 callUserFunc fd args kwargs
320320 | .boundMethod receiver method => callBoundMethod receiver method args
321+ | .classObj cref => do
322+ -- Instance creation: allocate instance, call __init__
323+ let cd ← heapGetClassData cref
324+ let inst ← allocInstance { cls := callee, attrs := {} }
325+ -- Look up __init__ in MRO, tracking which class defines it
326+ let mut initFound : Option (Value × Value) := none -- (function, defining class)
327+ for mroEntry in cd.mro do
328+ match mroEntry with
329+ | .classObj mref => do
330+ let mcd ← heapGetClassData mref
331+ if let some fn := mcd.ns["__init__" ]? then
332+ initFound := some (fn, mroEntry)
333+ break
334+ | _ => pure ()
335+ -- Call __init__ if found, injecting __class__ for super() support
336+ match initFound with
337+ | some (fn, definingCls) =>
338+ match fn with
339+ | .function fref => do
340+ let fd ← heapGetFunc fref
341+ let scope ← bindFuncParams fd.params (inst :: args) kwargs fd.defaults fd.kwDefaults
342+ let scopeWithClass := scope.insert "__class__" definingCls
343+ let _ ← callRegularFunc fd scopeWithClass
344+ | _ => let _ ← callValueDispatch fn (inst :: args) kwargs
345+ | none =>
346+ if !args.isEmpty then
347+ throwTypeError s! "{ cd.name} () takes no arguments"
348+ return inst
321349 | .exception _ _ =>
322350 -- Exception objects are not callable, but exception classes are handled as builtins
323351 throwTypeError s! "'{ typeName callee} ' object is not callable"
@@ -523,6 +551,39 @@ partial def getAttributeValue (obj : Value) (attr : String) : InterpM Value := d
523551 if attr == "__next__" || attr == "__iter__" || attr == "close" then
524552 return .boundMethod obj attr
525553 else throwAttributeError s! "'generator' object has no attribute '{ attr} '"
554+ | .classObj cref => do
555+ let cd ← heapGetClassData cref
556+ -- Look up in own namespace first, then walk MRO
557+ for mroEntry in cd.mro do
558+ match mroEntry with
559+ | .classObj mref => do
560+ let mcd ← heapGetClassData mref
561+ if let some v := mcd.ns[attr]? then return v
562+ | _ => pure ()
563+ throwAttributeError s! "type object '{ cd.name} ' has no attribute '{ attr} '"
564+ | .instance iref => do
565+ let id_ ← heapGetInstanceData iref
566+ -- Check instance attributes first
567+ if let some v := id_.attrs[attr]? then return v
568+ -- Then walk class MRO
569+ match id_.cls with
570+ | .classObj cref => do
571+ let cd ← heapGetClassData cref
572+ for mroEntry in cd.mro do
573+ match mroEntry with
574+ | .classObj mref => do
575+ let mcd ← heapGetClassData mref
576+ if let some v := mcd.ns[attr]? then
577+ -- If it's a function, return as bound method
578+ match v with
579+ | .function _ => return .boundMethod obj attr
580+ | _ => return v
581+ | _ => pure ()
582+ throwAttributeError s! "'{ cd.name} ' object has no attribute '{ attr} '"
583+ | _ => throwAttributeError s! "instance has no attribute '{ attr} '"
584+ | .superObj _startAfterCls _inst =>
585+ -- super().attr — resolved in callBoundMethod
586+ return .boundMethod obj attr
526587 | _ => throwAttributeError s! "'{ typeName obj} ' object has no attribute '{ attr} '"
527588
528589-- Subscript access
@@ -625,6 +686,12 @@ partial def assignToTarget (target : Expr) (value : Value) : InterpM Unit := do
625686 break
626687 if !found then newPairs := newPairs.push (key, value)
627688 heapSetDict ref newPairs
689+ | .instance iref => do
690+ let id_ ← heapGetInstanceData iref
691+ heapSetInstanceData iref { id_ with attrs := id_.attrs.insert attr value }
692+ | .classObj cref => do
693+ let cd ← heapGetClassData cref
694+ heapSetClassData cref { cd with ns := cd.ns.insert attr value }
628695 | _ => throwAttributeError s! "'{ typeName objVal} ' object attribute '{ attr} ' is read-only"
629696 | .starred inner _ => assignToTarget inner value
630697 | _ => throwRuntimeError (.runtimeError "invalid assignment target" )
@@ -758,7 +825,9 @@ partial def execStmt (s : Stmt) : InterpM Unit := do
758825 for n in names do declareNonlocal n
759826
760827 | .classDef cd => do
761- -- Minimal: run body in a scope, store as dict
828+ -- Evaluate base classes
829+ let bases ← cd.bases.mapM evalExpr
830+ -- Execute class body in a new scope
762831 pushScope {}
763832 try execStmts cd.body
764833 catch
@@ -769,11 +838,30 @@ partial def execStmt (s : Stmt) : InterpM Unit := do
769838 | s :: _ => s
770839 | [] => ({} : Scope)
771840 popScope
772- let mut entries : Array (Value × Value) := #[]
773- entries := entries.push (.str "__name__" , .str cd.name)
841+ -- Build namespace from class body scope
842+ let mut ns : Std.HashMap String Value := {}
774843 for (k, v) in classScope do
775- entries := entries.push (.str k, v)
776- let classVal ← allocDict entries
844+ ns := ns.insert k v
845+ -- Compute linear MRO: [self, ...bases' MROs flattened]
846+ let selfPlaceholder := Value.none -- placeholder, replaced after allocation
847+ let mut mro : Array Value := #[selfPlaceholder]
848+ for base in bases do
849+ match base with
850+ | .classObj bref => do
851+ let bcd ← heapGetClassData bref
852+ for entry in bcd.mro do
853+ -- Avoid duplicates
854+ let isDup := mro.any fun e => Value.beq e entry
855+ if !isDup then mro := mro.push entry
856+ | _ => pure () -- ignore non-class bases for now
857+ -- Allocate the class
858+ let classVal ← allocClassObj { name := cd.name, bases := bases.toArray, mro := mro, ns := ns }
859+ -- Fix MRO[ 0 ] to point to the actual class
860+ match classVal with
861+ | .classObj cref => do
862+ let cd' ← heapGetClassData cref
863+ heapSetClassData cref { cd' with mro := cd'.mro.set! 0 classVal }
864+ | _ => pure ()
777865 setVariable cd.name classVal
778866
779867 | .raise_ exprOpt cause _ => do
@@ -869,23 +957,21 @@ partial def execStmt (s : Stmt) : InterpM Unit := do
869957 | some other => do execStmts finally_; throw other
870958
871959 | .with_ items body _ => do
960+ -- Helper: try to get a named attribute from an object
961+ let getAttr := fun (obj : Value) (name : String) => do
962+ try
963+ let v ← getAttributeValue obj name
964+ return some v
965+ catch
966+ | .error (.attributeError _) => return (none : Option Value)
967+ | other => throw other
872968 -- Evaluate context managers and call __enter__
873969 let mut managers : List (Value × Value) := []
874970 for item in items do
875971 let mgr ← evalExpr item.contextExpr
876- -- Try to call __enter__ on the manager
877- let entered ← match mgr with
878- | .dict ref => do
879- let pairs ← heapGetDict ref
880- let mut enterFn : Option Value := none
881- for (k, v) in pairs do
882- match k with
883- | .str s => if s == "__enter__" then enterFn := some v
884- | _ => pure ()
885- match enterFn with
886- | some fn => callValueDispatch fn [mgr] []
887- | none => pure mgr
888- | _ => pure mgr
972+ let entered ← match ← getAttr mgr "__enter__" with
973+ | some fn => callValueDispatch fn [mgr] []
974+ | none => pure mgr
889975 if let some target := item.optionalVars then
890976 assignToTarget target entered
891977 managers := managers ++ [(mgr, entered)]
@@ -895,43 +981,23 @@ partial def execStmt (s : Stmt) : InterpM Unit := do
895981 catch
896982 | sig@(.error _) => bodyError := some sig
897983 | other => do
898- -- For control flow signals (return/break), still call __exit__ then re-throw
899984 for (mgr, _) in managers.reverse do
900- match mgr with
901- | .dict ref => do
902- let pairs ← heapGetDict ref
903- for (k, v) in pairs do
904- match k with
905- | .str s =>
906- if s == "__exit__" then do
907- let _ ← callValueDispatch v [mgr, .none, .none, .none] []
908- | _ => pure ()
909- | _ => pure ()
985+ if let some fn ← getAttr mgr "__exit__" then
986+ let _ ← callValueDispatch fn [mgr, .none, .none, .none] []
910987 throw other
911988 -- Call __exit__ on each manager in reverse order
912989 let mut suppressed := false
913990 for (mgr, _) in managers.reverse do
914- match mgr with
915- | .dict ref => do
916- let pairs ← heapGetDict ref
917- let mut exitFn : Option Value := none
918- for (k, v) in pairs do
919- match k with
920- | .str s => if s == "__exit__" then exitFn := some v
921- | _ => pure ()
922- match exitFn with
923- | some fn =>
924- let exitArgs := match bodyError with
925- | none => [mgr, .none, .none, .none]
926- | some (.error e) =>
927- let excType := Value.str (runtimeErrorTypeName e)
928- let excVal := Value.exception (runtimeErrorTypeName e) (runtimeErrorMessage e)
929- [mgr, excType, excVal, .none]
930- | _ => [mgr, .none, .none, .none]
931- let result ← callValueDispatch fn exitArgs []
932- if ← isTruthy result then suppressed := true
933- | none => pure ()
934- | _ => pure ()
991+ if let some fn ← getAttr mgr "__exit__" then
992+ let exitArgs := match bodyError with
993+ | none => [mgr, .none, .none, .none]
994+ | some (.error e) =>
995+ let excType := Value.str (runtimeErrorTypeName e)
996+ let excVal := Value.exception (runtimeErrorTypeName e) (runtimeErrorMessage e)
997+ [mgr, excType, excVal, .none]
998+ | _ => [mgr, .none, .none, .none]
999+ let result ← callValueDispatch fn exitArgs []
1000+ if ← isTruthy result then suppressed := true
9351001 -- Re-raise body error if not suppressed
9361002 match bodyError with
9371003 | some sig => if !suppressed then throw sig
@@ -1013,6 +1079,66 @@ partial def callBoundMethod (receiver : Value) (method : String) (args : List Va
10131079 | .tuple arr => callTupleMethod arr method args
10141080 | .builtin name => callBuiltinTypeMethod name method args
10151081 | .generator ref => callGeneratorMethod ref method args
1082+ | .instance iref => do
1083+ -- Look up method in instance's class MRO and call with self
1084+ let id_ ← heapGetInstanceData iref
1085+ match id_.cls with
1086+ | .classObj cref => do
1087+ let cd ← heapGetClassData cref
1088+ let mut found : Option (Value × Value) := none -- (function, defining class)
1089+ for mroEntry in cd.mro do
1090+ if found.isSome then break
1091+ match mroEntry with
1092+ | .classObj mref => do
1093+ let mcd ← heapGetClassData mref
1094+ if let some fn := mcd.ns[method]? then
1095+ found := some (fn, mroEntry)
1096+ | _ => pure ()
1097+ match found with
1098+ | some (fn, definingCls) =>
1099+ match fn with
1100+ | .function fref => do
1101+ let fd ← heapGetFunc fref
1102+ let scope ← bindFuncParams fd.params (receiver :: args) [] fd.defaults fd.kwDefaults
1103+ let scopeWithClass := scope.insert "__class__" definingCls
1104+ callRegularFunc fd scopeWithClass
1105+ | _ => callValueDispatch fn (receiver :: args) []
1106+ | none => throwAttributeError s! "'{ cd.name} ' object has no attribute '{ method} '"
1107+ | _ => throwAttributeError s! "instance has no attribute '{ method} '"
1108+ | .superObj startAfterCls inst => do
1109+ -- Look up method in MRO starting after startAfterCls
1110+ let instCls ← match inst with
1111+ | .instance iref => do
1112+ let id_ ← heapGetInstanceData iref
1113+ pure id_.cls
1114+ | _ => pure Value.none
1115+ match instCls with
1116+ | .classObj cref => do
1117+ let cd ← heapGetClassData cref
1118+ let mut pastStart := false
1119+ let mut found : Option (Value × Value) := none -- (function, defining class)
1120+ for mroEntry in cd.mro do
1121+ if found.isSome then break
1122+ if !pastStart then
1123+ if Value.beq mroEntry startAfterCls then pastStart := true
1124+ continue
1125+ match mroEntry with
1126+ | .classObj mref => do
1127+ let mcd ← heapGetClassData mref
1128+ if let some fn := mcd.ns[method]? then
1129+ found := some (fn, mroEntry)
1130+ | _ => pure ()
1131+ match found with
1132+ | some (fn, definingCls) =>
1133+ match fn with
1134+ | .function fref => do
1135+ let fd ← heapGetFunc fref
1136+ let scope ← bindFuncParams fd.params (inst :: args) [] fd.defaults fd.kwDefaults
1137+ let scopeWithClass := scope.insert "__class__" definingCls
1138+ callRegularFunc fd scopeWithClass
1139+ | _ => callValueDispatch fn (inst :: args) []
1140+ | none => throwAttributeError s! "'super' object has no attribute '{ method} '"
1141+ | _ => throwAttributeError s! "'super' object has no attribute '{ method} '"
10161142 | _ => throwAttributeError s! "'{ typeName receiver} ' object has no attribute '{ method} '"
10171143
10181144-- ============================================================
0 commit comments