Example #1
0
    def add_bool_branch(self, value: Value, true: BasicBlock,
                        false: BasicBlock) -> None:
        if is_runtime_subtype(value.type, int_rprimitive):
            zero = self.add(LoadInt(0))
            value = self.binary_op(value, zero, '!=', value.line)
        elif is_same_type(value.type, list_rprimitive):
            length = self.primitive_op(list_len_op, [value], value.line)
            zero = self.add(LoadInt(0))
            value = self.binary_op(length, zero, '!=', value.line)
        elif (isinstance(value.type, RInstance)
              and value.type.class_ir.is_ext_class
              and value.type.class_ir.has_method('__bool__')):
            # Directly call the __bool__ method on classes that have it.
            value = self.gen_method_call(value, '__bool__', [],
                                         bool_rprimitive, value.line)
        else:
            value_type = optional_value_type(value.type)
            if value_type is not None:
                is_none = self.binary_op(value, self.none_object(), 'is not',
                                         value.line)
                branch = Branch(is_none, true, false, Branch.BOOL_EXPR)
                self.add(branch)
                always_truthy = False
                if isinstance(value_type, RInstance):
                    # check whether X.__bool__ is always just the default (object.__bool__)
                    if (not value_type.class_ir.has_method('__bool__') and
                            value_type.class_ir.is_method_final('__bool__')):
                        always_truthy = True

                if not always_truthy:
                    # Optional[X] where X may be falsey and requires a check
                    branch.true = BasicBlock()
                    self.activate_block(branch.true)
                    # unbox_or_cast instead of coerce because we want the
                    # type to change even if it is a subtype.
                    remaining = self.unbox_or_cast(value, value_type,
                                                   value.line)
                    self.add_bool_branch(remaining, true, false)
                return
            elif not is_same_type(value.type, bool_rprimitive):
                value = self.primitive_op(bool_op, [value], value.line)
        self.add(Branch(value, true, false, Branch.BOOL_EXPR))
Example #2
0
    def emit_cast(self,
                  src: str,
                  dest: str,
                  typ: RType,
                  declare_dest: bool = False,
                  custom_message: Optional[str] = None,
                  optional: bool = False,
                  src_type: Optional[RType] = None,
                  likely: bool = True) -> None:
        """Emit code for casting a value of given type.

        Somewhat strangely, this supports unboxed types but only
        operates on boxed versions.  This is necessary to properly
        handle types such as Optional[int] in compatibility glue.

        Assign NULL (error value) to dest if the value has an incompatible type.

        Always copy/steal the reference in src.

        Args:
            src: Name of source C variable
            dest: Name of target C variable
            typ: Type of value
            declare_dest: If True, also declare the variable 'dest'
            likely: If the cast is likely to succeed (can be False for unions)
        """
        if custom_message is not None:
            err = custom_message
        else:
            err = 'CPy_TypeError("{}", {});'.format(self.pretty_name(typ), src)

        # Special case casting *from* optional
        if src_type and is_optional_type(
                src_type) and not is_object_rprimitive(typ):
            value_type = optional_value_type(src_type)
            assert value_type is not None
            if is_same_type(value_type, typ):
                if declare_dest:
                    self.emit_line('PyObject *{};'.format(dest))
                check = '({} != Py_None)'
                if likely:
                    check = '(likely{})'.format(check)
                self.emit_arg_check(src, dest, typ, check.format(src),
                                    optional)
                self.emit_lines('    {} = {};'.format(dest, src), 'else {',
                                err, '{} = NULL;'.format(dest), '}')
                return

        # TODO: Verify refcount handling.
        if (is_list_rprimitive(typ) or is_dict_rprimitive(typ)
                or is_set_rprimitive(typ) or is_float_rprimitive(typ)
                or is_str_rprimitive(typ) or is_int_rprimitive(typ)
                or is_bool_rprimitive(typ)):
            if declare_dest:
                self.emit_line('PyObject *{};'.format(dest))
            if is_list_rprimitive(typ):
                prefix = 'PyList'
            elif is_dict_rprimitive(typ):
                prefix = 'PyDict'
            elif is_set_rprimitive(typ):
                prefix = 'PySet'
            elif is_float_rprimitive(typ):
                prefix = 'CPyFloat'
            elif is_str_rprimitive(typ):
                prefix = 'PyUnicode'
            elif is_int_rprimitive(typ):
                prefix = 'PyLong'
            elif is_bool_rprimitive(typ) or is_bit_rprimitive(typ):
                prefix = 'PyBool'
            else:
                assert False, 'unexpected primitive type'
            check = '({}_Check({}))'
            if likely:
                check = '(likely{})'.format(check)
            self.emit_arg_check(src, dest, typ, check.format(prefix, src),
                                optional)
            self.emit_lines('    {} = {};'.format(dest, src), 'else {', err,
                            '{} = NULL;'.format(dest), '}')
        elif is_tuple_rprimitive(typ):
            if declare_dest:
                self.emit_line('{} {};'.format(self.ctype(typ), dest))
            check = '(PyTuple_Check({}))'
            if likely:
                check = '(likely{})'.format(check)
            self.emit_arg_check(src, dest, typ, check.format(src), optional)
            self.emit_lines('    {} = {};'.format(dest, src), 'else {', err,
                            '{} = NULL;'.format(dest), '}')
        elif isinstance(typ, RInstance):
            if declare_dest:
                self.emit_line('PyObject *{};'.format(dest))
            concrete = all_concrete_classes(typ.class_ir)
            # If there are too many concrete subclasses or we can't find any
            # (meaning the code ought to be dead or we aren't doing global opts),
            # fall back to a normal typecheck.
            # Otherwise check all the subclasses.
            if not concrete or len(
                    concrete) > FAST_ISINSTANCE_MAX_SUBCLASSES + 1:
                check = '(PyObject_TypeCheck({}, {}))'.format(
                    src, self.type_struct_name(typ.class_ir))
            else:
                full_str = '(Py_TYPE({src}) == {targets[0]})'
                for i in range(1, len(concrete)):
                    full_str += ' || (Py_TYPE({src}) == {targets[%d]})' % i
                if len(concrete) > 1:
                    full_str = '(%s)' % full_str
                check = full_str.format(
                    src=src,
                    targets=[self.type_struct_name(ir) for ir in concrete])
            if likely:
                check = '(likely{})'.format(check)
            self.emit_arg_check(src, dest, typ, check, optional)
            self.emit_lines('    {} = {};'.format(dest, src), 'else {', err,
                            '{} = NULL;'.format(dest), '}')
        elif is_none_rprimitive(typ):
            if declare_dest:
                self.emit_line('PyObject *{};'.format(dest))
            check = '({} == Py_None)'
            if likely:
                check = '(likely{})'.format(check)
            self.emit_arg_check(src, dest, typ, check.format(src), optional)
            self.emit_lines('    {} = {};'.format(dest, src), 'else {', err,
                            '{} = NULL;'.format(dest), '}')
        elif is_object_rprimitive(typ):
            if declare_dest:
                self.emit_line('PyObject *{};'.format(dest))
            self.emit_arg_check(src, dest, typ, '', optional)
            self.emit_line('{} = {};'.format(dest, src))
            if optional:
                self.emit_line('}')
        elif isinstance(typ, RUnion):
            self.emit_union_cast(src, dest, typ, declare_dest, err, optional,
                                 src_type)
        elif isinstance(typ, RTuple):
            assert not optional
            self.emit_tuple_cast(src, dest, typ, declare_dest, err, src_type)
        else:
            assert False, 'Cast not implemented: %s' % typ
Example #3
0
 def pretty_name(self, typ: RType) -> str:
     value_type = optional_value_type(typ)
     if value_type is not None:
         return '%s or None' % self.pretty_name(value_type)
     return str(typ)
Example #4
0
    def emit_cast(self,
                  src: str,
                  dest: str,
                  typ: RType,
                  *,
                  declare_dest: bool = False,
                  error: Optional[ErrorHandler] = None,
                  raise_exception: bool = True,
                  optional: bool = False,
                  src_type: Optional[RType] = None,
                  likely: bool = True) -> None:
        """Emit code for casting a value of given type.

        Somewhat strangely, this supports unboxed types but only
        operates on boxed versions.  This is necessary to properly
        handle types such as Optional[int] in compatibility glue.

        By default, assign NULL (error value) to dest if the value has
        an incompatible type and raise TypeError. These can be customized
        using 'error' and 'raise_exception'.

        Always copy/steal the reference in 'src'.

        Args:
            src: Name of source C variable
            dest: Name of target C variable
            typ: Type of value
            declare_dest: If True, also declare the variable 'dest'
            error: What happens on error
            raise_exception: If True, also raise TypeError on failure
            likely: If the cast is likely to succeed (can be False for unions)
        """
        error = error or AssignHandler()
        if isinstance(error, AssignHandler):
            handle_error = '%s = NULL;' % dest
        elif isinstance(error, GotoHandler):
            handle_error = 'goto %s;' % error.label
        else:
            assert isinstance(error, ReturnHandler)
            handle_error = 'return %s;' % error.value
        if raise_exception:
            raise_exc = f'CPy_TypeError("{self.pretty_name(typ)}", {src}); '
            err = raise_exc + handle_error
        else:
            err = handle_error

        # Special case casting *from* optional
        if src_type and is_optional_type(src_type) and not is_object_rprimitive(typ):
            value_type = optional_value_type(src_type)
            assert value_type is not None
            if is_same_type(value_type, typ):
                if declare_dest:
                    self.emit_line(f'PyObject *{dest};')
                check = '({} != Py_None)'
                if likely:
                    check = f'(likely{check})'
                self.emit_arg_check(src, dest, typ, check.format(src), optional)
                self.emit_lines(
                    f'    {dest} = {src};',
                    'else {',
                    err,
                    '}')
                return

        # TODO: Verify refcount handling.
        if (is_list_rprimitive(typ) or is_dict_rprimitive(typ) or is_set_rprimitive(typ)
                or is_str_rprimitive(typ) or is_range_rprimitive(typ) or is_float_rprimitive(typ)
                or is_int_rprimitive(typ) or is_bool_rprimitive(typ) or is_bit_rprimitive(typ)):
            if declare_dest:
                self.emit_line(f'PyObject *{dest};')
            if is_list_rprimitive(typ):
                prefix = 'PyList'
            elif is_dict_rprimitive(typ):
                prefix = 'PyDict'
            elif is_set_rprimitive(typ):
                prefix = 'PySet'
            elif is_str_rprimitive(typ):
                prefix = 'PyUnicode'
            elif is_range_rprimitive(typ):
                prefix = 'PyRange'
            elif is_float_rprimitive(typ):
                prefix = 'CPyFloat'
            elif is_int_rprimitive(typ):
                prefix = 'PyLong'
            elif is_bool_rprimitive(typ) or is_bit_rprimitive(typ):
                prefix = 'PyBool'
            else:
                assert False, 'unexpected primitive type'
            check = '({}_Check({}))'
            if likely:
                check = f'(likely{check})'
            self.emit_arg_check(src, dest, typ, check.format(prefix, src), optional)
            self.emit_lines(
                f'    {dest} = {src};',
                'else {',
                err,
                '}')
        elif is_bytes_rprimitive(typ):
            if declare_dest:
                self.emit_line(f'PyObject *{dest};')
            check = '(PyBytes_Check({}) || PyByteArray_Check({}))'
            if likely:
                check = f'(likely{check})'
            self.emit_arg_check(src, dest, typ, check.format(src, src), optional)
            self.emit_lines(
                f'    {dest} = {src};',
                'else {',
                err,
                '}')
        elif is_tuple_rprimitive(typ):
            if declare_dest:
                self.emit_line(f'{self.ctype(typ)} {dest};')
            check = '(PyTuple_Check({}))'
            if likely:
                check = f'(likely{check})'
            self.emit_arg_check(src, dest, typ,
                                check.format(src), optional)
            self.emit_lines(
                f'    {dest} = {src};',
                'else {',
                err,
                '}')
        elif isinstance(typ, RInstance):
            if declare_dest:
                self.emit_line(f'PyObject *{dest};')
            concrete = all_concrete_classes(typ.class_ir)
            # If there are too many concrete subclasses or we can't find any
            # (meaning the code ought to be dead or we aren't doing global opts),
            # fall back to a normal typecheck.
            # Otherwise check all the subclasses.
            if not concrete or len(concrete) > FAST_ISINSTANCE_MAX_SUBCLASSES + 1:
                check = '(PyObject_TypeCheck({}, {}))'.format(
                    src, self.type_struct_name(typ.class_ir))
            else:
                full_str = '(Py_TYPE({src}) == {targets[0]})'
                for i in range(1, len(concrete)):
                    full_str += ' || (Py_TYPE({src}) == {targets[%d]})' % i
                if len(concrete) > 1:
                    full_str = '(%s)' % full_str
                check = full_str.format(
                    src=src, targets=[self.type_struct_name(ir) for ir in concrete])
            if likely:
                check = f'(likely{check})'
            self.emit_arg_check(src, dest, typ, check, optional)
            self.emit_lines(
                f'    {dest} = {src};',
                'else {',
                err,
                '}')
        elif is_none_rprimitive(typ):
            if declare_dest:
                self.emit_line(f'PyObject *{dest};')
            check = '({} == Py_None)'
            if likely:
                check = f'(likely{check})'
            self.emit_arg_check(src, dest, typ,
                                check.format(src), optional)
            self.emit_lines(
                f'    {dest} = {src};',
                'else {',
                err,
                '}')
        elif is_object_rprimitive(typ):
            if declare_dest:
                self.emit_line(f'PyObject *{dest};')
            self.emit_arg_check(src, dest, typ, '', optional)
            self.emit_line(f'{dest} = {src};')
            if optional:
                self.emit_line('}')
        elif isinstance(typ, RUnion):
            self.emit_union_cast(src, dest, typ, declare_dest, err, optional, src_type)
        elif isinstance(typ, RTuple):
            assert not optional
            self.emit_tuple_cast(src, dest, typ, declare_dest, err, src_type)
        else:
            assert False, 'Cast not implemented: %s' % typ