@@ -1401,18 +1401,32 @@ partial def callValueDispatch (callee : Value) (args : List Value)
14011401 | _ => throwTypeError s! "int.__new__: cannot convert { typeName val} to int"
14021402 | other => throwTypeError s! "int.__new__: cannot convert { typeName other} to int"
14031403 else if name == "bytes.__new__" then do
1404- match args with
1405- | [cls@(.classObj _), .bytes b] =>
1404+ -- bytes.__new__ (cls, value) — may be called via super().__new__ (cls, value)
1405+ -- which prepends inst, giving [inst, cls, value]. Extract last class and value.
1406+ let (cls, mval) ← match args with
1407+ | [cls@(.classObj _), v] => pure (cls, some v)
1408+ | [_, cls@(.classObj _), v] => pure (cls, some v) -- super() prepends inst
1409+ | [cls@(.classObj _)] => pure (cls, none)
1410+ | [_, cls@(.classObj _)] => pure (cls, none) -- super() prepends inst, no value
1411+ | _ => throwTypeError "bytes.__new__(cls, value) requires a class and bytes"
1412+ match mval with
1413+ | some (.bytes b) =>
14061414 allocInstance { cls := cls, attrs := {}, wrappedValue := some (.bytes b) }
1407- | [cls@(.classObj _), . int n] => do
1415+ | some (. int n) => do
14081416 -- bytes(n) creates n zero bytes
14091417 let mut buf := ByteArray.empty
14101418 for _ in List.range n.toNat do
14111419 buf := buf.push 0
14121420 allocInstance { cls := cls, attrs := {}, wrappedValue := some (.bytes buf) }
1413- | [_cls@(.classObj _)] =>
1414- throwTypeError "bytes.__new__() missing required argument"
1415- | _ => throwTypeError "bytes.__new__(cls, value) requires a class and bytes"
1421+ | some (.instance iref) => do
1422+ let id_ ← heapGetInstanceData iref
1423+ match id_.wrappedValue with
1424+ | some (.bytes b) =>
1425+ allocInstance { cls := cls, attrs := {}, wrappedValue := some (.bytes b) }
1426+ | _ => throwTypeError s! "bytes.__new__: cannot convert { typeName (.instance iref)} to bytes"
1427+ | none =>
1428+ allocInstance { cls := cls, attrs := {}, wrappedValue := some (.bytes ByteArray.empty) }
1429+ | some other => throwTypeError s! "bytes.__new__: cannot convert { typeName other} to bytes"
14161430 else if name.startsWith "int." then do
14171431 -- Dispatch int dunder methods: extract wrapped int values
14181432 let methodName := String.ofList (name.toList.drop "int." .length)
@@ -1608,6 +1622,114 @@ partial def callValueDispatch (callee : Value) (args : List Value)
16081622 return .int bits
16091623 | _ => throwTypeError "int.bit_length takes 1 argument"
16101624 | _ => throwTypeError s! "int.{ methodName} is not implemented"
1625+ else if name.startsWith "bytes." && name != "bytes.__new__" then do
1626+ -- Dispatch bytes dunder methods: extract wrapped bytes values
1627+ let methodName := String.ofList (name.toList.drop "bytes." .length)
1628+ let extractBytes : Value → InterpM ByteArray := fun v =>
1629+ match v with
1630+ | .bytes b => pure b
1631+ | .instance iref => do
1632+ let id_ ← heapGetInstanceData iref
1633+ match id_.wrappedValue with
1634+ | some (.bytes b) => pure b
1635+ | _ => throwTypeError s! "expected bytes, got { typeName v} "
1636+ | other => throwTypeError s! "expected bytes, got { typeName other} "
1637+ match methodName with
1638+ | "__len__" => match args with
1639+ | [a] => return .int (← extractBytes a).size
1640+ | _ => throwTypeError "bytes.__len__ takes 1 argument"
1641+ | "__getitem__" => match args with
1642+ | [a, .int idx] => do
1643+ let b ← extractBytes a
1644+ let i : Int := if idx < 0 then (b.size : Int) + idx else idx
1645+ if i < 0 || i >= b.size then throwTypeError "index out of range"
1646+ return .int (b[i.toNat]!.toNat : Int)
1647+ | _ => throwTypeError "bytes.__getitem__ takes 2 arguments"
1648+ | "__contains__" => match args with
1649+ | [a, .int byte_] => do
1650+ let b ← extractBytes a
1651+ return .bool (b.toList.any (fun x => x.toNat == byte_.toNat))
1652+ | _ => return .bool false
1653+ | "__iter__" => match args with
1654+ | [a] => do
1655+ let b ← extractBytes a
1656+ let items := b.toList.map (fun byte => Value.int byte.toNat)
1657+ allocGenerator items.toArray
1658+ | _ => throwTypeError "bytes.__iter__ takes 1 argument"
1659+ | "__add__" => match args with
1660+ | [a, b] => return .bytes ((← extractBytes a) ++ (← extractBytes b))
1661+ | _ => throwTypeError "bytes.__add__ takes 2 arguments"
1662+ | "__mul__" | "__rmul__" => match args with
1663+ | [a, .int n] => do
1664+ let b ← extractBytes a
1665+ if n <= 0 then return .bytes ByteArray.empty
1666+ else
1667+ let mut result := ByteArray.empty
1668+ for _ in [:n.toNat] do result := result ++ b
1669+ return .bytes result
1670+ | _ => throwTypeError "bytes.__mul__ takes 2 arguments"
1671+ | "__eq__" => match args with
1672+ | [a, b] => return .bool ((← extractBytes a) == (← extractBytes b))
1673+ | _ => throwTypeError "bytes.__eq__ takes 2 arguments"
1674+ | "__ne__" => match args with
1675+ | [a, b] => return .bool ((← extractBytes a) != (← extractBytes b))
1676+ | _ => throwTypeError "bytes.__ne__ takes 2 arguments"
1677+ | "__hash__" => match args with
1678+ | [a] => do
1679+ let b ← extractBytes a
1680+ let h := hash b.toList
1681+ return .int h.toNat
1682+ | _ => throwTypeError "bytes.__hash__ takes 1 argument"
1683+ | "__repr__" | "__str__" => match args with
1684+ | [a] => do
1685+ let b ← extractBytes a
1686+ -- Simple hex representation
1687+ let hexDigit (n : Nat) : Char := if n < 10 then Char.ofNat (48 + n) else Char.ofNat (87 + n)
1688+ let mut s := "b'"
1689+ for byte in b.toList do
1690+ let hi := byte.toNat / 16
1691+ let lo := byte.toNat % 16
1692+ s := s ++ s! "\\ x{ String.ofList [hexDigit hi, hexDigit lo]} "
1693+ s := s ++ "'"
1694+ return .str s
1695+ | _ => throwTypeError "bytes.__repr__ takes 1 argument"
1696+ | "hex" => match args with
1697+ | [a] => do
1698+ let b ← extractBytes a
1699+ let mut result := ""
1700+ for byte in b.toList do
1701+ let hi := byte.toNat / 16
1702+ let lo := byte.toNat % 16
1703+ let hexDigit (n : Nat) : Char := if n < 10 then Char.ofNat (48 + n) else Char.ofNat (87 + n)
1704+ result := result ++ String.ofList [hexDigit hi, hexDigit lo]
1705+ return .str result
1706+ | _ => throwTypeError "bytes.hex takes 1 argument"
1707+ | "fromhex" => match args with
1708+ | [.str hex] => do
1709+ let cleaned := hex.toList.filter (· != ' ' )
1710+ let mut result := ByteArray.empty
1711+ let mut i := 0
1712+ while i + 1 < cleaned.length do
1713+ let hi := cleaned[i]!
1714+ let lo := cleaned[i+1 ]!
1715+ let hexVal (c : Char) : Nat :=
1716+ if c.val >= 48 && c.val <= 57 then c.val.toNat - 48
1717+ else if c.val >= 65 && c.val <= 70 then c.val.toNat - 55
1718+ else if c.val >= 97 && c.val <= 102 then c.val.toNat - 87
1719+ else 0
1720+ result := result.push (hexVal hi * 16 + hexVal lo).toUInt8
1721+ i := i + 2
1722+ return .bytes result
1723+ | _ => throwTypeError "bytes.fromhex takes 1 argument"
1724+ | "decode" => match args with
1725+ | [a] => do
1726+ let b ← extractBytes a
1727+ return .str (String.ofList (b.toList.map (fun byte => Char.ofNat byte.toNat)))
1728+ | [a, _encoding] => do
1729+ let b ← extractBytes a
1730+ return .str (String.ofList (b.toList.map (fun byte => Char.ofNat byte.toNat)))
1731+ | _ => throwTypeError "bytes.decode takes at most 1 argument"
1732+ | _ => throwTypeError s! "bytes.{ methodName} is not implemented"
16111733 else
16121734 callBuiltin name args kwargs
16131735 | .function ref => do
@@ -3598,7 +3720,16 @@ partial def callBoundMethod (receiver : Value) (method : String) (args : List Va
35983720 | .dict ref => callDictMethod ref method args
35993721 | .str s => callStrMethod s method args
36003722 | .set ref => callSetMethod ref method args
3601- | .int n => callIntMethod n method args
3723+ | .int n => do
3724+ -- Convert known kwargs to positional args for builtin int methods
3725+ let args' ← if kwargs.isEmpty then pure args else
3726+ match method with
3727+ | "to_bytes" =>
3728+ let length := kwargs.find? (·.1 == "length" ) |>.map (·.2 ) |>.getD (.int 1 )
3729+ let byteorder := kwargs.find? (·.1 == "byteorder" ) |>.map (·.2 ) |>.getD (.str "big" )
3730+ pure (args ++ [length, byteorder])
3731+ | _ => pure args
3732+ callIntMethod n method args'
36023733 | .bytes b => callBytesMethod b method args
36033734 | .tuple arr => callTupleMethod arr method args
36043735 | .builtin name => callBuiltinTypeMethod name method args
@@ -3629,15 +3760,15 @@ partial def callBoundMethod (receiver : Value) (method : String) (args : List Va
36293760 | _ => pure ()
36303761 match found with
36313762 | some (.classMethod innerFn) =>
3632- callValueDispatch innerFn (receiver :: args) []
3763+ callValueDispatch innerFn (receiver :: args) kwargs
36333764 | some (.staticMethod innerFn) =>
3634- callValueDispatch innerFn args []
3765+ callValueDispatch innerFn args kwargs
36353766 | some (.function fref) => do
36363767 -- Regular function called on class (no self binding)
36373768 let fd ← heapGetFunc fref
3638- let scope ← bindFuncParams fd.params args [] fd.defaults fd.kwDefaults
3769+ let scope ← bindFuncParams fd.params args kwargs fd.defaults fd.kwDefaults
36393770 callRegularFunc fd scope
3640- | some fn => callValueDispatch fn args []
3771+ | some fn => callValueDispatch fn args kwargs
36413772 | none => throwAttributeError s! "type object '{ cd.name} ' has no attribute '{ method} '"
36423773 | .instance iref => do
36433774 -- Look up method in instance's class MRO and call with self
@@ -3703,16 +3834,16 @@ partial def callBoundMethod (receiver : Value) (method : String) (args : List Va
37033834 match fn with
37043835 | .classMethod innerFn =>
37053836 -- Call with class as first arg instead of instance
3706- callValueDispatch innerFn (id_.cls :: args) []
3837+ callValueDispatch innerFn (id_.cls :: args) kwargs
37073838 | .staticMethod innerFn =>
37083839 -- Call without self
3709- callValueDispatch innerFn args []
3840+ callValueDispatch innerFn args kwargs
37103841 | .function fref => do
37113842 let fd ← heapGetFunc fref
3712- let scope ← bindFuncParams fd.params (receiver :: args) [] fd.defaults fd.kwDefaults
3843+ let scope ← bindFuncParams fd.params (receiver :: args) kwargs fd.defaults fd.kwDefaults
37133844 let scopeWithClass := scope.insert "__class__" definingCls
37143845 callRegularFunc fd scopeWithClass
3715- | _ => callValueDispatch fn (receiver :: args) []
3846+ | _ => callValueDispatch fn (receiver :: args) kwargs
37163847 | none => throwAttributeError s! "'{ cd.name} ' object has no attribute '{ method} '"
37173848 | _ => throwAttributeError s! "instance has no attribute '{ method} '"
37183849 | .superObj startAfterCls inst => do
@@ -3744,19 +3875,19 @@ partial def callBoundMethod (receiver : Value) (method : String) (args : List Va
37443875 match fn with
37453876 | .function fref => do
37463877 let fd ← heapGetFunc fref
3747- let scope ← bindFuncParams fd.params (inst :: args) [] fd.defaults fd.kwDefaults
3878+ let scope ← bindFuncParams fd.params (inst :: args) kwargs fd.defaults fd.kwDefaults
37483879 let scopeWithClass := scope.insert "__class__" definingCls
37493880 callRegularFunc fd scopeWithClass
37503881 | .classMethod innerFn =>
37513882 -- classmethod: call with class as first arg
3752- callValueDispatch innerFn (inst :: args) []
3883+ callValueDispatch innerFn (inst :: args) kwargs
37533884 | .staticMethod innerFn =>
37543885 -- staticmethod: call without self
3755- callValueDispatch innerFn args []
3886+ callValueDispatch innerFn args kwargs
37563887 | .builtin _ =>
37573888 -- Builtins in synthetic type classes: prepend inst (self/cls)
3758- callValueDispatch fn (inst :: args) []
3759- | _ => callValueDispatch fn (inst :: args) []
3889+ callValueDispatch fn (inst :: args) kwargs
3890+ | _ => callValueDispatch fn (inst :: args) kwargs
37603891 | none => throwAttributeError s! "'super' object has no attribute '{ method} '"
37613892 | _ => throwAttributeError s! "'super' object has no attribute '{ method} '"
37623893 | _ => throwAttributeError s! "'{ typeName receiver} ' object has no attribute '{ method} '"
0 commit comments