Skip to content

Commit 43d2a14

Browse files
author
Christopher Doris
committed
tweak pyconvert to be safer to overload
1 parent 1a1e2a0 commit 43d2a14

File tree

1 file changed

+23
-12
lines changed

1 file changed

+23
-12
lines changed

src/convert.jl

Lines changed: 23 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -47,12 +47,6 @@ It must return one of:
4747
The target type `S` is never a union or the empty type, i.e. it is always a data type or
4848
union-all.
4949
50-
**Important.** To enable an optimization, the *Julia* object `x` may be invalidated after
51-
the call to `func`, which means that the returned `ans` must not contain a reference to `x`.
52-
Therefore if `ans` is a wrapper type (such as `PyList`) you may need to create a new
53-
Julia object around the same Python object by calling `Py(x)` and wrap that. It is
54-
recommended that wrapper types do this automatically in their inner constructor.
55-
5650
### Priority
5751
5852
Most rules should have priority `PYCONVERT_PRIORITY_NORMAL` (the default) which is for any
@@ -66,6 +60,17 @@ and not to a `Vector`. There should not be more than one canonical conversion ru
6660
given Python type.
6761
6862
Other priorities are reserved for internal use.
63+
64+
### Optional optimization (for experts)
65+
66+
If `func(S, x::Py)` makes a successful conversion, it may call `pydel!(x)` if safe to do so.
67+
This means that the returned answer must not reference `x` in any way.
68+
69+
If you are returning a wrapper type, such as `PyList(x)`, then the object `x` should be
70+
duplicated first, as in `Py(x)`. It is recommended to force this in the inner constructor
71+
of the wrapper type.
72+
73+
If the conversion is not successful, it **must not** call `pydel!(x)`.
6974
"""
7075
function pyconvert_add_rule(pytypename::String, type::Type, func::Function, priority::PyConvertPriority=PYCONVERT_PRIORITY_NORMAL)
7176
@nospecialize type func
@@ -278,14 +283,16 @@ function pyconvert_rule_fast(::Type{T}, x::Py) where {T}
278283
pyconvert_unconverted()
279284
end
280285

281-
pytryconvert(::Type{T}, x) where {T} = @autopy x begin
286+
function pytryconvert(::Type{T}, x_) where {T}
287+
# Copy the input
288+
x = Py(x_)
282289
# We can optimize the conversion for some types by overloading pytryconvert_fast.
283290
# It MUST give the same results as via the slower route using rules.
284-
ans = pyconvert_rule_fast(T, getpy(x_)) :: pyconvert_returntype(T)
291+
ans = pyconvert_rule_fast(T, x) :: pyconvert_returntype(T)
285292
pyconvert_isunconverted(ans) || return ans
286293
# get rules from the cache
287294
# TODO: we should hold weak references and clear the cache if types get deleted
288-
tptr = C.Py_Type(getptr(x_))
295+
tptr = C.Py_Type(getptr(x))
289296
trules = get!(Dict{Type, Vector{PyConvertRule}}, PYCONVERT_RULES_CACHE, tptr)
290297
if !haskey(trules, T)
291298
t = pynew(incref(tptr))
@@ -295,9 +302,11 @@ pytryconvert(::Type{T}, x) where {T} = @autopy x begin
295302
rules = trules[T]
296303
# apply the rules
297304
for rule in rules
298-
ans = rule(getpy(x_)) :: pyconvert_returntype(T)
305+
ans = rule(x) :: pyconvert_returntype(T)
299306
pyconvert_isunconverted(ans) || return ans
300307
end
308+
# if no rule succeeded, assume nothing references x
309+
pydel!(x)
301310
return pyconvert_unconverted()
302311
end
303312

@@ -310,11 +319,13 @@ On failure, evaluates to `onfail`, which defaults to `return pyconvert_unconvert
310319
"""
311320
macro pyconvert(T, x, onfail=:(return $pyconvert_unconverted()))
312321
quote
313-
ans = pytryconvert($(esc(T)), $(esc(x)))
322+
T = $(esc(T))
323+
x = $(esc(x))
324+
ans = pytryconvert(T, x)
314325
if pyconvert_isunconverted(ans)
315326
$(esc(onfail))
316327
else
317-
pyconvert_result($(esc(T)), ans)
328+
pyconvert_result(T, ans)
318329
end
319330
end
320331
end

0 commit comments

Comments
 (0)