def binary_op(self, lreg: Value, rreg: Value, expr_op: str, line: int) -> Value: # special case tuple comparison here so that nested tuples can be supported if (isinstance(lreg.type, RTuple) and isinstance(rreg.type, RTuple) and expr_op in ('==', '!=')): return self.compare_tuples(lreg, rreg, expr_op, line) # Special case == and != when we can resolve the method call statically. value = None if expr_op in ('==', '!='): value = self.translate_eq_cmp(lreg, rreg, expr_op, line) if value is not None: return value # Special case 'is' and 'is not' if expr_op in ('is', 'is not'): return self.translate_is_op(lreg, rreg, expr_op, line) if (is_str_rprimitive(lreg.type) and is_str_rprimitive(rreg.type) and expr_op in ('==', '!=')): return self.compare_strings(lreg, rreg, expr_op, line) if is_tagged(lreg.type) and is_tagged( rreg.type) and expr_op in int_comparison_op_mapping: return self.compare_tagged(lreg, rreg, expr_op, line) call_c_ops_candidates = c_binary_ops.get(expr_op, []) target = self.matching_call_c(call_c_ops_candidates, [lreg, rreg], line) assert target, 'Unsupported binary operation: %s' % expr_op return target
def convert_expr(builder: IRBuilder, format_ops: List[FormatOp], exprs: List[Expression], line: int) -> Optional[List[Value]]: """Convert expressions into string literals with the guidance of FormatOps.""" if len(format_ops) != len(exprs): return None converted = [] for x, format_op in zip(exprs, format_ops): node_type = builder.node_type(x) if format_op == FormatOp.STR: if is_str_rprimitive(node_type): var_str = builder.accept(x) elif is_int_rprimitive(node_type) or is_short_int_rprimitive( node_type): var_str = builder.call_c(int_to_str_op, [builder.accept(x)], line) else: var_str = builder.call_c(str_op, [builder.accept(x)], line) elif format_op == FormatOp.INT: if is_int_rprimitive(node_type) or is_short_int_rprimitive( node_type): var_str = builder.call_c(int_to_str_op, [builder.accept(x)], line) else: return None else: return None converted.append(var_str) return converted
def sequence_from_generator_preallocate_helper( builder: IRBuilder, gen: GeneratorExpr, empty_op_llbuilder: Callable[[Value, int], Value], set_item_op: CFunctionDescription) -> Optional[Value]: """Generate a new tuple or list from a simple generator expression. Currently we only optimize for simplest generator expression, which means that there is no condition list in the generator and only one original sequence with one index is allowed. e.g. (1) tuple(f(x) for x in a_list/a_tuple) (2) list(f(x) for x in a_list/a_tuple) (3) [f(x) for x in a_list/a_tuple] RTuple as an original sequence is not supported yet. Args: empty_op_llbuilder: A function that can generate an empty sequence op when passed in length. See `new_list_op_with_length` and `new_tuple_op_with_length` for detailed implementation. set_item_op: A primitive that can modify an arbitrary position of a sequence. The op should have three arguments: - Self - Target position - New Value See `new_list_set_item_op` and `new_tuple_set_item_op` for detailed implementation. """ if len(gen.sequences) == 1 and len(gen.indices) == 1 and len( gen.condlists[0]) == 0: rtype = builder.node_type(gen.sequences[0]) if (is_list_rprimitive(rtype) or is_tuple_rprimitive(rtype) or is_str_rprimitive(rtype)): sequence = builder.accept(gen.sequences[0]) length = builder.builder.builtin_len(sequence, gen.line, use_pyssize_t=True) target_op = empty_op_llbuilder(length, gen.line) def set_item(item_index: Value) -> None: e = builder.accept(gen.left_expr) builder.call_c(set_item_op, [target_op, item_index, e], gen.line) for_loop_helper_with_index(builder, gen.indices[0], gen.sequences[0], sequence, set_item, gen.line) return target_op return None
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
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