def check_instance_attribute_access_through_class(builder: IRBuilder, expr: MemberExpr, typ: Optional[ProperType]) -> None: """Report error if accessing an instance attribute through class object.""" if isinstance(expr.expr, RefExpr): node = expr.expr.node if isinstance(typ, TypeType) and isinstance(typ.item, Instance): # TODO: Handle other item types node = typ.item.type if isinstance(node, TypeInfo): class_ir = builder.mapper.type_to_ir.get(node) if class_ir is not None and class_ir.is_ext_class: sym = node.get(expr.name) if (sym is not None and isinstance(sym.node, Var) and not sym.node.is_classvar and not sym.node.is_final): builder.error( 'Cannot access instance attribute "{}" through class object'.format( expr.name), expr.line ) builder.note( '(Hint: Use "x: Final = ..." or "x: ClassVar = ..." to define ' 'a class attribute)', expr.line )
def transform_generator_expr(builder: IRBuilder, o: GeneratorExpr) -> Value: if any(o.is_async): builder.error('async comprehensions are unimplemented', o.line) builder.warning('Treating generator comprehension as list', o.line) return builder.call_c(iter_op, [translate_list_comprehension(builder, o)], o.line)
def transform_del_item(builder: IRBuilder, target: AssignmentTarget, line: int) -> None: if isinstance(target, AssignmentTargetIndex): builder.gen_method_call(target.base, '__delitem__', [target.index], result_type=None, line=line) elif isinstance(target, AssignmentTargetAttr): if isinstance(target.obj_type, RInstance): cl = target.obj_type.class_ir if not cl.is_deletable(target.attr): builder.error('"{}" cannot be deleted'.format(target.attr), line) builder.note( 'Using "__deletable__ = ' + '[\'<attr>\']" in the class body enables "del obj.<attr>"', line) key = builder.load_str(target.attr) builder.call_c(py_delattr_op, [target.obj, key], line) elif isinstance(target, AssignmentTargetRegister): # Delete a local by assigning an error value to it, which will # prompt the insertion of uninit checks. builder.add( Assign(target.register, builder.add(LoadErrorValue(target.type, undefines=True)))) elif isinstance(target, AssignmentTargetTuple): for subtarget in target.items: transform_del_item(builder, subtarget, line)
def transform_yield_expr(builder: IRBuilder, expr: YieldExpr) -> Value: if builder.fn_info.is_coroutine: builder.error('async generators are unimplemented', expr.line) if expr.expr: retval = builder.accept(expr.expr) else: retval = builder.builder.none() return emit_yield(builder, retval, expr.line)
def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: assert expr.node, "RefExpr not resolved" fullname = expr.node.fullname if fullname in builtin_names: typ, src = builtin_names[fullname] return builder.add(LoadAddress(typ, src, expr.line)) # special cases if fullname == 'builtins.None': return builder.none() if fullname == 'builtins.True': return builder.true() if fullname == 'builtins.False': return builder.false() if fullname in name_ref_ops: # Use special access op for this particular name. desc = name_ref_ops[fullname] assert desc.result_type is not None return builder.add(PrimitiveOp([], desc, expr.line)) if isinstance(expr.node, Var) and expr.node.is_final: value = builder.emit_load_final( expr.node, fullname, expr.name, builder.is_native_ref_expr(expr), builder.types[expr], expr.line, ) if value is not None: return value if isinstance(expr.node, MypyFile) and expr.node.fullname in builder.imports: return builder.load_module(expr.node.fullname) # If the expression is locally defined, then read the result from the corresponding # assignment target and return it. Otherwise if the expression is a global, load it from # the globals dictionary. # Except for imports, that currently always happens in the global namespace. if expr.kind == LDEF and not (isinstance(expr.node, Var) and expr.node.is_suppressed_import): # Try to detect and error when we hit the irritating mypy bug # where a local variable is cast to None. (#5423) if (isinstance(expr.node, Var) and is_none_rprimitive(builder.node_type(expr)) and expr.node.is_inferred): builder.error( "Local variable '{}' has inferred type None; add an annotation" .format(expr.node.name), expr.node.line) # TODO: Behavior currently only defined for Var and FuncDef node types. return builder.read(builder.get_assignment_target(expr), expr.line) return builder.load_global(expr)
def transform_with_stmt(builder: IRBuilder, o: WithStmt) -> None: if o.is_async: builder.error('async with is unimplemented', o.line) # Generate separate logic for each expr in it, left to right def generate(i: int) -> None: if i >= len(o.expr): builder.accept(o.body) else: transform_with(builder, o.expr[i], o.target[i], lambda: generate(i + 1), o.line) generate(0)
def transform_for_stmt(builder: IRBuilder, s: ForStmt) -> None: if s.is_async: builder.error('async for is unimplemented', s.line) def body() -> None: builder.accept(s.body) def else_block() -> None: assert s.else_body is not None builder.accept(s.else_body) for_loop_helper(builder, s.index, s.expr, body, else_block if s.else_body else None, s.line)
def transform_dictionary_comprehension(builder: IRBuilder, o: DictionaryComprehension) -> Value: if any(o.is_async): builder.error('async comprehensions are unimplemented', o.line) d = builder.call_c(dict_new_op, [], o.line) loop_params = list(zip(o.indices, o.sequences, o.condlists)) def gen_inner_stmts() -> None: k = builder.accept(o.key) v = builder.accept(o.value) builder.call_c(dict_set_item_op, [d, k, v], o.line) comprehension_helper(builder, loop_params, gen_inner_stmts, o.line) return d
def check_deletable_declaration(builder: IRBuilder, cl: ClassIR, line: int) -> None: for attr in cl.deletable: if attr not in cl.attributes: if not cl.has_attr(attr): builder.error('Attribute "{}" not defined'.format(attr), line) continue for base in cl.mro: if attr in base.property_types: builder.error('Cannot make property "{}" deletable'.format(attr), line) break else: _, base = cl.attr_details(attr) builder.error(('Attribute "{}" not defined in "{}" ' + '(defined in "{}")').format(attr, cl.name, base.name), line)
def make_for_loop_generator(builder: IRBuilder, index: Lvalue, expr: Expression, body_block: BasicBlock, loop_exit: BasicBlock, line: int, nested: bool = False) -> 'ForGenerator': """Return helper object for generating a for loop over an iterable. If "nested" is True, this is a nested iterator such as "e" in "enumerate(e)". """ rtyp = builder.node_type(expr) if is_sequence_rprimitive(rtyp): # Special case "for x in <list>". expr_reg = builder.accept(expr) target_type = builder.get_sequence_type(expr) for_list = ForSequence(builder, index, body_block, loop_exit, line, nested) for_list.init(expr_reg, target_type, reverse=False) return for_list if is_dict_rprimitive(rtyp): # Special case "for k in <dict>". expr_reg = builder.accept(expr) target_type = builder.get_dict_key_type(expr) for_dict = ForDictionaryKeys(builder, index, body_block, loop_exit, line, nested) for_dict.init(expr_reg, target_type) return for_dict if (isinstance(expr, CallExpr) and isinstance(expr.callee, RefExpr)): if (expr.callee.fullname == 'builtins.range' and (len(expr.args) <= 2 or (len(expr.args) == 3 and builder.extract_int(expr.args[2]) is not None)) and set(expr.arg_kinds) == {ARG_POS}): # Special case "for x in range(...)". # We support the 3 arg form but only for int literals, since it doesn't # seem worth the hassle of supporting dynamically determining which # direction of comparison to do. if len(expr.args) == 1: start_reg = builder.add(LoadInt(0)) end_reg = builder.accept(expr.args[0]) else: start_reg = builder.accept(expr.args[0]) end_reg = builder.accept(expr.args[1]) if len(expr.args) == 3: step = builder.extract_int(expr.args[2]) assert step is not None if step == 0: builder.error("range() step can't be zero", expr.args[2].line) else: step = 1 for_range = ForRange(builder, index, body_block, loop_exit, line, nested) for_range.init(start_reg, end_reg, step) return for_range elif (expr.callee.fullname == 'builtins.enumerate' and len(expr.args) == 1 and expr.arg_kinds == [ARG_POS] and isinstance(index, TupleExpr) and len(index.items) == 2): # Special case "for i, x in enumerate(y)". lvalue1 = index.items[0] lvalue2 = index.items[1] for_enumerate = ForEnumerate(builder, index, body_block, loop_exit, line, nested) for_enumerate.init(lvalue1, lvalue2, expr.args[0]) return for_enumerate elif (expr.callee.fullname == 'builtins.zip' and len(expr.args) >= 2 and set(expr.arg_kinds) == {ARG_POS} and isinstance(index, TupleExpr) and len(index.items) == len(expr.args)): # Special case "for x, y in zip(a, b)". for_zip = ForZip(builder, index, body_block, loop_exit, line, nested) for_zip.init(index.items, expr.args) return for_zip if (expr.callee.fullname == 'builtins.reversed' and len(expr.args) == 1 and expr.arg_kinds == [ARG_POS] and is_sequence_rprimitive(rtyp)): # Special case "for x in reversed(<list>)". expr_reg = builder.accept(expr.args[0]) target_type = builder.get_sequence_type(expr) for_list = ForSequence(builder, index, body_block, loop_exit, line, nested) for_list.init(expr_reg, target_type, reverse=True) return for_list if (isinstance(expr, CallExpr) and isinstance(expr.callee, MemberExpr) and not expr.args): # Special cases for dictionary iterator methods, like dict.items(). rtype = builder.node_type(expr.callee.expr) if (is_dict_rprimitive(rtype) and expr.callee.name in ('keys', 'values', 'items')): expr_reg = builder.accept(expr.callee.expr) for_dict_type = None # type: Optional[Type[ForGenerator]] if expr.callee.name == 'keys': target_type = builder.get_dict_key_type(expr.callee.expr) for_dict_type = ForDictionaryKeys elif expr.callee.name == 'values': target_type = builder.get_dict_value_type(expr.callee.expr) for_dict_type = ForDictionaryValues else: target_type = builder.get_dict_item_type(expr.callee.expr) for_dict_type = ForDictionaryItems for_dict_gen = for_dict_type(builder, index, body_block, loop_exit, line, nested) for_dict_gen.init(expr_reg, target_type) return for_dict_gen # Default to a generic for loop. expr_reg = builder.accept(expr) for_obj = ForIterable(builder, index, body_block, loop_exit, line, nested) item_type = builder._analyze_iterable_item_type(expr) item_rtype = builder.type_to_rtype(item_type) for_obj.init(expr_reg, item_rtype) return for_obj
def transform_set_comprehension(builder: IRBuilder, o: SetComprehension) -> Value: if any(o.generator.is_async): builder.error('async comprehensions are unimplemented', o.line) return translate_set_comprehension(builder, o.generator)
def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: if expr.node is None: builder.add(RaiseStandardError(RaiseStandardError.RUNTIME_ERROR, "mypyc internal error: should be unreachable", expr.line)) return builder.none() fullname = expr.node.fullname if fullname in builtin_names: typ, src = builtin_names[fullname] return builder.add(LoadAddress(typ, src, expr.line)) # special cases if fullname == 'builtins.None': return builder.none() if fullname == 'builtins.True': return builder.true() if fullname == 'builtins.False': return builder.false() if isinstance(expr.node, Var) and expr.node.is_final: value = builder.emit_load_final( expr.node, fullname, expr.name, builder.is_native_ref_expr(expr), builder.types[expr], expr.line, ) if value is not None: return value if isinstance(expr.node, MypyFile) and expr.node.fullname in builder.imports: return builder.load_module(expr.node.fullname) # If the expression is locally defined, then read the result from the corresponding # assignment target and return it. Otherwise if the expression is a global, load it from # the globals dictionary. # Except for imports, that currently always happens in the global namespace. if expr.kind == LDEF and not (isinstance(expr.node, Var) and expr.node.is_suppressed_import): # Try to detect and error when we hit the irritating mypy bug # where a local variable is cast to None. (#5423) if (isinstance(expr.node, Var) and is_none_rprimitive(builder.node_type(expr)) and expr.node.is_inferred): builder.error( 'Local variable "{}" has inferred type None; add an annotation'.format( expr.node.name), expr.node.line) # TODO: Behavior currently only defined for Var, FuncDef and MypyFile node types. if isinstance(expr.node, MypyFile): # Load reference to a module imported inside function from # the modules dictionary. It would be closer to Python # semantics to access modules imported inside functions # via local variables, but this is tricky since the mypy # AST doesn't include a Var node for the module. We # instead load the module separately on each access. mod_dict = builder.call_c(get_module_dict_op, [], expr.line) obj = builder.call_c(dict_get_item_op, [mod_dict, builder.load_str(expr.node.fullname)], expr.line) return obj else: return builder.read(builder.get_assignment_target(expr), expr.line) return builder.load_global(expr)
def transform_class_def(builder: IRBuilder, cdef: ClassDef) -> None: """Create IR for a class definition. This can generate both extension (native) and non-extension classes. These are generated in very different ways. In the latter case we construct a Python type object at runtime by doing the equivalent of "type(name, bases, dict)" in IR. Extension classes are defined via C structs that are generated later in mypyc.codegen.emitclass. This is the main entry point to this module. """ ir = builder.mapper.type_to_ir[cdef.info] # We do this check here because the base field of parent # classes aren't necessarily populated yet at # prepare_class_def time. if any(ir.base_mro[i].base != ir. base_mro[i + 1] for i in range(len(ir.base_mro) - 1)): builder.error("Non-trait MRO must be linear", cdef.line) if ir.allow_interpreted_subclasses: for parent in ir.mro: if not parent.allow_interpreted_subclasses: builder.error( 'Base class "{}" does not allow interpreted subclasses'.format( parent.fullname), cdef.line) # Currently, we only create non-extension classes for classes that are # decorated or inherit from Enum. Classes decorated with @trait do not # apply here, and are handled in a different way. if ir.is_ext_class: # If the class is not decorated, generate an extension class for it. type_obj = allocate_class(builder, cdef) # type: Optional[Value] non_ext = None # type: Optional[NonExtClassInfo] dataclass_non_ext = dataclass_non_ext_info(builder, cdef) else: non_ext_bases = populate_non_ext_bases(builder, cdef) non_ext_metaclass = find_non_ext_metaclass(builder, cdef, non_ext_bases) non_ext_dict = setup_non_ext_dict(builder, cdef, non_ext_metaclass, non_ext_bases) # We populate __annotations__ for non-extension classes # because dataclasses uses it to determine which attributes to compute on. # TODO: Maybe generate more precise types for annotations non_ext_anns = builder.call_c(dict_new_op, [], cdef.line) non_ext = NonExtClassInfo(non_ext_dict, non_ext_bases, non_ext_anns, non_ext_metaclass) dataclass_non_ext = None type_obj = None attrs_to_cache = [] # type: List[Tuple[Lvalue, RType]] for stmt in cdef.defs.body: if isinstance(stmt, OverloadedFuncDef) and stmt.is_property: if not ir.is_ext_class: # properties with both getters and setters in non_extension # classes not supported builder.error("Property setters not supported in non-extension classes", stmt.line) for item in stmt.items: with builder.catch_errors(stmt.line): transform_method(builder, cdef, non_ext, get_func_def(item)) elif isinstance(stmt, (FuncDef, Decorator, OverloadedFuncDef)): # Ignore plugin generated methods (since they have no # bodies to compile and will need to have the bodies # provided by some other mechanism.) if cdef.info.names[stmt.name].plugin_generated: continue with builder.catch_errors(stmt.line): transform_method(builder, cdef, non_ext, get_func_def(stmt)) elif isinstance(stmt, PassStmt): continue elif isinstance(stmt, AssignmentStmt): if len(stmt.lvalues) != 1: builder.error("Multiple assignment in class bodies not supported", stmt.line) continue lvalue = stmt.lvalues[0] if not isinstance(lvalue, NameExpr): builder.error("Only assignment to variables is supported in class bodies", stmt.line) continue # We want to collect class variables in a dictionary for both real # non-extension classes and fake dataclass ones. var_non_ext = non_ext or dataclass_non_ext if var_non_ext: add_non_ext_class_attr(builder, var_non_ext, lvalue, stmt, cdef, attrs_to_cache) if non_ext: continue # Variable declaration with no body if isinstance(stmt.rvalue, TempNode): continue # Only treat marked class variables as class variables. if not (is_class_var(lvalue) or stmt.is_final_def): continue typ = builder.load_native_type_object(cdef.fullname) value = builder.accept(stmt.rvalue) builder.call_c( py_setattr_op, [typ, builder.load_str(lvalue.name), value], stmt.line) if builder.non_function_scope() and stmt.is_final_def: builder.init_final_static(lvalue, value, cdef.name) elif isinstance(stmt, ExpressionStmt) and isinstance(stmt.expr, StrExpr): # Docstring. Ignore pass else: builder.error("Unsupported statement in class body", stmt.line) if not non_ext: # That is, an extension class generate_attr_defaults(builder, cdef) create_ne_from_eq(builder, cdef) if dataclass_non_ext: assert type_obj dataclass_finalize(builder, cdef, dataclass_non_ext, type_obj) else: # Dynamically create the class via the type constructor non_ext_class = load_non_ext_class(builder, ir, non_ext, cdef.line) non_ext_class = load_decorated_class(builder, cdef, non_ext_class) # Save the decorated class builder.add(InitStatic(non_ext_class, cdef.name, builder.module_name, NAMESPACE_TYPE)) # Add the non-extension class to the dict builder.call_c(dict_set_item_op, [ builder.load_globals_dict(), builder.load_str(cdef.name), non_ext_class ], cdef.line) # Cache any cachable class attributes cache_class_attrs(builder, attrs_to_cache, cdef)
def transform_class_def(builder: IRBuilder, cdef: ClassDef) -> None: """Create IR for a class definition. This can generate both extension (native) and non-extension classes. These are generated in very different ways. In the latter case we construct a Python type object at runtime by doing the equivalent of "type(name, bases, dict)" in IR. Extension classes are defined via C structs that are generated later in mypyc.codegen.emitclass. This is the main entry point to this module. """ ir = builder.mapper.type_to_ir[cdef.info] # We do this check here because the base field of parent # classes aren't necessarily populated yet at # prepare_class_def time. if any(ir.base_mro[i].base != ir.base_mro[i + 1] for i in range(len(ir.base_mro) - 1)): builder.error("Non-trait MRO must be linear", cdef.line) if ir.allow_interpreted_subclasses: for parent in ir.mro: if not parent.allow_interpreted_subclasses: builder.error( 'Base class "{}" does not allow interpreted subclasses'. format(parent.fullname), cdef.line) # Currently, we only create non-extension classes for classes that are # decorated or inherit from Enum. Classes decorated with @trait do not # apply here, and are handled in a different way. if ir.is_ext_class: cls_type = dataclass_type(cdef) if cls_type is None: cls_builder: ClassBuilder = ExtClassBuilder(builder, cdef) elif cls_type in ['dataclasses', 'attr-auto']: cls_builder = DataClassBuilder(builder, cdef) elif cls_type == 'attr': cls_builder = AttrsClassBuilder(builder, cdef) else: raise ValueError(cls_type) else: cls_builder = NonExtClassBuilder(builder, cdef) for stmt in cdef.defs.body: if isinstance(stmt, OverloadedFuncDef) and stmt.is_property: if isinstance(cls_builder, NonExtClassBuilder): # properties with both getters and setters in non_extension # classes not supported builder.error( "Property setters not supported in non-extension classes", stmt.line) for item in stmt.items: with builder.catch_errors(stmt.line): cls_builder.add_method(get_func_def(item)) elif isinstance(stmt, (FuncDef, Decorator, OverloadedFuncDef)): # Ignore plugin generated methods (since they have no # bodies to compile and will need to have the bodies # provided by some other mechanism.) if cdef.info.names[stmt.name].plugin_generated: continue with builder.catch_errors(stmt.line): cls_builder.add_method(get_func_def(stmt)) elif isinstance(stmt, PassStmt): continue elif isinstance(stmt, AssignmentStmt): if len(stmt.lvalues) != 1: builder.error( "Multiple assignment in class bodies not supported", stmt.line) continue lvalue = stmt.lvalues[0] if not isinstance(lvalue, NameExpr): builder.error( "Only assignment to variables is supported in class bodies", stmt.line) continue # We want to collect class variables in a dictionary for both real # non-extension classes and fake dataclass ones. cls_builder.add_attr(lvalue, stmt) elif isinstance(stmt, ExpressionStmt) and isinstance( stmt.expr, StrExpr): # Docstring. Ignore pass else: builder.error("Unsupported statement in class body", stmt.line) cls_builder.finalize(ir)