Skip to content

Commit c3e6906

Browse files
pirapiraclaude
andcommitted
Continue Phase 9a: Container, SSZVector/SSZList, BaseBitvector support
Add composite SSZ type support for leanSpec's Container, collection, and bitfield patterns. Key interpreter fixes: - isinstance(classObj, type) returns True for classes and builtin types - model_fields stores actual type classObj from __annotations_raw__ - .none subscriptable for typing stubs (ClassVar[int], Generic[T]) - TypeVar callable stub, __class_getitem__ on classObj - iterValuesExt: instance __iter__/__getitem__ support in for-loops New end-to-end tests: Container with serialize/deserialize round-trip, SSZVector with fixed-size elements, SSZList with variable-length, BaseBitvector with bit-packing. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 590434c commit c3e6906

4 files changed

Lines changed: 156 additions & 30 deletions

File tree

LeanPython/Interpreter/Eval.lean

Lines changed: 97 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -459,6 +459,27 @@ partial def callDunder (inst : Value) (name : String) (args : List Value) : Inte
459459
| _ => some <$> callValueDispatch fn (inst :: args) []
460460
| none => return none
461461

462+
/-- Extended iterValues that handles instance __iter__ via callDunder. -/
463+
partial def iterValuesExt (v : Value) : InterpM (Array Value) := do
464+
match v with
465+
| .instance _ => do
466+
match ← callDunder v "__iter__" [] with
467+
| some iterResult => iterValues iterResult
468+
| none =>
469+
-- Try __getitem__ protocol: iterate with indices 0, 1, 2, ...
470+
let mut items : Array Value := #[]
471+
let mut idx := 0
472+
let mut done := false
473+
while !done do
474+
match ← callDunder v "__getitem__" [.int idx] with
475+
| some item =>
476+
items := items.push item
477+
idx := idx + 1
478+
| none => done := true
479+
if idx == 0 then throwTypeError s!"'{typeName v}' object is not iterable"
480+
return items
481+
| _ => iterValues v
482+
462483
partial def evalExpr (e : Expr) : InterpM Value := do
463484
match e with
464485
| .name n _ => lookupVariable n
@@ -692,7 +713,7 @@ partial def evalExpr (e : Expr) : InterpM Value := do
692713
return .none
693714
| .yieldFrom iterExpr _ => do
694715
let iterVal ← evalExpr iterExpr
695-
let items ← iterValues iterVal
716+
let items ← iterValuesExt iterVal
696717
let st ← get
697718
match st.yieldAccumulator with
698719
| some acc => set { st with yieldAccumulator := some (acc ++ items) }
@@ -706,7 +727,7 @@ partial def evalArgList (args : List Expr) : InterpM (List Value) := do
706727
match arg with
707728
| .starred inner _ => do
708729
let v ← evalExpr inner
709-
let items ← iterValues v
730+
let items ← iterValuesExt v
710731
result := result ++ items.toList
711732
| _ => result := result ++ [← evalExpr arg]
712733
return result
@@ -746,10 +767,10 @@ partial def callValueDispatch (callee : Value) (args : List Value)
746767
| [] => pure (Value.none, #[])
747768
| [f] => pure (f, #[])
748769
| [f, init] => do
749-
let items ← iterValues init
770+
let items ← iterValuesExt init
750771
let mut pairs : Array (Value × Value) := #[]
751772
for item in items do
752-
let kv ← iterValues item
773+
let kv ← iterValuesExt item
753774
if kv.size >= 2 then pairs := pairs.push (kv[0]!, kv[1]!)
754775
pure (f, pairs)
755776
| _ => throwTypeError "defaultdict() takes at most 2 positional arguments"
@@ -771,7 +792,7 @@ partial def callValueDispatch (callee : Value) (args : List Value)
771792
match args with
772793
| [] => allocList #[]
773794
| [iter] => do
774-
let items ← iterValues iter
795+
let items ← iterValuesExt iter
775796
allocList items
776797
| _ => throwTypeError "deque() takes at most 1 argument"
777798
| "operator.itemgetter" => do
@@ -2141,7 +2162,7 @@ partial def evalCompGen (generators : List Comprehension)
21412162
match generators with
21422163
| [] => return acc.push (← body)
21432164
| gen :: rest => do
2144-
let items ← iterValues (← evalExpr gen.iter)
2165+
let items ← iterValuesExt (← evalExpr gen.iter)
21452166
let mut result := acc
21462167
for item in items do
21472168
assignToTarget gen.target item
@@ -2157,7 +2178,7 @@ partial def evalDictCompGen (generators : List Comprehension)
21572178
match generators with
21582179
| [] => return acc.push (← body)
21592180
| gen :: rest => do
2160-
let items ← iterValues (← evalExpr gen.iter)
2181+
let items ← iterValuesExt (← evalExpr gen.iter)
21612182
let mut result := acc
21622183
for item in items do
21632184
assignToTarget gen.target item
@@ -2514,6 +2535,13 @@ partial def evalSubscriptValue (obj idx : Value) : InterpM Value := do
25142535
match ← callDunder obj "__getitem__" [idx] with
25152536
| some v => return v
25162537
| none => throwTypeError s!"'{typeName obj}' object is not subscriptable"
2538+
| .none => return .none -- typing stubs: ClassVar[int], Generic[T], IO[bytes] etc.
2539+
| .classObj cref => do
2540+
-- __class_getitem__ support: SomeClass[T] calls cls.__class_getitem__(T)
2541+
let cd ← heapGetClassData cref
2542+
match cd.ns["__class_getitem__"]? with
2543+
| some fn => callValueDispatch fn [idx] []
2544+
| none => return .none -- default: subscripting a class returns .none (for typing)
25172545
| _ => throwTypeError s!"'{typeName obj}' object is not subscriptable"
25182546

25192547
-- Assignment target resolution
@@ -2525,7 +2553,7 @@ partial def assignToTarget (target : Expr) (value : Value) : InterpM Unit := do
25252553
let idxVal ← evalExpr idx
25262554
assignSubscriptValue objVal idxVal value
25272555
| .tuple targets _ | .list_ targets _ => do
2528-
let items ← iterValues value
2556+
let items ← iterValuesExt value
25292557
-- Check for starred
25302558
let starIdx := targets.findIdx? fun
25312559
| .starred _ _ => true
@@ -2695,9 +2723,11 @@ partial def getBuiltinModule (name : String) : InterpM (Option Value) := do
26952723
"TypeAlias", "Literal", "IO", "Sequence", "Mapping",
26962724
"Iterator", "Iterable", "Generator", "Coroutine",
26972725
"Awaitable", "AsyncIterator", "AsyncGenerator",
2698-
"Type", "Generic", "TypeVar", "Annotated",
2726+
"Type", "Generic", "Annotated",
26992727
"overload", "cast", "no_type_check"] do
27002728
ns := ns.insert n .none
2729+
-- TypeVar is callable (returns .none as a stub type variable)
2730+
ns := ns.insert "TypeVar" (.builtin "typing.TypeVar")
27012731
-- override is a callable identity decorator (not .none)
27022732
ns := ns.insert "override" (.builtin "typing.override")
27032733
some <$> mkMod ns
@@ -3279,7 +3309,7 @@ partial def execStmt (s : Stmt) : InterpM Unit := do
32793309
if !brokeOut then execStmts orelse
32803310

32813311
| .for_ target iter body orelse _ => do
3282-
let items ← iterValues (← evalExpr iter)
3312+
let items ← iterValuesExt (← evalExpr iter)
32833313
let mut brokeOut := false
32843314
for item in items do
32853315
assignToTarget target item
@@ -3724,7 +3754,7 @@ partial def execStmt (s : Stmt) : InterpM Unit := do
37243754
throwImportError s!"cannot import name '{alias.name}' from '{fqName}'"
37253755

37263756
| .asyncFor target iter body orelse _ => do
3727-
let items ← iterValues (← evalExpr iter)
3757+
let items ← iterValuesExt (← evalExpr iter)
37283758
let mut brokeOut := false
37293759
for item in items do
37303760
assignToTarget target item
@@ -3975,7 +4005,7 @@ partial def callBoundMethod (receiver : Value) (method : String) (args : List Va
39754005
partial def builtinMap (args : List Value) : InterpM Value := do
39764006
match args with
39774007
| [func, iter] => do
3978-
let items ← iterValues iter
4008+
let items ← iterValuesExt iter
39794009
let mut result : Array Value := #[]
39804010
for item in items do
39814011
let v ← callValueDispatch func [item] []
@@ -3986,7 +4016,7 @@ partial def builtinMap (args : List Value) : InterpM Value := do
39864016
partial def builtinFilter (args : List Value) : InterpM Value := do
39874017
match args with
39884018
| [func, iter] => do
3989-
let items ← iterValues iter
4019+
let items ← iterValuesExt iter
39904020
let mut result : Array Value := #[]
39914021
for item in items do
39924022
let keep ← match func with
@@ -4042,14 +4072,14 @@ partial def isBaseModelSubclass (bases : Array Value) : InterpM Bool := do
40424072
partial def builtinFunctoolsReduce (args : List Value) : InterpM Value := do
40434073
match args with
40444074
| [func, iter] => do
4045-
let items ← iterValues iter
4075+
let items ← iterValuesExt iter
40464076
if items.isEmpty then throwTypeError "reduce() of empty iterable with no initial value"
40474077
let mut acc := items[0]!
40484078
for i in [1:items.size] do
40494079
acc ← callValueDispatch func [acc, items[i]!] []
40504080
return acc
40514081
| [func, iter, initial] => do
4052-
let items ← iterValues iter
4082+
let items ← iterValuesExt iter
40534083
let mut acc := initial
40544084
for item in items do
40554085
acc ← callValueDispatch func [acc, item] []
@@ -4070,7 +4100,7 @@ partial def builtinItertoolsAccumulate (args : List Value)
40704100
| some (_, f) => some f
40714101
| none => func
40724102
let initial := kwargs.find? (fun p => p.1 == "initial") |>.map (·.2)
4073-
let items ← iterValues iter
4103+
let items ← iterValuesExt iter
40744104
let mut result : Array Value := #[]
40754105
let mut acc : Value := match initial with
40764106
| some v => v
@@ -4295,7 +4325,7 @@ partial def callListMethod (ref : HeapRef) (method : String) (args : List Value)
42954325
| "extend" =>
42964326
match args with
42974327
| [v] => do
4298-
let items ← iterValues v
4328+
let items ← iterValuesExt v
42994329
heapSetList ref ((← heapGetList ref) ++ items); return .none
43004330
| _ => throwTypeError "extend() takes exactly one argument"
43014331
| "pop" => do
@@ -4470,7 +4500,7 @@ partial def callStrMethod (s : String) (method : String) (args : List Value)
44704500
| "join" =>
44714501
match args with
44724502
| [iter] => do
4473-
let items ← iterValues iter
4503+
let items ← iterValuesExt iter
44744504
let strs ← items.toList.mapM fun v =>
44754505
match v with
44764506
| .str sub => pure sub
@@ -4677,7 +4707,7 @@ partial def callSetMethod (ref : HeapRef) (method : String) (args : List Value)
46774707
match args with
46784708
| [other] => do
46794709
let a ← heapGetSet ref
4680-
let b ← iterValues other
4710+
let b ← iterValuesExt other
46814711
let mut result := a
46824712
for elem in b do
46834713
let mut found := false
@@ -4690,7 +4720,7 @@ partial def callSetMethod (ref : HeapRef) (method : String) (args : List Value)
46904720
match args with
46914721
| [other] => do
46924722
let a ← heapGetSet ref
4693-
let b ← iterValues other
4723+
let b ← iterValuesExt other
46944724
let mut result : Array Value := #[]
46954725
for elem in a do
46964726
let mut found := false
@@ -4703,7 +4733,7 @@ partial def callSetMethod (ref : HeapRef) (method : String) (args : List Value)
47034733
match args with
47044734
| [other] => do
47054735
let a ← heapGetSet ref
4706-
let b ← iterValues other
4736+
let b ← iterValuesExt other
47074737
let mut result : Array Value := #[]
47084738
for elem in a do
47094739
let mut found := false
@@ -4716,7 +4746,7 @@ partial def callSetMethod (ref : HeapRef) (method : String) (args : List Value)
47164746
match args with
47174747
| [other] => do
47184748
let a ← heapGetSet ref
4719-
let b ← iterValues other
4749+
let b ← iterValuesExt other
47204750
let mut result : Array Value := #[]
47214751
for elem in a do
47224752
let mut found := false
@@ -4734,7 +4764,7 @@ partial def callSetMethod (ref : HeapRef) (method : String) (args : List Value)
47344764
match args with
47354765
| [other] => do
47364766
let a ← heapGetSet ref
4737-
let b ← iterValues other
4767+
let b ← iterValuesExt other
47384768
for elem in a do
47394769
let mut found := false
47404770
for otherElem in b do
@@ -4746,7 +4776,7 @@ partial def callSetMethod (ref : HeapRef) (method : String) (args : List Value)
47464776
match args with
47474777
| [other] => do
47484778
let a ← heapGetSet ref
4749-
let b ← iterValues other
4779+
let b ← iterValuesExt other
47504780
for elem in b do
47514781
let mut found := false
47524782
for otherElem in a do
@@ -4758,7 +4788,7 @@ partial def callSetMethod (ref : HeapRef) (method : String) (args : List Value)
47584788
match args with
47594789
| [other] => do
47604790
let a ← heapGetSet ref
4761-
let b ← iterValues other
4791+
let b ← iterValuesExt other
47624792
for elem in a do
47634793
for otherElem in b do
47644794
if ← valueEq elem otherElem then return .bool false
@@ -5127,10 +5157,50 @@ partial def applyPydanticModelProcessing (_classVal : Value) (cref : HeapRef)
51275157
nsUpdated := nsUpdated.insert "__pydantic_field_serializers__" (.list fsRef)
51285158
nsUpdated := nsUpdated.insert "__pydantic_model_serializers__" (.list msRef)
51295159
-- 6. Build model_fields dict: {field_name: {"annotation": ..., "default": ...}}
5160+
-- First, collect all raw annotations (own + inherited) for actual type values
5161+
let ownRawAnns ← match cd.ns["__annotations_raw__"]? with
5162+
| some (.dict ref) => heapGetDict ref
5163+
| _ => pure #[]
5164+
let mut allRawAnns : Array (Value × Value) := #[]
5165+
-- Collect inherited raw annotations from parent classes
5166+
for mroEntry in cd.mro do
5167+
match mroEntry with
5168+
| .classObj mref => do
5169+
if mref == cref then continue
5170+
let mcd ← heapGetClassData mref
5171+
if mcd.name == "BaseModel" then continue
5172+
if mcd.ns["__pydantic_model__"]? != some (.bool true) then continue
5173+
match mcd.ns["__annotations_raw__"]? with
5174+
| some (.dict ref) => do
5175+
let pairs ← heapGetDict ref
5176+
for (k, v) in pairs do
5177+
-- Only add if not already present (child overrides parent)
5178+
if !(allRawAnns.any fun (ek, _) => Value.beq ek k) then
5179+
allRawAnns := allRawAnns.push (k, v)
5180+
| _ => pure ()
5181+
| _ => pure ()
5182+
-- Add own raw annotations (override inherited)
5183+
for (k, v) in ownRawAnns do
5184+
let mut found := false
5185+
for i in [:allRawAnns.size] do
5186+
if Value.beq allRawAnns[i]!.1 k then
5187+
allRawAnns := allRawAnns.set! i (k, v)
5188+
found := true
5189+
break
5190+
if !found then allRawAnns := allRawAnns.push (k, v)
5191+
-- Build the model_fields dict using actual types from raw annotations
51305192
let mut mfPairs : Array (Value × Value) := #[]
51315193
for (name, defVal) in allFields do
51325194
let mut infoPairs : Array (Value × Value) := #[]
5133-
infoPairs := infoPairs.push (.str "annotation", .str "Any")
5195+
-- Look up actual type from raw annotations
5196+
let annVal ← do
5197+
let mut found : Value := .str "Any"
5198+
for (k, v) in allRawAnns do
5199+
if Value.beq k (.str name) then
5200+
found := v
5201+
break
5202+
pure found
5203+
infoPairs := infoPairs.push (.str "annotation", annVal)
51345204
match defVal with
51355205
| some v => infoPairs := infoPairs.push (.str "default", v)
51365206
| none => pure ()
@@ -5140,9 +5210,7 @@ partial def applyPydanticModelProcessing (_classVal : Value) (cref : HeapRef)
51405210
nsUpdated := nsUpdated.insert "model_fields" mfDict
51415211
-- 6b. Build __get_pydantic_core_schema__ field schema map
51425212
-- For each field, check if the annotation type has __get_pydantic_core_schema__
5143-
let rawAnns ← match cd.ns["__annotations_raw__"]? with
5144-
| some (.dict ref) => heapGetDict ref
5145-
| _ => pure #[]
5213+
let rawAnns := allRawAnns
51465214
let mut fieldSchemaPairs : Array (Value × Value) := #[]
51475215
for (nameV, typeV) in rawAnns do
51485216
match nameV with

LeanPython/Runtime/Builtins.lean

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,13 @@ partial def builtinIsinstance (args : List Value) : InterpM Value := do
367367
| "set" => tn == "set"
368368
| "bytes" => tn == "bytes"
369369
| "bytearray" => tn == "bytearray"
370+
| "type" =>
371+
-- classObj values are types, and builtin type names (int, str, etc.) are types
372+
tn == "type" || match obj with
373+
| .builtin n => ["int", "float", "str", "bool", "list", "tuple", "dict",
374+
"set", "bytes", "bytearray", "type", "object",
375+
"frozenset", "memoryview", "complex"].contains n
376+
| _ => false
370377
| _ => false
371378
if isMatch then return .bool true
372379
-- For instances, check if any class in MRO is the synthetic built-in type
@@ -937,6 +944,9 @@ partial def callBuiltin (name : String) (args : List Value)
937944
match args with
938945
| [f] => return f
939946
| _ => throwTypeError "override() takes exactly 1 argument"
947+
| "typing.TypeVar" => do
948+
-- TypeVar('T', bound=SomeType) — stub that returns .none
949+
return .none
940950
-- ============================================================
941951
-- dataclasses module functions
942952
-- ============================================================

0 commit comments

Comments
 (0)