def generate_attr_defaults(builder: IRBuilder, cdef: ClassDef) -> None: """Generate an initialization method for default attr values (from class vars).""" cls = builder.mapper.type_to_ir[cdef.info] if cls.builtin_base: return # Pull out all assignments in classes in the mro so we can initialize them # TODO: Support nested statements default_assignments = [] for info in reversed(cdef.info.mro): if info not in builder.mapper.type_to_ir: continue for stmt in info.defn.defs.body: if (isinstance(stmt, AssignmentStmt) and isinstance(stmt.lvalues[0], NameExpr) and not is_class_var(stmt.lvalues[0]) and not isinstance(stmt.rvalue, TempNode)): name = stmt.lvalues[0].name if name == '__slots__': continue if name == '__deletable__': check_deletable_declaration(builder, cls, stmt.line) continue # Skip type annotated assignments in dataclasses if is_dataclass(cdef) and stmt.type: continue default_assignments.append(stmt) if not default_assignments: return builder.enter_method(cls, '__mypyc_defaults_setup', bool_rprimitive) self_var = builder.self() for stmt in default_assignments: lvalue = stmt.lvalues[0] assert isinstance(lvalue, NameExpr) if not stmt.is_final_def and not is_constant(stmt.rvalue): builder.warning('Unsupported default attribute value', stmt.rvalue.line) # If the attribute is initialized to None and type isn't optional, # don't initialize it to anything. attr_type = cls.attr_type(lvalue.name) if isinstance(stmt.rvalue, RefExpr) and stmt.rvalue.fullname == 'builtins.None': if (not is_optional_type(attr_type) and not is_object_rprimitive(attr_type) and not is_none_rprimitive(attr_type)): continue val = builder.coerce(builder.accept(stmt.rvalue), attr_type, stmt.line) builder.add(SetAttr(self_var, lvalue.name, val, -1)) builder.add(Return(builder.true())) builder.leave_method()
def find_attr_initializers( builder: IRBuilder, cdef: ClassDef, skip: Optional[Callable[[str, AssignmentStmt], bool]] = None, ) -> Tuple[Set[str], List[AssignmentStmt]]: """Find initializers of attributes in a class body. If provided, the skip arg should be a callable which will return whether to skip generating a default for an attribute. It will be passed the name of the attribute and the corresponding AssignmentStmt. """ cls = builder.mapper.type_to_ir[cdef.info] if cls.builtin_base: return set(), [] attrs_with_defaults = set() # Pull out all assignments in classes in the mro so we can initialize them # TODO: Support nested statements default_assignments = [] for info in reversed(cdef.info.mro): if info not in builder.mapper.type_to_ir: continue for stmt in info.defn.defs.body: if (isinstance(stmt, AssignmentStmt) and isinstance(stmt.lvalues[0], NameExpr) and not is_class_var(stmt.lvalues[0]) and not isinstance(stmt.rvalue, TempNode)): name = stmt.lvalues[0].name if name == '__slots__': continue if name == '__deletable__': check_deletable_declaration(builder, cls, stmt.line) continue if skip is not None and skip(name, stmt): continue attr_type = cls.attr_type(name) # If the attribute is initialized to None and type isn't optional, # doesn't initialize it to anything (special case for "# type:" comments). if isinstance( stmt.rvalue, RefExpr) and stmt.rvalue.fullname == 'builtins.None': if (not is_optional_type(attr_type) and not is_object_rprimitive(attr_type) and not is_none_rprimitive(attr_type)): continue attrs_with_defaults.add(name) default_assignments.append(stmt) return attrs_with_defaults, default_assignments
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 generate_attr_defaults(builder: IRBuilder, cdef: ClassDef) -> None: """Generate an initialization method for default attr values (from class vars).""" cls = builder.mapper.type_to_ir[cdef.info] if cls.builtin_base: return # Pull out all assignments in classes in the mro so we can initialize them # TODO: Support nested statements default_assignments = [] for info in reversed(cdef.info.mro): if info not in builder.mapper.type_to_ir: continue for stmt in info.defn.defs.body: if (isinstance(stmt, AssignmentStmt) and isinstance(stmt.lvalues[0], NameExpr) and not is_class_var(stmt.lvalues[0]) and not isinstance(stmt.rvalue, TempNode)): if stmt.lvalues[0].name == '__slots__': continue # Skip type annotated assignments in dataclasses if is_dataclass(cdef) and stmt.type: continue default_assignments.append(stmt) if not default_assignments: return builder.enter() builder.ret_types[-1] = bool_rprimitive rt_args = (RuntimeArg(SELF_NAME, RInstance(cls)), ) self_var = builder.read(add_self_to_env(builder.environment, cls), -1) for stmt in default_assignments: lvalue = stmt.lvalues[0] assert isinstance(lvalue, NameExpr) if not stmt.is_final_def and not is_constant(stmt.rvalue): builder.warning('Unsupported default attribute value', stmt.rvalue.line) # If the attribute is initialized to None and type isn't optional, # don't initialize it to anything. attr_type = cls.attr_type(lvalue.name) if isinstance(stmt.rvalue, RefExpr) and stmt.rvalue.fullname == 'builtins.None': if (not is_optional_type(attr_type) and not is_object_rprimitive(attr_type) and not is_none_rprimitive(attr_type)): continue val = builder.coerce(builder.accept(stmt.rvalue), attr_type, stmt.line) builder.add(SetAttr(self_var, lvalue.name, val, -1)) builder.add(Return(builder.true())) blocks, env, ret_type, _ = builder.leave() ir = FuncIR( FuncDecl('__mypyc_defaults_setup', cls.name, builder.module_name, FuncSignature(rt_args, ret_type)), blocks, env) builder.functions.append(ir) cls.methods[ir.name] = ir
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