diff --git a/mypyc/codegen/emit.py b/mypyc/codegen/emit.py index bcaf0963af6f..c71e098489a2 100644 --- a/mypyc/codegen/emit.py +++ b/mypyc/codegen/emit.py @@ -1201,6 +1201,7 @@ def _emit_traceback( type_str: str = "", src: str = "", ) -> None: + assert traceback_entry[1] >= 0, "Traceback cannot have a negative line number" globals_static = self.static_name("globals", module_name) line = '%s("%s", "%s", %d, %s' % ( func, diff --git a/mypyc/codegen/emitfunc.py b/mypyc/codegen/emitfunc.py index ca97ee7bf92d..78390858ba76 100644 --- a/mypyc/codegen/emitfunc.py +++ b/mypyc/codegen/emitfunc.py @@ -921,6 +921,9 @@ def emit_traceback(self, op: Branch) -> None: def emit_attribute_error(self, op: Branch, class_name: str, attr: str) -> None: assert op.traceback_entry is not None + assert ( + op.traceback_entry[1] >= 0 + ), "AttributeError traceback cannot have a negative line number" globals_static = self.emitter.static_name("globals", self.module_name) self.emit_line( 'CPy_AttributeError("%s", "%s", "%s", "%s", %d, %s);' diff --git a/mypyc/ir/ops.py b/mypyc/ir/ops.py index fa6b6af7bcf3..a1b628933a3b 100644 --- a/mypyc/ir/ops.py +++ b/mypyc/ir/ops.py @@ -773,9 +773,10 @@ class PrimitiveOp(RegisterOp): """ def __init__(self, args: list[Value], desc: PrimitiveDescription, line: int = -1) -> None: + self.error_kind = desc.error_kind + super().__init__(line) self.args = args self.type = desc.return_type - self.error_kind = desc.error_kind self.desc = desc def sources(self) -> list[Value]: @@ -849,7 +850,8 @@ class LoadLiteral(RegisterOp): error_kind = ERR_NEVER is_borrowed = True - def __init__(self, value: LiteralValue, rtype: RType) -> None: + def __init__(self, value: LiteralValue, rtype: RType, line: int = -1) -> None: + super().__init__(line) self.value = value self.type = rtype @@ -1214,6 +1216,7 @@ class RaiseStandardError(RegisterOp): def __init__(self, class_name: str, value: str | Value | None, line: int) -> None: super().__init__(line) + assert line >= 0, "RaiseStandardError cannot have a negative line number" self.class_name = class_name self.value = value self.type = bool_rprimitive @@ -1800,7 +1803,8 @@ class KeepAlive(RegisterOp): error_kind = ERR_NEVER - def __init__(self, src: list[Value], *, steal: bool = False) -> None: + def __init__(self, src: list[Value], line: int = -1, *, steal: bool = False) -> None: + super().__init__(line) assert src self.src = src self.steal = steal diff --git a/mypyc/irbuild/ast_helpers.py b/mypyc/irbuild/ast_helpers.py index 3b0f50514594..8fff4b19b33d 100644 --- a/mypyc/irbuild/ast_helpers.py +++ b/mypyc/irbuild/ast_helpers.py @@ -90,12 +90,12 @@ def maybe_process_conditional_comparison( elif not is_fixed_width_rtype(rtype): right = self.coerce(right, ltype, e.line) reg = self.binary_op(left, right, op, e.line) - self.builder.flush_keep_alives() + self.builder.flush_keep_alives(e.line) self.add_bool_branch(reg, true, false) else: # "left op right" for two tagged integers reg = self.builder.binary_op(left, right, op, e.line) - self.flush_keep_alives() + self.flush_keep_alives(e.line) self.add_bool_branch(reg, true, false) return True diff --git a/mypyc/irbuild/builder.py b/mypyc/irbuild/builder.py index 5099f7053b92..c28cc38949cd 100644 --- a/mypyc/irbuild/builder.py +++ b/mypyc/irbuild/builder.py @@ -66,6 +66,7 @@ from mypyc.ir.ops import ( NAMESPACE_MODULE, NAMESPACE_TYPE_VAR, + NO_TRACEBACK_LINE_NO, Assign, BasicBlock, Branch, @@ -288,7 +289,7 @@ def accept(self, node: Statement | Expression, *, can_borrow: bool = False) -> V res = Register(self.node_type(node)) self.can_borrow = old_can_borrow if not can_borrow: - self.flush_keep_alives() + self.flush_keep_alives(node.line) return res else: try: @@ -297,8 +298,8 @@ def accept(self, node: Statement | Expression, *, can_borrow: bool = False) -> V pass return None - def flush_keep_alives(self) -> None: - self.builder.flush_keep_alives() + def flush_keep_alives(self, line: int) -> None: + self.builder.flush_keep_alives(line) # Pass through methods for the most common low-level builder ops, for convenience. @@ -320,23 +321,23 @@ def self(self) -> Register: def py_get_attr(self, obj: Value, attr: str, line: int) -> Value: return self.builder.py_get_attr(obj, attr, line) - def load_str(self, value: str) -> Value: - return self.builder.load_str(value) + def load_str(self, value: str, line: int = -1) -> Value: + return self.builder.load_str(value, line) - def load_bytes_from_str_literal(self, value: str) -> Value: + def load_bytes_from_str_literal(self, value: str, line: int = -1) -> Value: """Load bytes object from a string literal. The literal characters of BytesExpr (the characters inside b'') are stored in BytesExpr.value, whose type is 'str' not 'bytes'. Thus we perform a special conversion here. """ - return self.builder.load_bytes(bytes_from_str(value)) + return self.builder.load_bytes(bytes_from_str(value), line) - def load_int(self, value: int) -> Value: - return self.builder.load_int(value) + def load_int(self, value: int, line: int = -1) -> Value: + return self.builder.load_int(value, line) - def load_float(self, value: float) -> Value: - return self.builder.load_float(value) + def load_float(self, value: float, line: int = -1) -> Value: + return self.builder.load_float(value, line) def unary_op(self, lreg: Value, expr_op: str, line: int) -> Value: return self.builder.unary_op(lreg, expr_op, line) @@ -347,17 +348,17 @@ def binary_op(self, lreg: Value, rreg: Value, expr_op: str, line: int) -> Value: def coerce(self, src: Value, target_type: RType, line: int, force: bool = False) -> Value: return self.builder.coerce(src, target_type, line, force, can_borrow=self.can_borrow) - def none_object(self) -> Value: - return self.builder.none_object() + def none_object(self, line: int = -1) -> Value: + return self.builder.none_object(line) - def none(self) -> Value: - return self.builder.none() + def none(self, line: int = -1) -> Value: + return self.builder.none(line) - def true(self) -> Value: - return self.builder.true() + def true(self, line: int = -1) -> Value: + return self.builder.true(line) - def false(self) -> Value: - return self.builder.false() + def false(self, line: int = -1) -> Value: + return self.builder.false(line) def new_list_op(self, values: list[Value], line: int) -> Value: return self.builder.new_list_op(values, line) @@ -438,7 +439,7 @@ def add_to_non_ext_dict( self, non_ext: NonExtClassInfo, key: str, val: Value, line: int ) -> None: # Add an attribute entry into the class dict of a non-extension class. - key_unicode = self.load_str(key) + key_unicode = self.load_str(key, line) self.primitive_op(dict_set_item_op, [non_ext.dict, key_unicode, val], line) # It's important that accessing class dictionary items from multiple threads @@ -452,7 +453,7 @@ def gen_import(self, id: str, line: int) -> None: self.check_if_module_loaded(id, line, needs_import, out) self.activate_block(needs_import) - value = self.call_c(import_op, [self.load_str(id)], line) + value = self.call_c(import_op, [self.load_str(id, line)], line) self.add(InitStatic(value, id, namespace=NAMESPACE_MODULE)) self.goto_and_activate(out) @@ -467,14 +468,14 @@ def check_if_module_loaded( needs_import: the BasicBlock that is run if the module has not been loaded yet out: the BasicBlock that is run if the module has already been loaded""" first_load = self.load_module(id) - comparison = self.translate_is_op(first_load, self.none_object(), "is not", line) + comparison = self.translate_is_op(first_load, self.none_object(line), "is not", line) self.add_bool_branch(comparison, out, needs_import) def get_module(self, module: str, line: int) -> Value: # Python 3.7 has a nice 'PyImport_GetModule' function that we can't use :( mod_dict = self.call_c(get_module_dict_op, [], line) # Get module object from modules dict. - return self.primitive_op(dict_get_item_op, [mod_dict, self.load_str(module)], line) + return self.primitive_op(dict_get_item_op, [mod_dict, self.load_str(module, line)], line) def get_module_attr(self, module: str, attr: str, line: int) -> Value: """Look up an attribute of a module without storing it in the local namespace. @@ -491,9 +492,9 @@ def get_module_attr(self, module: str, attr: str, line: int) -> Value: def assign_if_null(self, target: Register, get_val: Callable[[], Value], line: int) -> None: """If target is NULL, assign value produced by get_val to it.""" error_block, body_block = BasicBlock(), BasicBlock() - self.add(Branch(target, error_block, body_block, Branch.IS_ERROR)) + self.add(Branch(target, error_block, body_block, Branch.IS_ERROR, line)) self.activate_block(error_block) - self.add(Assign(target, self.coerce(get_val(), target.type, line))) + self.add(Assign(target, self.coerce(get_val(), target.type, line), line)) self.goto(body_block) self.activate_block(body_block) @@ -508,7 +509,7 @@ def assign_if_bitmap_unset( IntOp.AND, line, ) - b = self.add(ComparisonOp(o, Integer(0, bitmap_rprimitive), ComparisonOp.EQ)) + b = self.add(ComparisonOp(o, Integer(0, bitmap_rprimitive), ComparisonOp.EQ, line)) self.add(Branch(b, error_block, body_block, Branch.BOOL)) self.activate_block(error_block) self.add(Assign(target, self.coerce(get_val(), target.type, line))) @@ -524,8 +525,9 @@ def maybe_add_implicit_return(self) -> None: def add_implicit_return(self) -> None: block = self.builder.blocks[-1] if not block.terminated: - retval = self.coerce(self.builder.none(), self.ret_types[-1], -1) - self.nonlocal_control[-1].gen_return(self, retval, self.fn_info.fitem.line) + line = self.fn_info.fitem.line + retval = self.coerce(self.builder.none(), self.ret_types[-1], line) + self.nonlocal_control[-1].gen_return(self, retval, line) def add_implicit_unreachable(self) -> None: block = self.builder.blocks[-1] @@ -605,13 +607,15 @@ def load_type_var(self, name: str, line: int) -> Value: ) ) - def load_literal_value(self, val: int | str | bytes | float | complex | bool) -> Value: + def load_literal_value( + self, val: int | str | bytes | float | complex | bool, line: int = -1 + ) -> Value: """Load value of a final name, class-level attribute, or constant folded expression.""" if isinstance(val, bool): if val: - return self.true() + return self.true(line) else: - return self.false() + return self.false(line) elif isinstance(val, int): return self.builder.load_int(val) elif isinstance(val, float): @@ -669,7 +673,7 @@ def get_assignment_target( return self.lookup(symbol) elif lvalue.kind == GDEF: globals_dict = self.load_globals_dict() - name = self.load_str(lvalue.name) + name = self.load_str(lvalue.name, line) return AssignmentTargetIndex(globals_dict, name) else: assert False, lvalue.kind @@ -711,6 +715,8 @@ def read( allow_error_value: bool = False, ) -> Value: if isinstance(target, Value): + if line != -1: + target.line = line return target if isinstance(target, AssignmentTargetRegister): return target.register @@ -745,15 +751,15 @@ def read_nullable_attr(self, obj: Value, attr: str, line: int = -1) -> Value: def assign(self, target: Register | AssignmentTarget, rvalue_reg: Value, line: int) -> None: if isinstance(target, Register): - self.add(Assign(target, self.coerce_rvalue(rvalue_reg, target.type, line))) + self.add(Assign(target, self.coerce_rvalue(rvalue_reg, target.type, line), line)) elif isinstance(target, AssignmentTargetRegister): rvalue_reg = self.coerce_rvalue(rvalue_reg, target.type, line) - self.add(Assign(target.register, rvalue_reg)) + self.add(Assign(target.register, rvalue_reg, line)) elif isinstance(target, AssignmentTargetAttr): if isinstance(target.obj_type, RInstance): setattr = target.obj_type.class_ir.get_method("__setattr__") if setattr: - key = self.load_str(target.attr) + key = self.load_str(target.attr, line) boxed_reg = self.builder.box(rvalue_reg) call = MethodCall(target.obj, setattr.name, [key, boxed_reg], line) self.add(call) @@ -761,7 +767,7 @@ def assign(self, target: Register | AssignmentTarget, rvalue_reg: Value, line: i rvalue_reg = self.coerce_rvalue(rvalue_reg, target.type, line) self.add(SetAttr(target.obj, target.attr, rvalue_reg, line)) else: - key = self.load_str(target.attr) + key = self.load_str(target.attr, line) boxed_reg = self.builder.box(rvalue_reg) self.primitive_op(py_setattr_op, [target.obj, key, boxed_reg], line) elif isinstance(target, AssignmentTargetIndex): @@ -931,8 +937,8 @@ def make_spill_target(self, type: RType) -> AssignmentTarget: def spill(self, value: Value) -> AssignmentTarget: """Moves a given Value instance into the generator class' environment class.""" target = self.make_spill_target(value.type) - # Shouldn't be able to fail, so -1 for line - self.assign(target, value, -1) + # Shouldn't be able to fail + self.assign(target, value, NO_TRACEBACK_LINE_NO) return target def maybe_spill(self, value: Value) -> Value | AssignmentTarget: @@ -963,7 +969,7 @@ def maybe_spill_assignable(self, value: Value) -> Register | AssignmentTarget: # Allocate a temporary register for the assignable value. reg = Register(value.type) - self.assign(reg, value, -1) + self.assign(reg, value, NO_TRACEBACK_LINE_NO) return reg def extract_int(self, e: Expression) -> int | None: @@ -1132,7 +1138,7 @@ def emit_load_final( line: line number where loading occurs """ if final_var.final_value is not None: # this is safe even for non-native names - return self.load_literal_value(final_var.final_value) + return self.load_literal_value(final_var.final_value, line) elif native and module_prefix(self.graph, fullname): return self.load_final_static(fullname, self.mapper.type_to_rtype(typ), line, name) else: @@ -1409,7 +1415,7 @@ def load_global(self, expr: NameExpr) -> Value: def load_global_str(self, name: str, line: int) -> Value: _globals = self.load_globals_dict() - reg = self.load_str(name) + reg = self.load_str(name, line) return self.primitive_op(dict_get_item_op, [_globals, reg], line) def load_globals_dict(self) -> Value: @@ -1473,7 +1479,7 @@ def add_coroutine_setup_call(self, class_name: str, obj: Value) -> Value: FuncSignature([RuntimeArg("type", object_rprimitive)], bool_rprimitive), ), [obj], - -1, + obj.line, ) ) @@ -1571,13 +1577,13 @@ def create_type_params( # To match runtime semantics, pass infer_variance=True tv = builder.py_call( tvt, - [builder.load_str(type_param.name), builder.true()], + [builder.load_str(type_param.name, line), builder.true(line)], line, arg_kinds=[ARG_POS, ARG_NAMED], arg_names=[None, "infer_variance"], ) else: - tv = builder.py_call(tvt, [builder.load_str(type_param.name)], line) + tv = builder.py_call(tvt, [builder.load_str(type_param.name, line)], line) builder.init_type_var(tv, type_param.name, line) tvs.append(tv) return tvs diff --git a/mypyc/irbuild/callable_class.py b/mypyc/irbuild/callable_class.py index 22784ca12dfa..f1dc32c6b5c8 100644 --- a/mypyc/irbuild/callable_class.py +++ b/mypyc/irbuild/callable_class.py @@ -113,13 +113,15 @@ def add_coroutine_properties( "__annotations__": cpyfunction_set_annotations, } + line = builder.fn_info.fitem.line + def get_func_wrapper() -> Value: - return builder.add(GetAttr(builder.self(), CPYFUNCTION_NAME, -1)) + return builder.add(GetAttr(builder.self(), CPYFUNCTION_NAME, line)) for name, primitive in properties.items(): with builder.enter_method(callable_class_ir, name, object_rprimitive, internal=True): func = get_func_wrapper() - val = builder.primitive_op(primitive, [func, Integer(0, c_pointer_rprimitive)], -1) + val = builder.primitive_op(primitive, [func, Integer(0, c_pointer_rprimitive)], line) builder.add(Return(val)) for name, primitive in writable_props.items(): @@ -129,7 +131,7 @@ def get_func_wrapper() -> Value: value = builder.add_argument("value", object_rprimitive) func = get_func_wrapper() rv = builder.primitive_op( - primitive, [func, value, Integer(0, c_pointer_rprimitive)], -1 + primitive, [func, value, Integer(0, c_pointer_rprimitive)], line ) builder.add(Return(rv)) @@ -185,7 +187,7 @@ def add_get_to_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> None: # instance method object. instance_block, class_block = BasicBlock(), BasicBlock() comparison = builder.translate_is_op( - builder.read(instance), builder.none_object(), "is", line + builder.read(instance), builder.none_object(line), "is", line ) builder.add_bool_branch(comparison, class_block, instance_block) diff --git a/mypyc/irbuild/classdef.py b/mypyc/irbuild/classdef.py index 2fd7357ec4f0..d59355c33a50 100644 --- a/mypyc/irbuild/classdef.py +++ b/mypyc/irbuild/classdef.py @@ -470,7 +470,7 @@ def allocate_class(builder: IRBuilder, cdef: ClassDef) -> Value: FuncSignature([], bool_rprimitive), ), [], - -1, + cdef.line, ) ) builder.add_coroutine_setup_call(cdef.name, tp) @@ -784,7 +784,7 @@ def generate_attr_defaults_init( attr_type = cls.attr_type(lvalue.name) val = builder.coerce(builder.accept(stmt.rvalue), attr_type, stmt.line) - init = SetAttr(self_var, lvalue.name, val, -1) + init = SetAttr(self_var, lvalue.name, val, stmt.rvalue.line) init.mark_as_initializer() builder.add(init) diff --git a/mypyc/irbuild/env_class.py b/mypyc/irbuild/env_class.py index 2334b4370103..2a9cc50d9ccf 100644 --- a/mypyc/irbuild/env_class.py +++ b/mypyc/irbuild/env_class.py @@ -261,20 +261,21 @@ def setup_func_for_recursive_call( prev_env = builder.fn_infos[-2].env_class attr_name = prefix + fdef.name prev_env.attributes[attr_name] = builder.type_to_rtype(fdef.type) + line = fdef.line if isinstance(base, GeneratorClass): # If we are dealing with a generator class, then we need to first get the register # holding the current environment class, and load the previous environment class from # there. - prev_env_reg = builder.add(GetAttr(base.curr_env_reg, ENV_ATTR_NAME, -1)) + prev_env_reg = builder.add(GetAttr(base.curr_env_reg, ENV_ATTR_NAME, line)) else: prev_env_reg = base.prev_env_reg # Obtain the instance of the callable class representing the FuncDef, and add it to the # current environment. - val = builder.add(GetAttr(prev_env_reg, attr_name, -1)) + val = builder.add(GetAttr(prev_env_reg, attr_name, line)) target = builder.add_local_reg(fdef, object_rprimitive) - builder.assign(target, val, -1) + builder.assign(target, val, line) def is_free_variable(builder: IRBuilder, symbol: SymbolNode) -> bool: diff --git a/mypyc/irbuild/expression.py b/mypyc/irbuild/expression.py index c19248622f1d..e99542138ed7 100644 --- a/mypyc/irbuild/expression.py +++ b/mypyc/irbuild/expression.py @@ -135,22 +135,22 @@ def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: RaiseStandardError.NAME_ERROR, f'name "{expr.name}" is not defined', expr.line ) ) - return builder.none() + return builder.none(expr.line) 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() + return builder.none(expr.line) if fullname == "builtins.True": - return builder.true() + return builder.true(expr.line) if fullname == "builtins.False": - return builder.false() + return builder.false(expr.line) if fullname in ("typing.TYPE_CHECKING", "typing_extensions.TYPE_CHECKING"): - return builder.false() + return builder.false(expr.line) - math_literal = transform_math_literal(builder, fullname) + math_literal = transform_math_literal(builder, fullname, expr.line) if math_literal is not None: return math_literal @@ -208,7 +208,7 @@ def transform_name_expr(builder: IRBuilder, expr: NameExpr) -> Value: def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value: # Special Cases if expr.fullname in ("typing.TYPE_CHECKING", "typing_extensions.TYPE_CHECKING"): - return builder.false() + return builder.false(expr.line) # First check if this is maybe a final attribute. final = builder.get_final_ref(expr) @@ -223,7 +223,7 @@ def transform_member_expr(builder: IRBuilder, expr: MemberExpr) -> Value: if value is not None: return value - math_literal = transform_math_literal(builder, expr.fullname) + math_literal = transform_math_literal(builder, expr.fullname, expr.line) if math_literal is not None: return math_literal @@ -587,7 +587,9 @@ def try_optimize_int_floor_divide(builder: IRBuilder, expr: OpExpr) -> OpExpr: return expr shift = divisor.bit_length() - 1 if 0 < shift < 28 and divisor == (1 << shift): - return OpExpr(">>", expr.left, IntExpr(shift)) + new_expr = OpExpr(">>", expr.left, IntExpr(shift)) + new_expr.line = expr.line + return new_expr return expr @@ -683,13 +685,13 @@ def transform_conditional_expr(builder: IRBuilder, expr: ConditionalExpr) -> Val builder.activate_block(if_body) true_value = builder.accept(expr.if_expr) true_value = builder.coerce(true_value, expr_type, expr.line) - builder.add(Assign(target, true_value)) + builder.add(Assign(target, true_value, expr.line)) builder.goto(next_block) builder.activate_block(else_body) false_value = builder.accept(expr.else_expr) false_value = builder.coerce(false_value, expr_type, expr.line) - builder.add(Assign(target, false_value)) + builder.add(Assign(target, false_value, expr.line)) builder.goto(next_block) builder.activate_block(next_block) @@ -1090,10 +1092,10 @@ def transform_dictionary_comprehension(builder: IRBuilder, o: DictionaryComprehe def gen_inner_stmts() -> None: k = builder.accept(o.key) v = builder.accept(o.value) - builder.call_c(exact_dict_set_item_op, [builder.read(d), k, v], o.line) + builder.call_c(exact_dict_set_item_op, [builder.read(d, o.line), k, v], o.line) comprehension_helper(builder, loop_params, gen_inner_stmts, o.line) - return builder.read(d) + return builder.read(d, o.line) # Misc @@ -1122,16 +1124,16 @@ def transform_assignment_expr(builder: IRBuilder, o: AssignmentExpr) -> Value: return value -def transform_math_literal(builder: IRBuilder, fullname: str) -> Value | None: +def transform_math_literal(builder: IRBuilder, fullname: str, line: int) -> Value | None: if fullname == "math.e": - return builder.load_float(math.e) + return builder.load_float(math.e, line) if fullname == "math.pi": - return builder.load_float(math.pi) + return builder.load_float(math.pi, line) if fullname == "math.inf": - return builder.load_float(math.inf) + return builder.load_float(math.inf, line) if fullname == "math.nan": - return builder.load_float(math.nan) + return builder.load_float(math.nan, line) if fullname == "math.tau": - return builder.load_float(math.tau) + return builder.load_float(math.tau, line) return None diff --git a/mypyc/irbuild/for_helpers.py b/mypyc/irbuild/for_helpers.py index 1ee13955fb0e..5c7589f99dfc 100644 --- a/mypyc/irbuild/for_helpers.py +++ b/mypyc/irbuild/for_helpers.py @@ -195,7 +195,7 @@ def for_loop_helper_with_index( builder.activate_block(body_block) for_gen.begin_body() - body_insts(builder.read(for_gen.index_target)) + body_insts(builder.read(for_gen.index_target, line)) builder.goto_and_activate(step_block) for_gen.gen_step() @@ -314,10 +314,10 @@ def translate_list_comprehension(builder: IRBuilder, gen: GeneratorExpr) -> Valu def gen_inner_stmts() -> None: e = builder.accept(gen.left_expr) - builder.primitive_op(list_append_op, [builder.read(list_ops), e], gen.line) + builder.primitive_op(list_append_op, [builder.read(list_ops, gen.line), e], gen.line) comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line) - return builder.read(list_ops) + return builder.read(list_ops, gen.line) def raise_error_if_contains_unreachable_names( @@ -349,10 +349,10 @@ def translate_set_comprehension(builder: IRBuilder, gen: GeneratorExpr) -> Value def gen_inner_stmts() -> None: e = builder.accept(gen.left_expr) - builder.primitive_op(set_add_op, [builder.read(set_ops), e], gen.line) + builder.primitive_op(set_add_op, [builder.read(set_ops, gen.line), e], gen.line) comprehension_helper(builder, loop_params, gen_inner_stmts, gen.line) - return builder.read(set_ops) + return builder.read(set_ops, gen.line) def comprehension_helper( @@ -725,7 +725,10 @@ def gen_condition(self) -> None: ptr = builder.add(LoadAddress(object_pointer_rprimitive, self.return_value)) nn = builder.none_object() helper_call = MethodCall( - builder.read(self.iter_target), GENERATOR_HELPER_NAME, [nn, nn, nn, nn, ptr], line + builder.read(self.iter_target, line), + GENERATOR_HELPER_NAME, + [nn, nn, nn, nn, ptr], + line, ) # We provide custom handling for error values. helper_call.error_kind = ERR_NEVER @@ -789,9 +792,9 @@ def except_match() -> Value: return builder.add(LoadMem(stop_async_iteration_op.type, addr, borrow=True)) def try_body() -> None: - awaitable = builder.call_c(anext_op, [builder.read(self.iter_target)], line) + awaitable = builder.call_c(anext_op, [builder.read(self.iter_target, line)], line) self.next_reg = emit_await(builder, awaitable, line) - builder.assign(self.stop_reg, builder.false(), -1) + builder.assign(self.stop_reg, builder.false(), line) def except_body() -> None: builder.assign(self.stop_reg, builder.true(), line) @@ -863,7 +866,7 @@ def init( index_reg: Value = Integer(0, c_pyssize_t_rprimitive) else: if self.length_reg is not None: - len_val = builder.read(self.length_reg) + len_val = builder.read(self.length_reg, self.line) else: len_val = self.load_len(self.expr_target) index_reg = builder.builder.int_sub(len_val, 1) @@ -1080,8 +1083,8 @@ def init(self, start_reg: Value, end_reg: Value, step: int) -> None: index_type = end_reg.type else: index_type = int_rprimitive - index_reg = Register(index_type) - builder.assign(index_reg, start_reg, -1) + index_reg = Register(index_type, line=self.line) + builder.assign(index_reg, start_reg, self.line) self.index_reg = builder.maybe_spill_assignable(index_reg) # Initialize loop index to 0. Assert that the index target is assignable. self.index_target: Register | AssignmentTarget = builder.get_assignment_target(self.index) @@ -1145,7 +1148,9 @@ def gen_step(self) -> None: builder.assign(self.index_reg, new_val, line) def begin_body(self) -> None: - self.builder.assign(self.index_target, self.builder.read(self.index_reg), self.line) + self.builder.assign( + self.index_target, self.builder.read(self.index_reg, self.line), self.line + ) class ForEnumerate(ForGenerator): diff --git a/mypyc/irbuild/function.py b/mypyc/irbuild/function.py index a2ff268d4d3d..ef2d8f767b1a 100644 --- a/mypyc/irbuild/function.py +++ b/mypyc/irbuild/function.py @@ -1040,7 +1040,7 @@ def generate_dispatch_glue_native_function( def generate_singledispatch_callable_class_ctor(builder: IRBuilder) -> None: """Create an __init__ that sets registry and dispatch_cache to empty dicts""" - line = -1 + line = builder.fn_info.fitem.line class_ir = builder.fn_info.callable_class.ir with builder.enter_method(class_ir, "__init__", bool_rprimitive): empty_dict = builder.call_c(dict_new_op, [], line) @@ -1054,7 +1054,7 @@ def generate_singledispatch_callable_class_ctor(builder: IRBuilder) -> None: def add_register_method_to_callable_class(builder: IRBuilder, fn_info: FuncInfo) -> None: - line = -1 + line = fn_info.fitem.line with builder.enter_method(fn_info.callable_class.ir, "register", object_rprimitive): cls_arg = builder.add_argument("cls", object_rprimitive) func_arg = builder.add_argument("func", object_rprimitive, ArgKind.ARG_OPT) @@ -1137,9 +1137,10 @@ def gen_property_getter_ir( name = func_decl.name builder.enter(name) self_reg = builder.add_argument("self", func_decl.sig.args[0].type) + line = func_decl._line or -1 if not is_trait: - value = builder.builder.get_attr(self_reg, name, func_decl.sig.ret_type, -1) - builder.add(Return(value)) + value = builder.builder.get_attr(self_reg, name, func_decl.sig.ret_type, line) + builder.add(Return(value, line)) else: builder.add(Unreachable()) args, _, blocks, ret_type, fn_info = builder.leave() @@ -1159,8 +1160,9 @@ def gen_property_setter_ir( value_reg = builder.add_argument("value", func_decl.sig.args[1].type) assert name.startswith(PROPSET_PREFIX) attr_name = name[len(PROPSET_PREFIX) :] + line = func_decl._line or -1 if not is_trait: - builder.add(SetAttr(self_reg, attr_name, value_reg, -1)) - builder.add(Return(builder.none())) + builder.add(SetAttr(self_reg, attr_name, value_reg, line)) + builder.add(Return(builder.none(), line)) args, _, blocks, ret_type, fn_info = builder.leave() - return FuncIR(func_decl, args, blocks) + return FuncIR(func_decl, args, blocks, line) diff --git a/mypyc/irbuild/generator.py b/mypyc/irbuild/generator.py index 5f06afe9ae8e..82fc74f9b1ce 100644 --- a/mypyc/irbuild/generator.py +++ b/mypyc/irbuild/generator.py @@ -310,8 +310,8 @@ def add_throw_to_generator_class(builder: IRBuilder, fn_info: FuncInfo, fn_decl: # can be NULL if not passed in, we have to assign them Py_None if # they are not passed in. none_reg = builder.none_object() - builder.assign_if_null(val, lambda: none_reg, builder.fn_info.fitem.line) - builder.assign_if_null(tb, lambda: none_reg, builder.fn_info.fitem.line) + builder.assign_if_null(val, lambda: none_reg, fn_info.fitem.line) + builder.assign_if_null(tb, lambda: none_reg, fn_info.fitem.line) # Call the helper function using the arguments passed in, and return that result. result = builder.add( @@ -345,6 +345,7 @@ def add_close_to_generator_class(builder: IRBuilder, fn_info: FuncInfo) -> None: builder.self(), "throw", [generator_exit, builder.none_object(), builder.none_object()], + fn_info.fitem.line, ) ) builder.goto(else_block) diff --git a/mypyc/irbuild/ll_builder.py b/mypyc/irbuild/ll_builder.py index 8c6b673e3273..f7e537e55da6 100644 --- a/mypyc/irbuild/ll_builder.py +++ b/mypyc/irbuild/ll_builder.py @@ -299,8 +299,8 @@ def goto_and_activate(self, block: BasicBlock) -> None: self.goto(block) self.activate_block(block) - def keep_alive(self, values: list[Value], *, steal: bool = False) -> None: - self.add(KeepAlive(values, steal=steal)) + def keep_alive(self, values: list[Value], line: int, *, steal: bool = False) -> None: + self.add(KeepAlive(values, line, steal=steal)) def load_mem(self, ptr: Value, value_type: RType, *, borrow: bool = False) -> Value: return self.add(LoadMem(value_type, ptr, borrow=borrow)) @@ -318,9 +318,9 @@ def self(self) -> Register: """ return self.args[0] - def flush_keep_alives(self) -> None: + def flush_keep_alives(self, line: int) -> None: if self.keep_alives: - self.add(KeepAlive(self.keep_alives.copy())) + self.add(KeepAlive(self.keep_alives.copy(), line)) self.keep_alives = [] def debug_print(self, toprint: str | Value) -> None: @@ -334,7 +334,7 @@ def box(self, src: Value) -> Value: if src.type.is_unboxed: if isinstance(src, Integer) and is_tagged(src.type): return self.add(LoadLiteral(src.value >> 1, rtype=object_rprimitive)) - return self.add(Box(src)) + return self.add(Box(src, src.line)) else: return src @@ -451,7 +451,7 @@ def coerce( return self.unbox_or_cast(src, target_type, line, can_borrow=can_borrow) elif force: tmp = Register(target_type) - self.add(Assign(tmp, src)) + self.add(Assign(tmp, src, line)) return tmp return src @@ -551,8 +551,10 @@ def coerce_tagged_to_fixed_width_with_range_check( upper_bound *= 2 # Check if value < upper_bound - check_upper = self.add(ComparisonOp(src, Integer(upper_bound, src.type), ComparisonOp.SLT)) - self.add(Branch(check_upper, in_range, overflow_block, Branch.BOOL)) + check_upper = self.add( + ComparisonOp(src, Integer(upper_bound, src.type), ComparisonOp.SLT, line) + ) + self.add(Branch(check_upper, in_range, overflow_block, Branch.BOOL, line)) self.activate_block(in_range) @@ -561,8 +563,10 @@ def coerce_tagged_to_fixed_width_with_range_check( lower_bound = -upper_bound else: lower_bound = 0 - check_lower = self.add(ComparisonOp(src, Integer(lower_bound, src.type), ComparisonOp.SGE)) - self.add(Branch(check_lower, in_range2, overflow_block, Branch.BOOL)) + check_lower = self.add( + ComparisonOp(src, Integer(lower_bound, src.type), ComparisonOp.SGE, line) + ) + self.add(Branch(check_lower, in_range2, overflow_block, Branch.BOOL, line)) self.activate_block(in_range2) @@ -574,8 +578,8 @@ def coerce_tagged_to_fixed_width_with_range_check( IntOp.RIGHT_SHIFT, line, ) - tmp = self.add(Truncate(shifted, target_type)) - self.add(Assign(res, tmp)) + tmp = self.add(Truncate(shifted, target_type, line)) + self.add(Assign(res, tmp, line)) self.goto(success_block) return res @@ -644,11 +648,11 @@ def coerce_fixed_width_to_int(self, src: Value, line: int) -> Value: fast, fast2, slow, end = BasicBlock(), BasicBlock(), BasicBlock(), BasicBlock() - c1 = self.add(ComparisonOp(src, Integer(MAX_SHORT_INT, src_type), ComparisonOp.SLE)) + c1 = self.add(ComparisonOp(src, Integer(MAX_SHORT_INT, src_type), ComparisonOp.SLE, line)) self.add(Branch(c1, fast, slow, Branch.BOOL)) self.activate_block(fast) - c2 = self.add(ComparisonOp(src, Integer(MIN_SHORT_INT, src_type), ComparisonOp.SGE)) + c2 = self.add(ComparisonOp(src, Integer(MIN_SHORT_INT, src_type), ComparisonOp.SGE, line)) self.add(Branch(c2, fast2, slow, Branch.BOOL)) self.activate_block(slow) @@ -758,7 +762,7 @@ def other() -> Value: def get_type_of_obj(self, obj: Value, line: int) -> Value: ob_type_address = self.add(GetElementPtr(obj, PyObject, "ob_type", line)) ob_type = self.load_mem(ob_type_address, object_rprimitive, borrow=True) - self.add(KeepAlive([obj])) + self.add(KeepAlive([obj], line)) return ob_type def type_is_op(self, obj: Value, type_obj: Value, line: int) -> Value: @@ -1080,7 +1084,7 @@ def _py_vector_call( if arg_values: # Create a C array containing all arguments as boxed values. coerced_args = [self.coerce(arg, object_rprimitive, line) for arg in arg_values] - arg_ptr = self.setup_rarray(object_rprimitive, coerced_args, object_ptr=True) + arg_ptr = self.setup_rarray(object_rprimitive, coerced_args, line, object_ptr=True) else: arg_ptr = Integer(0, object_pointer_rprimitive) num_pos = num_positional_args(arg_values, arg_kinds) @@ -1094,7 +1098,7 @@ def _py_vector_call( # Make sure arguments won't be freed until after the call. # We need this because RArray doesn't support automatic # memory management. - self.add(KeepAlive(coerced_args)) + self.add(KeepAlive(coerced_args, line)) return value return None @@ -1155,7 +1159,7 @@ def _py_vector_method_call( coerced_args = [ self.coerce(arg, object_rprimitive, line) for arg in [obj] + arg_values ] - arg_ptr = self.setup_rarray(object_rprimitive, coerced_args, object_ptr=True) + arg_ptr = self.setup_rarray(object_rprimitive, coerced_args, line, object_ptr=True) num_pos = num_positional_args(arg_values, arg_kinds) keywords = self._vectorcall_keywords(arg_names) value = self.call_c( @@ -1171,7 +1175,7 @@ def _py_vector_method_call( # Make sure arguments won't be freed until after the call. # We need this because RArray doesn't support automatic # memory management. - self.add(KeepAlive(coerced_args)) + self.add(KeepAlive(coerced_args, line)) return value return None @@ -1377,56 +1381,56 @@ def call_union_item(value: Value) -> Value: # Loading various values - def none(self) -> Value: + def none(self, line: int = -1) -> Value: """Load unboxed None value (type: none_rprimitive).""" - return Integer(1, none_rprimitive) + return Integer(1, none_rprimitive, line) - def true(self) -> Value: + def true(self, line: int = -1) -> Value: """Load unboxed True value (type: bool_rprimitive).""" - return Integer(1, bool_rprimitive) + return Integer(1, bool_rprimitive, line) - def false(self) -> Value: + def false(self, line: int = -1) -> Value: """Load unboxed False value (type: bool_rprimitive).""" - return Integer(0, bool_rprimitive) + return Integer(0, bool_rprimitive, line) - def none_object(self) -> Value: + def none_object(self, line: int = -1) -> Value: """Load Python None value (type: object_rprimitive).""" - return self.add(LoadAddress(none_object_op.type, none_object_op.src, line=-1)) + return self.add(LoadAddress(none_object_op.type, none_object_op.src, line)) - def true_object(self) -> Value: + def true_object(self, line: int = -1) -> Value: """Load Python True object (type: object_rprimitive).""" - return self.add(LoadGlobal(object_rprimitive, "Py_True")) + return self.add(LoadGlobal(object_rprimitive, "Py_True", line)) - def false_object(self) -> Value: + def false_object(self, line: int = -1) -> Value: """Load Python False object (type: object_rprimitive).""" - return self.add(LoadGlobal(object_rprimitive, "Py_False")) + return self.add(LoadGlobal(object_rprimitive, "Py_False", line)) - def load_int(self, value: int) -> Value: + def load_int(self, value: int, line: int = -1) -> Value: """Load a tagged (Python) integer literal value.""" if value > MAX_LITERAL_SHORT_INT or value < MIN_LITERAL_SHORT_INT: - return self.add(LoadLiteral(value, int_rprimitive)) + return self.add(LoadLiteral(value, int_rprimitive, line)) else: - return Integer(value) + return Integer(value, line=line) - def load_float(self, value: float) -> Value: + def load_float(self, value: float, line: int = -1) -> Value: """Load a float literal value.""" - return Float(value) + return Float(value, line) - def load_str(self, value: str) -> Value: + def load_str(self, value: str, line: int = -1) -> Value: """Load a str literal value. This is useful for more than just str literals; for example, method calls also require a PyObject * form for the name of the method. """ - return self.add(LoadLiteral(value, str_rprimitive)) + return self.add(LoadLiteral(value, str_rprimitive, line)) - def load_bytes(self, value: bytes) -> Value: + def load_bytes(self, value: bytes, line: int = -1) -> Value: """Load a bytes literal value.""" - return self.add(LoadLiteral(value, bytes_rprimitive)) + return self.add(LoadLiteral(value, bytes_rprimitive, line)) - def load_complex(self, value: complex) -> Value: + def load_complex(self, value: complex, line: int = -1) -> Value: """Load a complex literal value.""" - return self.add(LoadLiteral(value, object_rprimitive)) + return self.add(LoadLiteral(value, object_rprimitive, line)) def load_static_checked( self, @@ -1954,18 +1958,18 @@ def new_list_op(self, values: list[Value], line: int) -> Value: self.primitive_op( buf_init_item, [ob_item_base, Integer(i, c_pyssize_t_rprimitive), args[i]], line ) - self.add(KeepAlive([result_list])) + self.add(KeepAlive([result_list], line)) return result_list def new_set_op(self, values: list[Value], line: int) -> Value: return self.primitive_op(new_set_op, values, line) def setup_rarray( - self, item_type: RType, values: Sequence[Value], *, object_ptr: bool = False + self, item_type: RType, values: Sequence[Value], line: int, *, object_ptr: bool = False ) -> Value: """Declare and initialize a new RArray, returning its address.""" array = Register(RArray(item_type, len(values))) - self.add(AssignMulti(array, list(values))) + self.add(AssignMulti(array, list(values), line)) return self.add( LoadAddress(object_pointer_rprimitive if object_ptr else c_pointer_rprimitive, array) ) @@ -1979,7 +1983,7 @@ def shortcircuit_helper( line: int, ) -> Value: # Having actual Phi nodes would be really nice here! - target = Register(expr_type) + target = Register(expr_type, line=line) # left_body takes the value of the left side, right_body the right left_body, right_body, next_block = BasicBlock(), BasicBlock(), BasicBlock() # true_body is taken if the left is true, false_body if it is false. @@ -1992,13 +1996,13 @@ def shortcircuit_helper( self.activate_block(left_body) left_coerced = self.coerce(left_value, expr_type, line) - self.add(Assign(target, left_coerced)) + self.add(Assign(target, left_coerced, line)) self.goto(next_block) self.activate_block(right_body) right_value = right() right_coerced = self.coerce(right_value, expr_type, line) - self.add(Assign(target, right_coerced)) + self.add(Assign(target, right_coerced, line)) self.goto(next_block) self.activate_block(next_block) @@ -2075,11 +2079,11 @@ def add_bool_branch(self, value: Value, true: BasicBlock, false: BasicBlock) -> opt_value_type = optional_value_type(value.type) if opt_value_type is None: bool_value = self.bool_value(value) - self.add(Branch(bool_value, true, false, Branch.BOOL)) + self.add(Branch(bool_value, true, false, Branch.BOOL, value.line)) else: # Special-case optional types is_none = self.translate_is_op(value, self.none_object(), "is not", value.line) - branch = Branch(is_none, true, false, Branch.BOOL) + branch = Branch(is_none, true, false, Branch.BOOL, value.line) self.add(branch) always_truthy = False if isinstance(opt_value_type, RInstance): @@ -2392,7 +2396,7 @@ def int_add(self, lhs: Value, rhs: Value | int) -> Value: """ if isinstance(rhs, int): rhs = Integer(rhs, lhs.type) - return self.int_op(lhs.type, lhs, rhs, IntOp.ADD, line=-1) + return self.int_op(lhs.type, lhs, rhs, IntOp.ADD, lhs.line) def int_sub(self, lhs: Value, rhs: Value | int) -> Value: """Helper to subtract a native integer from another one. @@ -2401,7 +2405,7 @@ def int_sub(self, lhs: Value, rhs: Value | int) -> Value: """ if isinstance(rhs, int): rhs = Integer(rhs, lhs.type) - return self.int_op(lhs.type, lhs, rhs, IntOp.SUB, line=-1) + return self.int_op(lhs.type, lhs, rhs, IntOp.SUB, lhs.line) def int_mul(self, lhs: Value, rhs: Value | int) -> Value: """Helper to multiply two native integers. @@ -2410,7 +2414,7 @@ def int_mul(self, lhs: Value, rhs: Value | int) -> Value: """ if isinstance(rhs, int): rhs = Integer(rhs, lhs.type) - return self.int_op(lhs.type, lhs, rhs, IntOp.MUL, line=-1) + return self.int_op(lhs.type, lhs, rhs, IntOp.MUL, lhs.line) def fixed_width_int_op( self, type: RPrimitive, lhs: Value, rhs: Value, op: int, line: int @@ -2543,7 +2547,7 @@ def builtin_len(self, val: Value, line: int, use_pyssize_t: bool = False) -> Val elif is_set_rprimitive(typ) or is_frozenset_rprimitive(typ): elem_address = self.add(GetElementPtr(val, PySetObject, "used")) size_value = self.load_mem(elem_address, c_pyssize_t_rprimitive) - self.add(KeepAlive([val])) + self.add(KeepAlive([val], line)) elif is_dict_rprimitive(typ): size_value = self.call_c(dict_ssize_t_size_op, [val], line) elif is_str_rprimitive(typ): @@ -2650,7 +2654,7 @@ def decompose_union_helper( # For everything but RInstance we fall back to C API rest_items.append(item) exit_block = BasicBlock() - result = Register(result_type) + result = Register(result_type, line=line) for i, item in enumerate(fast_items): more_types = i < len(fast_items) - 1 or rest_items if more_types: @@ -2662,7 +2666,7 @@ def decompose_union_helper( coerced = self.coerce(obj, item, line) temp = process_item(coerced) temp2 = self.coerce(temp, result_type, line) - self.add(Assign(result, temp2)) + self.add(Assign(result, temp2, line)) self.goto(exit_block) if more_types: self.activate_block(false_block) @@ -2672,7 +2676,7 @@ def decompose_union_helper( coerced = self.coerce(obj, object_rprimitive, line, force=True) temp = process_item(coerced) temp2 = self.coerce(temp, result_type, line) - self.add(Assign(result, temp2)) + self.add(Assign(result, temp2, line)) self.goto(exit_block) self.activate_block(exit_block) return result @@ -2774,7 +2778,7 @@ def _translate_fast_optional_eq_cmp( lreg, rreg = rreg, lreg value_typ = optional_value_type(lreg.type) assert value_typ - res = Register(bool_rprimitive) + res = Register(bool_rprimitive, line=line) # Fast path: left value is None? cmp = self.add(ComparisonOp(lreg, self.none_object(), ComparisonOp.EQ, line)) @@ -2785,11 +2789,11 @@ def _translate_fast_optional_eq_cmp( self.activate_block(l_none) if not isinstance(rreg.type, RUnion): val = self.false() if expr_op == "==" else self.true() - self.add(Assign(res, val)) + self.add(Assign(res, val, line)) else: op = ComparisonOp.EQ if expr_op == "==" else ComparisonOp.NEQ cmp = self.add(ComparisonOp(rreg, self.none_object(), op, line)) - self.add(Assign(res, cmp)) + self.add(Assign(res, cmp, line)) self.goto(out) self.activate_block(l_not_none) @@ -2802,7 +2806,7 @@ def _translate_fast_optional_eq_cmp( line, ) assert eq is not None - self.add(Assign(res, eq)) + self.add(Assign(res, eq, line)) else: r_none = BasicBlock() r_not_none = BasicBlock() @@ -2812,7 +2816,7 @@ def _translate_fast_optional_eq_cmp( self.activate_block(r_none) # None vs not-None val = self.false() if expr_op == "==" else self.true() - self.add(Assign(res, val)) + self.add(Assign(res, val, line)) self.goto(out) self.activate_block(r_not_none) # Both operands are known to be not None, perform specialized comparison @@ -2823,7 +2827,7 @@ def _translate_fast_optional_eq_cmp( line, ) assert eq is not None - self.add(Assign(res, eq)) + self.add(Assign(res, eq, line)) self.goto(out) self.activate_block(out) return res diff --git a/mypyc/irbuild/main.py b/mypyc/irbuild/main.py index 3e769f2b6d51..30dcdc33522d 100644 --- a/mypyc/irbuild/main.py +++ b/mypyc/irbuild/main.py @@ -153,7 +153,7 @@ def transform_mypy_file(builder: IRBuilder, mypyfile: MypyFile) -> None: builder.enter("") # Make sure we have a builtins import - builder.gen_import("builtins", -1) + builder.gen_import("builtins", 0) # Generate ops. for node in mypyfile.defs: diff --git a/mypyc/irbuild/nonlocalcontrol.py b/mypyc/irbuild/nonlocalcontrol.py index 4a7136fbd18d..e0a23769770d 100644 --- a/mypyc/irbuild/nonlocalcontrol.py +++ b/mypyc/irbuild/nonlocalcontrol.py @@ -63,7 +63,7 @@ def gen_continue(self, builder: IRBuilder, line: int) -> None: assert False, "continue outside of loop" def gen_return(self, builder: IRBuilder, value: Value, line: int) -> None: - builder.add(Return(value)) + builder.add(Return(value, line)) class LoopNonlocalControl(NonlocalControl): @@ -193,7 +193,7 @@ def __init__(self, outer: NonlocalControl, saved: Value | AssignmentTarget) -> N self.saved = saved def gen_cleanup(self, builder: IRBuilder, line: int) -> None: - builder.call_c(restore_exc_info_op, [builder.read(self.saved)], line) + builder.call_c(restore_exc_info_op, [builder.read(self.saved, line)], line) class FinallyNonlocalControl(CleanupNonlocalControl): diff --git a/mypyc/irbuild/specialize.py b/mypyc/irbuild/specialize.py index bcc73f0ea93b..ca7e5a004f3e 100644 --- a/mypyc/irbuild/specialize.py +++ b/mypyc/irbuild/specialize.py @@ -530,8 +530,9 @@ def any_all_helper( modify: Callable[[Value], Value], new_value: Callable[[], Value], ) -> Value: - retval = Register(bool_rprimitive) - builder.assign(retval, initial_value(), -1) + init_val = initial_value() + retval = Register(bool_rprimitive, line=init_val.line) + builder.assign(retval, init_val, init_val.line) loop_params = list(zip(gen.indices, gen.sequences, gen.condlists, gen.is_async)) true_block, false_block, exit_block = BasicBlock(), BasicBlock(), BasicBlock() @@ -539,7 +540,8 @@ def gen_inner_stmts() -> None: comparison = modify(builder.accept(gen.left_expr)) builder.add_bool_branch(comparison, true_block, false_block) builder.activate_block(true_block) - builder.assign(retval, new_value(), -1) + new_val = new_value() + builder.assign(retval, new_val, new_val.line) builder.goto(exit_block) builder.activate_block(false_block) @@ -573,12 +575,16 @@ def translate_sum_call(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> V gen_expr = expr.args[0] target_type = builder.node_type(expr) - retval = Register(target_type) - builder.assign(retval, builder.coerce(builder.accept(start_expr), target_type, -1), -1) + retval = Register(target_type, line=expr.line) + builder.assign( + retval, builder.coerce(builder.accept(start_expr), target_type, expr.line), expr.line + ) def gen_inner_stmts() -> None: call_expr = builder.accept(gen_expr.left_expr) - builder.assign(retval, builder.binary_op(retval, call_expr, "+", -1), -1) + builder.assign( + retval, builder.binary_op(retval, call_expr, "+", call_expr.line), call_expr.line + ) loop_params = list( zip(gen_expr.indices, gen_expr.sequences, gen_expr.condlists, gen_expr.is_async) diff --git a/mypyc/irbuild/statement.py b/mypyc/irbuild/statement.py index d306124d8c0f..446165777058 100644 --- a/mypyc/irbuild/statement.py +++ b/mypyc/irbuild/statement.py @@ -160,7 +160,7 @@ def transform_expression_stmt(builder: IRBuilder, stmt: ExpressionStmt) -> None: # ExpressionStmts do not need to be coerced like other Expressions, so we shouldn't # call builder.accept here. stmt.expr.accept(builder.visitor) - builder.flush_keep_alives() + builder.flush_keep_alives(stmt.line) def transform_return_stmt(builder: IRBuilder, stmt: ReturnStmt) -> None: @@ -234,7 +234,7 @@ def transform_assignment_stmt(builder: IRBuilder, stmt: AssignmentStmt) -> None: for left, temp in zip(first_lvalue.items, temps): assignment_target = builder.get_assignment_target(left) builder.assign(assignment_target, temp, stmt.line) - builder.flush_keep_alives() + builder.flush_keep_alives(stmt.line) return line = stmt.rvalue.line @@ -254,11 +254,11 @@ def transform_assignment_stmt(builder: IRBuilder, stmt: AssignmentStmt) -> None: ): n = len(first_lvalue.items) borrows = [builder.add(TupleGet(rvalue_reg, i, borrow=True)) for i in range(n)] - builder.builder.keep_alive([rvalue_reg], steal=True) + builder.builder.keep_alive([rvalue_reg], line, steal=True) for lvalue_item, rvalue_item in zip(first_lvalue.items, borrows): rvalue_item = builder.add(Unborrow(rvalue_item)) builder.assign(builder.get_assignment_target(lvalue_item), rvalue_item, line) - builder.flush_keep_alives() + builder.flush_keep_alives(line) return for lvalue in lvalues: @@ -268,12 +268,12 @@ def transform_assignment_stmt(builder: IRBuilder, stmt: AssignmentStmt) -> None: builder, lvalue.base, [lvalue.index, stmt.rvalue], "__setitem__", lvalue ) if specialized is not None: - builder.flush_keep_alives() + builder.flush_keep_alives(lvalue.line) continue target = builder.get_assignment_target(lvalue) builder.assign(target, rvalue_reg, line) - builder.flush_keep_alives() + builder.flush_keep_alives(line) def is_simple_lvalue(expr: Expression) -> bool: @@ -302,7 +302,7 @@ def transform_operator_assignment_stmt(builder: IRBuilder, stmt: OperatorAssignm # usually operator assignments are done in-place # but when target doesn't support that we need to manually assign builder.assign(target, res, res.line) - builder.flush_keep_alives() + builder.flush_keep_alives(res.line) def import_globals_id_and_name(module_id: str, as_name: str | None) -> tuple[str, str]: @@ -378,8 +378,10 @@ def transform_import(builder: IRBuilder, node: Import) -> None: static_ptrs.append(builder.add(LoadAddress(object_pointer_rprimitive, mod_static))) mod_lines.append(Integer(import_node.line, c_pyssize_t_rprimitive)) - static_array_ptr = builder.builder.setup_rarray(object_pointer_rprimitive, static_ptrs) - import_line_ptr = builder.builder.setup_rarray(c_pyssize_t_rprimitive, mod_lines) + static_array_ptr = builder.builder.setup_rarray( + object_pointer_rprimitive, static_ptrs, node.line + ) + import_line_ptr = builder.builder.setup_rarray(c_pyssize_t_rprimitive, mod_lines, node.line) builder.call_c( import_many_op, [ @@ -552,7 +554,7 @@ def transform_try_except( type_f, type_line = type next_block, body_block = BasicBlock(), BasicBlock() matches = builder.call_c(exc_matches_op, [type_f()], type_line) - builder.add(Branch(matches, body_block, next_block, Branch.BOOL)) + builder.add(Branch(matches, body_block, next_block, Branch.BOOL, type_line)) builder.activate_block(body_block) if var: target = builder.get_assignment_target(var) @@ -574,14 +576,14 @@ def transform_try_except( # restore the saved exc_info information and continue propagating # the exception if it exists. builder.activate_block(cleanup_block) - builder.call_c(restore_exc_info_op, [builder.read(old_exc)], line) + builder.call_c(restore_exc_info_op, [builder.read(old_exc, line)], line) builder.goto(exit_block) # Cleanup for if we leave except through a raised exception: # restore the saved exc_info information and continue propagating # the exception. builder.activate_block(double_except_block) - builder.call_c(restore_exc_info_op, [builder.read(old_exc)], line) + builder.call_c(restore_exc_info_op, [builder.read(old_exc, line)], line) builder.call_c(keep_propagating_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) @@ -644,23 +646,24 @@ def try_finally_entry_blocks( finally_block: BasicBlock, ret_reg: Register | AssignmentTarget | None, ) -> Value: - old_exc = Register(exc_rtuple) + line = builder.fn_info.fitem.line + old_exc = Register(exc_rtuple, line=line) # Entry block for non-exceptional flow builder.activate_block(main_entry) if ret_reg: - builder.assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1])), -1) + builder.assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1], line)), line) builder.goto(return_entry) builder.activate_block(return_entry) - builder.add(Assign(old_exc, builder.add(LoadErrorValue(exc_rtuple)))) + builder.add(Assign(old_exc, builder.add(LoadErrorValue(exc_rtuple, line)), line)) builder.goto(finally_block) # Entry block for errors builder.activate_block(err_handler) if ret_reg: - builder.assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1])), -1) - builder.add(Assign(old_exc, builder.call_c(error_catch_op, [], -1))) + builder.assign(ret_reg, builder.add(LoadErrorValue(builder.ret_types[-1], line)), line) + builder.add(Assign(old_exc, builder.call_c(error_catch_op, [], line), line)) builder.goto(finally_block) return old_exc @@ -693,13 +696,14 @@ def try_finally_resolve_control( This means returning if there was a return, propagating exceptions, break/continue (soon), or just continuing on. """ + line = builder.fn_info.fitem.line reraise, rest = BasicBlock(), BasicBlock() - builder.add(Branch(old_exc, rest, reraise, Branch.IS_ERROR)) + builder.add(Branch(old_exc, rest, reraise, Branch.IS_ERROR, line)) # Reraise the exception if there was one builder.activate_block(reraise) builder.call_c(reraise_exception_op, [], NO_TRACEBACK_LINE_NO) - builder.add(Unreachable()) + builder.add(Unreachable(line)) builder.builder.pop_error_handler() # If there was a return, keep returning @@ -708,13 +712,13 @@ def try_finally_resolve_control( return_block, rest = BasicBlock(), BasicBlock() # For spill targets in try/finally, use nullable read to avoid AttributeError if isinstance(ret_reg, AssignmentTargetAttr) and ret_reg.attr.startswith(TEMP_ATTR_NAME): - ret_val = builder.read_nullable_attr(ret_reg.obj, ret_reg.attr, -1) + ret_val = builder.read_nullable_attr(ret_reg.obj, ret_reg.attr, line) else: - ret_val = builder.read(ret_reg) - builder.add(Branch(ret_val, rest, return_block, Branch.IS_ERROR)) + ret_val = builder.read(ret_reg, line) + builder.add(Branch(ret_val, rest, return_block, Branch.IS_ERROR, line)) builder.activate_block(return_block) - builder.nonlocal_control[-1].gen_return(builder, ret_val, -1) + builder.nonlocal_control[-1].gen_return(builder, ret_val, line) # TODO: handle break/continue builder.activate_block(rest) @@ -723,7 +727,7 @@ def try_finally_resolve_control( # If there was an exception, restore again builder.activate_block(cleanup_block) - finally_control.gen_cleanup(builder, -1) + finally_control.gen_cleanup(builder, line) builder.call_c(keep_propagating_op, [], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) @@ -843,7 +847,7 @@ def transform_try_finally_stmt_async( return_block, check_old_exc = BasicBlock(), BasicBlock() builder.add( Branch( - builder.read(ret_reg, allow_error_value=True), + builder.read(ret_reg, line, allow_error_value=True), check_old_exc, return_block, Branch.IS_ERROR, @@ -851,7 +855,7 @@ def transform_try_finally_stmt_async( ) builder.activate_block(return_block) - builder.nonlocal_control[-1].gen_return(builder, builder.read(ret_reg), -1) + builder.nonlocal_control[-1].gen_return(builder, builder.read(ret_reg, line), line) builder.activate_block(check_old_exc) @@ -928,8 +932,9 @@ def transform_try_body() -> None: def get_sys_exc_info(builder: IRBuilder) -> list[Value]: - exc_info = builder.call_c(get_exc_info_op, [], -1) - return [builder.add(TupleGet(exc_info, i, -1)) for i in range(3)] + line = builder.fn_info.fitem.line + exc_info = builder.call_c(get_exc_info_op, [], line) + return [builder.add(TupleGet(exc_info, i, line)) for i in range(3)] def transform_with( @@ -972,7 +977,7 @@ def maybe_natively_call_exit(exc_info: bool) -> Value: if is_native: assert isinstance(mgr_v.type, RInstance), mgr_v.type exit_val = builder.gen_method_call( - builder.read(mgr), + builder.read(mgr, line), f"__{al}exit__", arg_values=args, line=line, @@ -980,7 +985,9 @@ def maybe_natively_call_exit(exc_info: bool) -> Value: ) else: assert exit_ is not None - exit_val = builder.py_call(builder.read(exit_), [builder.read(mgr)] + args, line) + exit_val = builder.py_call( + builder.read(exit_, line), [builder.read(mgr, line)] + args, line + ) if is_async: return emit_await(builder, exit_val, line) @@ -1003,7 +1010,7 @@ def except_body() -> None: def finally_body() -> None: out_block, exit_block = BasicBlock(), BasicBlock() - builder.add(Branch(builder.read(exc), exit_block, out_block, Branch.BOOL)) + builder.add(Branch(builder.read(exc, line), exit_block, out_block, Branch.BOOL)) builder.activate_block(exit_block) maybe_natively_call_exit(exc_info=False) @@ -1150,7 +1157,7 @@ def emit_yield_from_or_await( # Calling a generated generator, so avoid raising StopIteration by passing # an extra PyObject ** argument to helper where the stop iteration value is stored. fast_path = True - obj = builder.read(iter_reg) + obj = builder.read(iter_reg, line) nn = builder.none_object() stop_iter_val = Register(object_rprimitive) err = builder.add(LoadErrorValue(object_rprimitive, undefines=True)) @@ -1162,7 +1169,7 @@ def emit_yield_from_or_await( _y_init = builder.add(m) else: fast_path = False - _y_init = builder.call_c(next_raw_op, [builder.read(iter_reg)], line) + _y_init = builder.call_c(next_raw_op, [builder.read(iter_reg, line)], line) builder.add(Branch(_y_init, stop_block, main_block, Branch.IS_ERROR)) @@ -1188,7 +1195,9 @@ def emit_yield_from_or_await( builder.goto_and_activate(loop_block) def try_body() -> None: - builder.assign(received_reg, emit_yield(builder, builder.read(to_yield_reg), line), line) + builder.assign( + received_reg, emit_yield(builder, builder.read(to_yield_reg, line), line), line + ) def except_body() -> None: # The body of the except is all implemented in a C function to @@ -1196,7 +1205,9 @@ def except_body() -> None: # indicating whether to break or yield (or raise an exception). val = Register(object_rprimitive) val_address = builder.add(LoadAddress(object_pointer_rprimitive, val)) - to_stop = builder.call_c(yield_from_except_op, [builder.read(iter_reg), val_address], line) + to_stop = builder.call_c( + yield_from_except_op, [builder.read(iter_reg, line), val_address], line + ) ok, stop = BasicBlock(), BasicBlock() builder.add(Branch(to_stop, stop, ok, Branch.BOOL)) @@ -1214,7 +1225,9 @@ def except_body() -> None: def else_body() -> None: # Do a next() or a .send(). It will return NULL on exception # but it won't automatically propagate. - _y = builder.call_c(send_op, [builder.read(iter_reg), builder.read(received_reg)], line) + _y = builder.call_c( + send_op, [builder.read(iter_reg, line), builder.read(received_reg, line)], line + ) ok, stop = BasicBlock(), BasicBlock() builder.add(Branch(_y, stop, ok, Branch.IS_ERROR)) @@ -1234,7 +1247,7 @@ def else_body() -> None: builder.pop_loop_stack() builder.goto_and_activate(done_block) - return builder.read(result) + return builder.read(result, line) def emit_await(builder: IRBuilder, val: Value, line: int) -> Value: diff --git a/mypyc/test-data/exceptions.test b/mypyc/test-data/exceptions.test index 18983b2c92e9..9e097a18d103 100644 --- a/mypyc/test-data/exceptions.test +++ b/mypyc/test-data/exceptions.test @@ -485,7 +485,7 @@ L4: if is_error(v) goto L16 else goto L7 L5: r8 = raise UnboundLocalError('local variable "v" referenced before assignment') - if not r8 goto L12 (error at f:-1) else goto L6 :: bool + if not r8 goto L12 (error at f:7) else goto L6 :: bool L6: unreachable L7: @@ -498,7 +498,7 @@ L8: if is_error(v) goto L9 else goto L11 L9: r12 = raise UnboundLocalError('local variable "v" referenced before assignment') - if not r12 goto L12 (error at f:-1) else goto L10 :: bool + if not r12 goto L12 (error at f:7) else goto L10 :: bool L10: unreachable L11: @@ -667,7 +667,7 @@ L2: if r4 goto L3 else goto L5 :: bool L3: r5 = raise UnboundLocalError('local variable "y" referenced before assignment') - if not r5 goto L6 (error at f:-1) else goto L4 :: bool + if not r5 goto L6 (error at f:6) else goto L4 :: bool L4: unreachable L5: diff --git a/mypyc/transform/exceptions.py b/mypyc/transform/exceptions.py index 28bbd80c52cc..165d3adf19c5 100644 --- a/mypyc/transform/exceptions.py +++ b/mypyc/transform/exceptions.py @@ -130,6 +130,9 @@ def split_blocks_at_errors( ) branch.negated = negated if op.line != NO_TRACEBACK_LINE_NO and func_name is not None: + assert ( + op.line >= 0 + ), f"Cannot add a traceback entry with a negative line number for op {op}" branch.traceback_entry = (func_name, op.line) cur_block.ops.append(branch) cur_block = new_block diff --git a/mypyc/transform/uninit.py b/mypyc/transform/uninit.py index 45b403588f8e..813e568de023 100644 --- a/mypyc/transform/uninit.py +++ b/mypyc/transform/uninit.py @@ -104,6 +104,9 @@ def split_blocks_at_uninits( op.line, ) + assert ( + op.line >= 0 + ), f"Cannot raise an error with a negative line number for op {op}" raise_std = RaiseStandardError( RaiseStandardError.UNBOUND_LOCAL_ERROR, f'local variable "{src.name}" referenced before assignment',