@@ -63,39 +63,41 @@ Other priorities are reserved for internal use.
6363
6464### Optional optimization (for experts)
6565
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.
66+ The `func` is given the only reference to `x`. This means it may call `pydel!(x)` when done
67+ with `x` provided `x` is not referenced anywhere else, such as in the return value. See
68+ [`pydel!`](@ref).
6869
6970If you are returning a wrapper type, such as `PyList(x)`, then the object `x` should be
7071duplicated first, as in `Py(x)`. It is recommended to force this in the inner constructor
7172of the wrapper type.
72-
73- If the conversion is not successful, it **must not** call `pydel!(x)`.
7473"""
7574function pyconvert_add_rule (pytypename:: String , type:: Type , func:: Function , priority:: PyConvertPriority = PYCONVERT_PRIORITY_NORMAL)
7675 @nospecialize type func
7776 push! (get! (Vector{PyConvertRule}, PYCONVERT_RULES, pytypename), PyConvertRule (type, func, priority))
7877 return
7978end
8079
81- if false
82- # this scheme returns either the result or Unconverted()
80+ # Alternative ways to represent the result of conversion.
81+ if true
82+ # Returns either the result or Unconverted().
8383 struct Unconverted end
84- pyconvert_return (x) = x
85- pyconvert_unconverted () = Unconverted ()
86- pyconvert_returntype (:: Type{T} ) where {T} = Union{T,Unconverted}
87- pyconvert_isunconverted (r) = r === Unconverted ()
88- pyconvert_result (:: Type{T} , r) where {T} = r:: T
89- elseif true
90- # this scheme stores the result in PYCONVERT_RESULT
84+ @inline pyconvert_return (x) = x
85+ @inline pyconvert_unconverted () = Unconverted ()
86+ @inline pyconvert_returntype (:: Type{T} ) where {T} = Union{T,Unconverted}
87+ @inline pyconvert_isunconverted (r) = r === Unconverted ()
88+ @inline pyconvert_result (:: Type{T} , r) where {T} = r:: T
89+ elseif false
90+ # Stores the result in PYCONVERT_RESULT.
91+ # This is global state, probably best avoided.
9192 const PYCONVERT_RESULT = Ref {Any} (nothing )
92- pyconvert_return (x) = (PYCONVERT_RESULT[] = x; true )
93- pyconvert_unconverted () = false
94- pyconvert_returntype (:: Type{T} ) where {T} = Bool
95- pyconvert_isunconverted (r:: Bool ) = ! r
96- pyconvert_result (:: Type{T} , r:: Bool ) where {T} = (ans = PYCONVERT_RESULT[]:: T ; PYCONVERT_RESULT[] = nothing ; ans)
93+ @inline pyconvert_return (x) = (PYCONVERT_RESULT[] = x; true )
94+ @inline pyconvert_unconverted () = false
95+ @inline pyconvert_returntype (:: Type{T} ) where {T} = Bool
96+ @inline pyconvert_isunconverted (r:: Bool ) = ! r
97+ @inline pyconvert_result (:: Type{T} , r:: Bool ) where {T} = (ans = PYCONVERT_RESULT[]:: T ; PYCONVERT_RESULT[] = nothing ; ans)
9798else
98- # same as the previous scheme, but with special handling for bits types
99+ # Same as the previous scheme, but with special handling for bits types.
100+ # This is global state, probably best avoided.
99101 const PYCONVERT_RESULT = Ref {Any} (nothing )
100102 const PYCONVERT_RESULT_ISBITS = Ref {Bool} (false )
101103 const PYCONVERT_RESULT_TYPE = Ref {Type} (Union{})
112114 end
113115 return true
114116 end
115- pyconvert_unconverted () = false
116- pyconvert_returntype (:: Type{T} ) where {T} = Bool
117- pyconvert_isunconverted (r:: Bool ) = ! r
117+ @inline pyconvert_unconverted () = false
118+ @inline pyconvert_returntype (:: Type{T} ) where {T} = Bool
119+ @inline pyconvert_isunconverted (r:: Bool ) = ! r
118120 function pyconvert_result (:: Type{T} , r:: Bool ) where {T}
119121 if isbitstype (T)
120122 if sizeof (T) ≤ PYCONVERT_RESULT_BITSLEN
@@ -271,40 +273,63 @@ end
271273
272274pyconvert_fix (:: Type{T} , func) where {T} = x -> func (T, x)
273275
274- const PYCONVERT_RULES_CACHE = Dict {C.PyPtr, Dict{Type, Vector{Function}}} ()
276+ const PYCONVERT_RULES_CACHE = Dict {Type, Dict{C.PyPtr, Vector{Function}}} ()
277+
278+ @generated pyconvert_rules_cache (:: Type{T} ) where {T} = get! (Dict{C. PyPtr, Vector{Function}}, PYCONVERT_RULES_CACHE, T)
275279
276280function pyconvert_rule_fast (:: Type{T} , x:: Py ) where {T}
277281 if T isa Union
278282 a = pyconvert_rule_fast (T. a, x) :: pyconvert_returntype (T. a)
279283 pyconvert_isunconverted (a) || return a
280284 b = pyconvert_rule_fast (T. b, x) :: pyconvert_returntype (T. b)
281285 pyconvert_isunconverted (b) || return b
286+ elseif (T == Nothing) | (T == Missing)
287+ pyisnone (x) && return pyconvert_return (T ())
288+ elseif (T == Bool)
289+ pyisFalse (x) && return pyconvert_return (false )
290+ pyisTrue (x) && return pyconvert_return (true )
291+ elseif (T == Int) | (T == BigInt)
292+ pyisint (x) && return pyconvert_rule_int (T, Py (x))
293+ elseif (T == Float64)
294+ pyisfloat (x) && return pyconvert_return (T (pyfloat_asdouble (x)))
295+ elseif (T == ComplexF64)
296+ pyiscomplex (x) && return pyconvert_return (T (pycomplex_ascomplex (x)))
297+ elseif (T == String) | (T == Char) | (T == Symbol)
298+ pyisstr (x) && return pyconvert_rule_str (T, Py (x))
299+ elseif (T == Vector{UInt8}) | (T == Base. CodeUnits{UInt8,String})
300+ pyisbytes (x) && return pyconvert_rule_bytes (T, Py (x))
301+ elseif (T <: StepRange ) | (T <: UnitRange )
302+ pyisrange (x) && return pyconvert_rule_range (T, Py (x))
282303 end
283304 pyconvert_unconverted ()
284305end
285306
286307function pytryconvert (:: Type{T} , x_) where {T}
287308 # Copy the input
288309 x = Py (x_)
310+
289311 # We can optimize the conversion for some types by overloading pytryconvert_fast.
290312 # It MUST give the same results as via the slower route using rules.
291- ans = pyconvert_rule_fast (T, x) :: pyconvert_returntype (T)
292- pyconvert_isunconverted (ans) || return ans
313+ ans1 = pyconvert_rule_fast (T, x) :: pyconvert_returntype (T)
314+ pyconvert_isunconverted (ans1) || (pydel! (x); return ans1)
315+
293316 # get rules from the cache
294317 # TODO : we should hold weak references and clear the cache if types get deleted
295318 tptr = C. Py_Type (getptr (x))
296- trules = get! (Dict{Type, Vector{PyConvertRule}}, PYCONVERT_RULES_CACHE, tptr )
297- if ! haskey (trules, T)
319+ trules = pyconvert_rules_cache (T )
320+ rules = get! (trules, tptr) do
298321 t = pynew (incref (tptr))
299- trules[T] = pyconvert_get_rules (T, t)
322+ ans = pyconvert_get_rules (T, t):: Vector{Function}
300323 pydel! (t)
324+ ans
301325 end
302- rules = trules[T]
326+
303327 # apply the rules
304328 for rule in rules
305- ans = rule (x ) :: pyconvert_returntype (T)
306- pyconvert_isunconverted (ans ) || return ans
329+ ans2 = rule (Py (x) ) :: pyconvert_returntype (T)
330+ pyconvert_isunconverted (ans2 ) || ( pydel! (x); return ans2)
307331 end
332+
308333 # if no rule succeeded, assume nothing references x
309334 pydel! (x)
310335 return pyconvert_unconverted ()
0 commit comments