Skip to content

Commit 01456d1

Browse files
committed
feat(method): builtin classmethod
1 parent dd55fd8 commit 01456d1

File tree

6 files changed

+138
-16
lines changed

6 files changed

+138
-16
lines changed

Objects/descrobject.nim

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,37 @@ import methodobject
99
import ../Include/descrobject as incDescr
1010
export incDescr
1111

12+
#import ../Python/getargs/vargs
1213
import ./typeobject/apis/subtype
1314

1415
import ../Utils/utils
1516

1617
# method descriptor
1718

18-
declarePyType MethodDescr():
19+
declarePyType CommonMethodDescr():
1920
name: PyStrObject
2021
dType: PyTypeObject
2122
kind: NFunc
2223
meth: int # the method function pointer. Have to be int to make it generic.
2324

25+
declarePyType MethodDescr(base(CommonMethodDescr)):
26+
discard
2427

25-
template newMethodDescrTmpl(FunType) =
26-
proc newPyMethodDescr*(t: PyTypeObject,
28+
29+
template newXxMethodDescrTmpl(PyType, FunType){.dirty.} =
30+
proc `newPy PyType`*(t: PyTypeObject,
2731
meth: FunType,
2832
name: PyStrObject,
29-
): PyMethodDescrObject =
30-
result = newPyMethodDescrSimple()
33+
): `Py PyType Object` =
34+
result = `newPy PyType Simple`()
3135
result.dType = t
3236
result.kind = NFunc.FunType
3337
assert result.kind != NFunc.BltinFunc
3438
result.meth = cast[int](meth)
3539
result.name = name
3640

41+
template newMethodDescrTmpl(FunType) =
42+
newXxMethodDescrTmpl MethodDescr, FunType
3743

3844
newMethodDescrTmpl(UnaryMethod)
3945
newMethodDescrTmpl(BinaryMethod)
@@ -72,6 +78,64 @@ implMethodDescrMagic get:
7278
return newPyNimFunc(cast[BltinMethod](self.meth), self.name, owner)
7379

7480

81+
declarePyType ClassMethodDescr(base(CommonMethodDescr)): discard
82+
83+
84+
template newClassMethodDescrTmpl(FunType) =
85+
newXxMethodDescrTmpl ClassMethodDescr, FunType
86+
87+
# newPyClassMethodDescr
88+
newClassMethodDescrTmpl BltinMethod
89+
90+
proc `$?`*(descr: PyCommonMethodDescrObject): string =
91+
## inner. unstable.
92+
# PyErr_Format ... "%V" .. "?"
93+
if descr.name.isNil: "?" else: $descr.name
94+
95+
proc truncedTypeName*(descr: PyCommonMethodDescrObject): string =
96+
## inner. unstable.
97+
# %.100s with `PyDescr_TYPE(descr)->tp_name`
98+
descr.dType.name.substr(0, 99)
99+
100+
proc classmethod_getImpl(descr: PyCommonMethodDescrObject, obj: PyObject, typ: PyTypeObject): PyObject =
101+
assert not typ.isNil
102+
if not typ.isSubtype descr.dType:
103+
return newTypeError newPyStr(
104+
fmt"descriptor '{$?descr}' requires a subtype of '{descr.truncedTypeName}' " &
105+
fmt"but received '{typ.name:.100s}'"
106+
)
107+
var cls = PyTypeObject nil
108+
#if descr.isMETH_METHOD:
109+
cls = descr.dType
110+
assert descr.kind == NFunc.BltinMethod
111+
newPyNimFunc(cast[BltinMethod](descr.meth), descr.name, cls)
112+
113+
proc classmethod_get(descr: PyCommonMethodDescrObject, obj: PyObject, typ: PyTypeObject = nil): PyObject =
114+
var typ = typ
115+
if typ.isNil:
116+
if not obj.isNil:
117+
typ = obj.pyType
118+
else:
119+
# Wot - no type?!
120+
return newTypeError newPyStr(
121+
fmt"descriptor '{$?descr}' for type '{descr.truncedTypeName}' " &
122+
"needs either and object or a type")
123+
classmethod_getImpl(descr, obj, typ)
124+
125+
proc classmethod_get*(self: PyObject, obj: PyObject, typ: PyObject = nil): PyObject =
126+
## inner
127+
let descr = PyCommonMethodDescrObject self
128+
# Ensure a valid type. Class methods ignore obj.
129+
if not typ.isNil and not typ.ofPyTypeObject:
130+
return newTypeError newPyStr(
131+
fmt"descriptor '{$?descr}' for type '{descr.truncedTypeName}' " &
132+
fmt"needs a type, not a '{typ.typeName:.100s}' as arg 2"
133+
)
134+
classmethod_get(descr, obj, PyTypeObject typ)
135+
136+
implClassMethodDescrMagic get:
137+
classmethod_get(self, nil, other)
138+
75139
# get set descriptor
76140
# Nim level property decorator
77141

Objects/descrobjectImpl.nim

Lines changed: 47 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11

2-
import ./pyobject
3-
import ./noneobject
2+
import std/strformat
3+
import ./[
4+
pyobject,
5+
noneobject,
6+
exceptions,
7+
stringobject,
8+
methodobject,
9+
]
410
import ./descrobject
5-
import ./exceptions
611
import ../Python/[
712
structmember,
813
]
@@ -37,3 +42,42 @@ implMemberDescrMagic set:
3742
retIfExc PyMember_SetOne(obj, self.d_member, arg2)
3843
pyNone
3944

45+
46+
methodMacroTmpl(MethodDescr)
47+
48+
implMethodDescrMagic call:
49+
#call(bound, args, kwargs)
50+
let argc = len(args);
51+
if argc < 1:
52+
return newTypeError newPyStr(
53+
fmt"descriptor '{$?self}' of '{self.truncedTypeName}' " &
54+
"object needs an argument"
55+
)
56+
let owner = args[0]
57+
let bound = tpMagic(MethodDescr, get)(self, owner)
58+
#let bound = method_get(self, owner)
59+
retIfExc bound
60+
tpMagic(NimFunc, call)(bound, args.toOpenArray(1, args.high), kwargs)
61+
62+
methodMacroTmpl(ClassMethodDescr)
63+
implClassMethodDescrMagic call:
64+
##[Instances of classmethod_descriptor are unlikely to be called directly.
65+
For one, the analogous class "classmethod" (for Python classes) is not
66+
callable. Second, users are not likely to access a classmethod_descriptor
67+
directly, since it means pulling it from the class __dict__.
68+
69+
This is just an excuse to say that this doesn't need to be optimized:
70+
we implement this simply by calling __get__ and then calling the result.]##
71+
let argc = len(args);
72+
if argc < 1:
73+
return newTypeError newPyStr(
74+
fmt"descriptor '{$?self}' of '{self.truncedTypeName}' " &
75+
"object needs an argument"
76+
)
77+
#let owner = args[0]
78+
let bound = classmethod_get(self, nil, self.dType)
79+
retIfExc bound
80+
tpMagic(NimFunc, call)(bound, args#.toOpenArray(1, args.high)
81+
, kwargs)
82+
83+

Objects/moduleobject.nim

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ proc init_dict(modu: PyModuleObject, md_dict: PyDictObject, name: PyStrObject) =
4343
md_dict[pyDUId spec] = pyNone
4444

4545
# `_add_methods_to_object`
46-
for name, meth in modu.pyType.bltinMethods:
46+
for name, (meth, _) in modu.pyType.bltinMethods:
4747
let namePyStr = newPyAscii(name)
4848
md_dict[namePyStr] = newPyNimFunc(meth, namePyStr, modu)
4949

Objects/pyobject.nim

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -114,11 +114,14 @@ macro tpGetter*(tp, methodName: untyped): untyped =
114114
macro tpSetter*(tp, methodName: untyped): untyped =
115115
ident(methodName.strVal.toLowerAscii & "Py" & tp.strVal & "ObjectSetter")
116116

117-
proc registerBltinMethod*(t: PyTypeObject, name: string, fun: BltinMethod) =
117+
proc registerBltinMethod*(t: PyTypeObject, name: string, fun: BltinMethodDef) =
118118
if t.bltinMethods.hasKey(name):
119119
unreachable(fmt"Method {name} is registered twice for type {t.name}")
120120
t.bltinMethods[name] = fun
121121

122+
proc registerBltinMethod*(t: PyTypeObject, name: string, fun: BltinMethod) =
123+
registerBltinMethod(t, name, (fun, false))
124+
122125
template genProperty*(T; pyname: string; nname; getter, setter){.dirty.} =
123126
bind `[]=`, tpGetter, tpSetter
124127
`impl T Getter` nname: getter
@@ -503,13 +506,19 @@ proc implMethod*(prototype, ObjectType, pragmas, body: NimNode, kind: MethodKind
503506
# add pragmas, the last to add is the first to execute
504507

505508
# builtin function has no `self` to cast
506-
var selfCast = params != bltinFuncParams
509+
var
510+
selfCast = params != bltinFuncParams
511+
classmethod = false
507512
# custom pragms
508513
for p in pragmas:
509514
if p.eqIdent"noSelfCast":
510515
# no castSelf pragma
511516
selfCast = false
512517
continue
518+
if p.eqIdent"classmethod":
519+
selfCast = false
520+
classmethod = true
521+
continue
513522
procNode.addPragma(p)
514523

515524
if selfCast:
@@ -544,7 +553,8 @@ proc implMethod*(prototype, ObjectType, pragmas, body: NimNode, kind: MethodKind
544553
newIdentNode("registerBltinMethod")
545554
),
546555
newLit($methodName),
547-
name
556+
quote do:
557+
(`name`, `classmethod`)
548558
)
549559
of MethodKind.Magic:
550560
result.add newAssignment(

Objects/pyobjectBase.nim

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,7 @@ type
195195
# the values are set in typeobject.nim when the type is ready
196196
dict*: PyObject
197197

198+
BltinMethodDef* = tuple[meth: BltinMethod, classmethod: bool] ## unstable
198199
PyTypeObject* = ref object of PyObjectWithDict
199200
#mro: PyObject ## PyTupleObject
200201
name*: string
@@ -204,7 +205,7 @@ type
204205
kind*: PyTypeToken
205206
members*: RtArray[PyMemberDef]
206207
magicMethods*: MagicMethods
207-
bltinMethods*: Table[string, BltinMethod]
208+
bltinMethods*: Table[string, BltinMethodDef]
208209
getsetDescr*: Table[string, (UnaryMethod, BinaryMethod)]
209210
tp_dealloc*: destructor
210211
tp_alloc*: proc (self: PyTypeObject, nitems: int): PyObject{.pyCFuncPragma.} ## XXX: currently must not return exception
@@ -331,7 +332,8 @@ proc newPyTypePrivate[T: PyObject](name: string): PyTypeObject =
331332
when defined(js):
332333
result.giveId
333334
result.name = name
334-
result.bltinMethods = initTable[string, BltinMethod]()
335+
# Starting from Nim v0.20, tables are initialized by default and it is not necessary to call this function explicitly.
336+
#result.bltinMethods = initTable[string, BltinMethod]()
335337
result.getsetDescr = initTable[string, (UnaryMethod, BinaryMethod)]()
336338
bltinTypes.add(result)
337339
result.tp_basicsize = sizeof T

Objects/typeobject/type_ready.nim

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -287,9 +287,11 @@ proc initTypeDict(tp: PyTypeObject) =
287287
d[namePyStr] = descr
288288

289289
# bltin methods
290-
for name, meth in tp.bltinMethods.pairs:
290+
for name, (meth, classmethod) in tp.bltinMethods.pairs:
291291
let namePyStr = newPyAscii(name)
292-
d[namePyStr] = newPyMethodDescr(tp, meth, namePyStr)
292+
d[namePyStr] =
293+
if classmethod: newPyClassMethodDescr(tp, meth, namePyStr)
294+
else: newPyMethodDescr(tp, meth, namePyStr)
293295

294296
tp.dict = d
295297

0 commit comments

Comments
 (0)