Skip to content
Merged
3 changes: 3 additions & 0 deletions docs/source/reference/pysupported.rst
Original file line number Diff line number Diff line change
Expand Up @@ -933,6 +933,7 @@ The following built-in functions are supported:
* :func:`getattr`: the attribute must be a string literal and the return type
cannot be a function type (e.g. ``getattr(numpy, 'cos')`` is not supported as
it returns a function type).
* :func:`hasattr`
* :func:`hash` (see :ref:`pysupported-hashing` below)
* :class:`int`: only the one-argument form
* :func:`iter`: only the one-argument form
Expand All @@ -947,9 +948,11 @@ The following built-in functions are supported:
* :class:`range`: The only permitted use of range is as a callable function
(cannot pass range as an argument to a jitted function or return a range from
a jitted function).
* :func:`repr`
* :func:`round`
* :func:`sorted`: the ``key`` argument is not supported
* :func:`sum`
* :func:`str`
* :func:`type`: only the one-argument form, and only on some types
(e.g. numbers and named tuples)
* :func:`zip`
Expand Down
8 changes: 6 additions & 2 deletions numba/core/untyped_passes.py
Original file line number Diff line number Diff line change
Expand Up @@ -1480,6 +1480,8 @@ def run_pass(self, state):
typemap = state.typemap
flags = state.flags

accepted_functions = ('isinstance', 'hasattr')

if not hasattr(func_ir, '_definitions') \
and not flags.enable_ssa:
func_ir._definitions = build_definitions(func_ir.blocks)
Expand Down Expand Up @@ -1529,7 +1531,8 @@ def run_pass(self, state):
fn = guard(get_definition, func_ir, value.func.name)
if fn is None:
continue
if not (isinstance(fn, ir.Global) and fn.name == 'isinstance'): # noqa: E501
if not (isinstance(fn, ir.Global) and fn.name in
accepted_functions): # noqa: E501
Comment thread
sklam marked this conversation as resolved.
Resolved
continue

for arg in value.args:
Expand Down Expand Up @@ -1582,7 +1585,8 @@ def run_pass(self, state):
for blk in func_ir.blocks.values():
for asgn in blk.find_insts(ir.Assign):
if isinstance(asgn.value, (ir.Global, ir.FreeVar)):
if asgn.value.value is isinstance:
value = asgn.value.value
if value is isinstance or value is hasattr:
found = True
break
if found:
Expand Down
77 changes: 76 additions & 1 deletion numba/cpython/builtins.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
from numba.core.typeconv import Conversion
from numba.core.errors import (TypingError, LoweringError,
NumbaExperimentalFeatureWarning,
NumbaTypeError, RequireLiteralValue)
NumbaTypeError, RequireLiteralValue,
NumbaPerformanceWarning)
from numba.misc.special import literal_unroll
from numba.core.typing.asnumbatype import as_numba_type

Expand Down Expand Up @@ -928,3 +929,77 @@ def impl(obj, name, default):
return resolve_getattr(obj, name, default)
return impl


@intrinsic
def resolve_hasattr(tyctx, obj, name):
if not isinstance(name, types.StringLiteral):
raise RequireLiteralValue("argument 'name' must be a literal string")
lname = name.literal_value
fn = tyctx.resolve_getattr(obj, lname)
# Whilst technically the return type could be a types.bool_, the literal
# value is resolvable at typing time. Propagating this literal information
# into the type system allows the compiler to prune branches based on a
# hasattr predicate. As a result the signature is based on literals. This is
# "safe" because the overload requires a literal string so each will be a
# different variant of (obj, literal(name)) -> literal(bool).
if fn is None:
retty = types.literal(False)
else:
retty = types.literal(True)
sig = retty(obj, name)
def impl(cgctx, builder, sig, ll_args):
return cgutils.false_bit if fn is None else cgutils.true_bit
return sig, impl

# hasattr cannot be implemented as a getattr call and then catching
# AttributeError because Numba doesn't support catching anything other than
# "Exception", so lacks the specificity required. Instead this implementation
# tries to resolve the attribute via typing information and returns True/False
# based on that.
@overload(hasattr)
def ol_hasattr(obj, name):
def impl(obj, name):
return resolve_hasattr(obj, name)
return impl


def _get_py_repr_and_warn(obj):
# Gets the repr of an object at runtime whilst issuing a performance
# warning
from numba import typeof
msg = ("There is no '__repr__' specialisation defined for the type "
f"'{typeof(obj)}', as a result the Python interpreter has been "
"called to fetch a repr(), this has a performance penalty. To avoid "
"this penalty either define a '__repr__' for the type or remove the "
"call to str()/repr()!")
warnings.warn(NumbaPerformanceWarning(msg))
return repr(obj)


@overload(repr)
def ol_repr_generic(obj):
from numba import objmode
def impl(obj):
attr = '__repr__'
if hasattr(obj, attr) == True:
return getattr(obj, attr)()
else:
# There's no __str__ or __repr__ defined for this object, go via
# the interpreter.
pyrepr = ""
with objmode(pyrepr='unicode_type'):
pyrepr = _get_py_repr_and_warn(obj)
return pyrepr
return impl


@overload(str)
def ol_str_generic(object=''):
def impl(object=""):
attr = '__str__'
if hasattr(object, attr) == True:
return getattr(object, attr)()
else:
return repr(object)
return impl

26 changes: 13 additions & 13 deletions numba/cpython/charseq.py
Original file line number Diff line number Diff line change
Expand Up @@ -597,21 +597,21 @@ def tostr_impl(s):
return tostr_impl


@overload(str)
@overload_method(types.UnicodeCharSeq, "__str__")
def charseq_str(s):
if isinstance(s, types.UnicodeCharSeq):
get_code = _get_code_impl(s)
get_code = _get_code_impl(s)

def str_impl(s):
n = len(s)
kind = s._get_kind()
is_ascii = kind == 1 and s.isascii()
result = unicode._empty_string(kind, n, is_ascii)
for i in range(n):
code = get_code(s, i)
unicode._set_code_point(result, i, code)
return result
return str_impl
def str_impl(s):
n = len(s)
kind = s._get_kind()
is_ascii = kind == 1 and s.isascii()
result = unicode._empty_string(kind, n, is_ascii)
for i in range(n):
code = get_code(s, i)
unicode._set_code_point(result, i, code)
return result

return str_impl


@overload(bytes)
Expand Down
17 changes: 16 additions & 1 deletion numba/cpython/hashing.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,8 +47,23 @@

@overload(hash)
def hash_overload(obj):
objty = obj
err_msg = f"unhashable type: '{objty}'"
attempt_generic_msg = ("No __hash__ is defined for object of type "
f"'{objty}' and a generic hash() cannot be "
"performed as there is no suitable object "
"represention in Numba compiled code!")

def impl(obj):
return obj.__hash__()
if hasattr(obj, '__hash__'):
hash_func = getattr(obj, '__hash__')
hash_value = hash_func()
if hash_value is not None:
Comment thread
sklam marked this conversation as resolved.
Resolved
return hash_value
else:
raise TypeError(err_msg)
else:
raise TypeError(attempt_generic_msg)
return impl


Expand Down
80 changes: 45 additions & 35 deletions numba/cpython/unicode.py
Original file line number Diff line number Diff line change
Expand Up @@ -433,12 +433,6 @@ def _codepoint_is_ascii(ch):
# PUBLIC API


@overload(str)
def unicode_str(s):
if isinstance(s, types.UnicodeType):
return lambda s: s


@overload(len)
def unicode_len(s):
if isinstance(s, types.UnicodeType):
Expand Down Expand Up @@ -2391,39 +2385,55 @@ def impl(i):
return impl


@overload(str)
@overload_method(types.UnicodeType, "__str__")
def unicode_str(s):
return lambda s: s


@overload_method(types.UnicodeType, "__repr__")
def unicode_repr(s):
# Can't use f-string as the impl ends up calling str and then repr, which
# then recurses somewhere in imports.
return lambda s: "'" + s + "'"


@overload_method(types.Integer, "__str__")
def integer_str(n):
if isinstance(n, types.Integer):

ten = n(10)

def impl(n):
flag = False
if n < 0:
n = -n
flag = True
if n == 0:
return '0'
length = flag + 1 + int(np.floor(np.log10(n)))
kind = PY_UNICODE_1BYTE_KIND
char_width = _kind_to_byte_width(kind)
s = _malloc_string(kind, char_width, length, True)
if flag:
_set_code_point(s, 0, ord('-'))
idx = length - 1
while n > 0:
n, digit = divmod(n, ten)
c = ord('0') + digit
_set_code_point(s, idx, c)
idx -= 1
return s
return impl

ten = n(10)

def impl(n):
flag = False
if n < 0:
n = -n
flag = True
if n == 0:
return '0'
length = flag + 1 + int(np.floor(np.log10(n)))
kind = PY_UNICODE_1BYTE_KIND
char_width = _kind_to_byte_width(kind)
s = _malloc_string(kind, char_width, length, True)
if flag:
_set_code_point(s, 0, ord('-'))
idx = length - 1
while n > 0:
n, digit = divmod(n, ten)
c = ord('0') + digit
_set_code_point(s, idx, c)
idx -= 1
return s
return impl


@overload_method(types.Integer, "__repr__")
def integer_repr(n):
return lambda n: n.__str__()


@overload(str)
@overload_method(types.Boolean, "__repr__")
@overload_method(types.Boolean, "__str__")
def boolean_str(b):
if isinstance(b, types.Boolean):
return lambda b: "True" if b else "False"
return lambda b: 'True' if b else 'False'


# ------------------------------------------------------------------------------
Expand Down
8 changes: 8 additions & 0 deletions numba/np/arrayobj.py
Original file line number Diff line number Diff line change
Expand Up @@ -3026,6 +3026,14 @@ def array_is_impl(a, b):

return context.compile_internal(builder, array_is_impl, sig, args)

# ------------------------------------------------------------------------------
# Hash


@overload_method(types.Array, "__hash__")
def ol_array_hash(arr):
return lambda arr: None
Comment thread
sklam marked this conversation as resolved.
Resolved


# ------------------------------------------------------------------------------
# builtin `np.flat` implementation
Expand Down
35 changes: 34 additions & 1 deletion numba/tests/support.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@

import numpy as np

from numba import testing
from numba import testing, types
from numba.core import errors, typing, utils, config, cpu
from numba.core.compiler import (compile_extra, compile_isolated, Flags,
DEFAULT_FLAGS, CompilerBase,
Expand All @@ -41,6 +41,15 @@
from numba.np import numpy_support
from numba.pycc.platform import _external_compiler_ok
from numba.core.runtime import _nrt_python as _nrt
from numba.core.extending import (
overload_method,
typeof_impl,
register_model,
unbox,
NativeValue,
models,
)
from numba.core.datamodel.models import OpaqueModel


try:
Expand Down Expand Up @@ -613,6 +622,30 @@ def inner(self, *args, **kwargs):
else:
return wrapper

def make_dummy_type(self):
"""Use to generate a dummy type unique to this test. Returns a python
Dummy class and a corresponding Numba type DummyType."""

# Use test_id to make sure no collision is possible.
test_id = self.id()
DummyType = type('DummyTypeFor{}'.format(test_id), (types.Opaque,), {})

dummy_type = DummyType("my_dummy")
register_model(DummyType)(OpaqueModel)

class Dummy(object):
pass

@typeof_impl.register(Dummy)
def typeof_dummy(val, c):
return dummy_type

@unbox(DummyType)
def unbox_dummy(typ, obj, c):
return NativeValue(c.context.get_dummy_value())

return Dummy, DummyType


class SerialMixin(object):
"""Mixin to mark test for serial execution.
Expand Down
Loading