def compare_strings(self, lhs: Value, rhs: Value, op: str, line: int) -> Value: """Compare two strings""" compare_result = self.call_c(unicode_compare, [lhs, rhs], line) error_constant = self.add(LoadInt(-1, line, c_int_rprimitive)) compare_error_check = self.add( ComparisonOp(compare_result, error_constant, ComparisonOp.EQ, line)) exception_check, propagate, final_compare = BasicBlock(), BasicBlock( ), BasicBlock() branch = Branch(compare_error_check, exception_check, final_compare, Branch.BOOL_EXPR) branch.negated = False self.add(branch) self.activate_block(exception_check) check_error_result = self.call_c(err_occurred_op, [], line) null = self.add(LoadInt(0, line, pointer_rprimitive)) compare_error_check = self.add( ComparisonOp(check_error_result, null, ComparisonOp.NEQ, line)) branch = Branch(compare_error_check, propagate, final_compare, Branch.BOOL_EXPR) branch.negated = False self.add(branch) self.activate_block(propagate) self.call_c(keep_propagating_op, [], line) self.goto(final_compare) self.activate_block(final_compare) op_type = ComparisonOp.EQ if op == '==' else ComparisonOp.NEQ return self.add( ComparisonOp(compare_result, self.add(LoadInt(0, line, c_int_rprimitive)), op_type, line))
def check_tagged_short_int(self, val: Value, line: int) -> Value: """Check if a tagged integer is a short integer""" int_tag = self.add(LoadInt(1, line, rtype=c_pyssize_t_rprimitive)) bitwise_and = self.binary_int_op(c_pyssize_t_rprimitive, val, int_tag, BinaryIntOp.AND, line) zero = self.add(LoadInt(0, line, rtype=c_pyssize_t_rprimitive)) check = self.binary_int_op(bool_rprimitive, bitwise_and, zero, BinaryIntOp.EQ, line) return check
def init(self, expr_reg: Value, target_type: RType, reverse: bool) -> None: builder = self.builder self.reverse = reverse # Define target to contain the expression, along with the index that will be used # for the for-loop. If we are inside of a generator function, spill these into the # environment class. self.expr_target = builder.maybe_spill(expr_reg) if not reverse: index_reg = builder.add(LoadInt(0)) else: index_reg = builder.binary_op(self.load_len(), builder.add(LoadInt(1)), '-', self.line) self.index_target = builder.maybe_spill_assignable(index_reg) self.target_type = target_type
def load_static_int(self, value: int) -> Value: """Loads a static integer Python 'int' object into a register.""" if abs(value) > MAX_LITERAL_SHORT_INT: identifier = self.literal_static_name(value) return self.add(LoadGlobal(int_rprimitive, identifier, ann=value)) else: return self.add(LoadInt(value))
def test_register(self) -> None: op = LoadInt(5) self.block.ops.append(op) fn = FuncIR( FuncDecl('myfunc', None, 'mod', FuncSignature([self.arg], list_rprimitive)), [self.reg], [self.block]) value_names = generate_names_for_ir(fn.arg_regs, fn.blocks) emitter = Emitter(EmitterContext(NameGenerator([['mod']])), value_names) generate_native_function(fn, emitter, 'prog.py', 'prog', optimize_int=False) result = emitter.fragments assert_string_arrays_equal([ 'PyObject *CPyDef_myfunc(CPyTagged cpy_r_arg) {\n', ' CPyTagged cpy_r_i0;\n', 'CPyL0: ;\n', ' cpy_r_i0 = 10;\n', '}\n', ], result, msg='Generated code invalid')
def gen_return(self, builder: 'IRBuilder', value: Value, line: int) -> None: # Assign an invalid next label number so that the next time # __next__ is called, we jump to the case in which # StopIteration is raised. builder.assign(builder.fn_info.generator_class.next_label_target, builder.add(LoadInt(-1)), line) # Raise a StopIteration containing a field for the value that # should be returned. Before doing so, create a new block # without an error handler set so that the implicitly thrown # StopIteration isn't caught by except blocks inside of the # generator function. builder.builder.push_error_handler(None) builder.goto_and_activate(BasicBlock()) # Skip creating a traceback frame when we raise here, because # we don't care about the traceback frame and it is kind of # expensive since raising StopIteration is an extremely common # case. Also we call a special internal function to set # StopIteration instead of using RaiseStandardError because # the obvious thing doesn't work if the value is a tuple # (???). builder.primitive_op(set_stop_iteration_value, [value], NO_TRACEBACK_LINE_NO) builder.add(Unreachable()) builder.builder.pop_error_handler()
def test_assign(self) -> None: reg = register('foo') op1 = LoadInt(2) op2 = Assign(reg, op1) op3 = Assign(reg, op1) block = make_block([op1, op2, op3]) assert generate_names_for_ir([reg], [block]) == {op1: 'i0', reg: 'foo'}
def gen_step(self) -> None: builder = self.builder line = self.line # Increment curr_index register. If the range is known to fit in short ints, use # short ints. if (is_short_int_rprimitive(self.start_reg.type) and is_short_int_rprimitive(self.end_reg.type)): new_val = builder.binary_int_op(short_int_rprimitive, builder.read(self.index_reg, line), builder.add(LoadInt(self.step)), BinaryIntOp.ADD, line) else: new_val = builder.binary_op( builder.read(self.index_reg, line), builder.add(LoadInt(self.step)), '+', line) builder.assign(self.index_reg, new_val, line) builder.assign(self.index_target, new_val, line)
def gen_step(self) -> None: # Step to the next item. builder = self.builder line = self.line step = 1 if not self.reverse else -1 builder.assign(self.index_target, builder.primitive_op( unsafe_short_add, [builder.read(self.index_target, line), builder.add(LoadInt(step))], line), line)
def init(self) -> None: builder = self.builder # Create a register to store the state of the loop index and # initialize this register along with the loop index to 0. zero = builder.add(LoadInt(0)) self.index_reg = builder.maybe_spill_assignable(zero) self.index_target = builder.get_assignment_target( self.index) # type: Union[Register, AssignmentTarget] builder.assign(self.index_target, zero, self.line)
def gen_step(self) -> None: # Step to the next item. builder = self.builder line = self.line step = 1 if not self.reverse else -1 add = builder.binary_int_op(short_int_rprimitive, builder.read(self.index_target, line), builder.add(LoadInt(step)), BinaryIntOp.ADD, line) builder.assign(self.index_target, add, line)
def add_bool_branch(self, value: Value, true: BasicBlock, false: BasicBlock) -> None: if is_runtime_subtype(value.type, int_rprimitive): zero = self.add(LoadInt(0)) value = self.binary_op(value, zero, '!=', value.line) elif is_same_type(value.type, list_rprimitive): length = self.primitive_op(list_len_op, [value], value.line) zero = self.add(LoadInt(0)) value = self.binary_op(length, zero, '!=', value.line) elif (isinstance(value.type, RInstance) and value.type.class_ir.is_ext_class and value.type.class_ir.has_method('__bool__')): # Directly call the __bool__ method on classes that have it. value = self.gen_method_call(value, '__bool__', [], bool_rprimitive, value.line) else: value_type = optional_value_type(value.type) if value_type is not None: is_none = self.binary_op(value, self.none_object(), 'is not', value.line) branch = Branch(is_none, true, false, Branch.BOOL_EXPR) self.add(branch) always_truthy = False if isinstance(value_type, RInstance): # check whether X.__bool__ is always just the default (object.__bool__) if (not value_type.class_ir.has_method('__bool__') and value_type.class_ir.is_method_final('__bool__')): always_truthy = True if not always_truthy: # Optional[X] where X may be falsey and requires a check branch.true = BasicBlock() self.activate_block(branch.true) # unbox_or_cast instead of coerce because we want the # type to change even if it is a subtype. remaining = self.unbox_or_cast(value, value_type, value.line) self.add_bool_branch(remaining, true, false) return elif not is_same_type(value.type, bool_rprimitive): value = self.primitive_op(bool_op, [value], value.line) self.add(Branch(value, true, false, Branch.BOOL_EXPR))
def gen_step(self) -> None: builder = self.builder line = self.line # We can safely assume that the integer is short, since we are not going to wrap # around a 63-bit integer. # NOTE: This would be questionable if short ints could be 32 bits. new_val = builder.binary_int_op(short_int_rprimitive, builder.read(self.index_reg, line), builder.add(LoadInt(1)), BinaryIntOp.ADD, line) builder.assign(self.index_reg, new_val, line) builder.assign(self.index_target, new_val, line)
def translate_len(builder: IRBuilder, expr: CallExpr, callee: RefExpr) -> Optional[Value]: # Special case builtins.len if (len(expr.args) == 1 and expr.arg_kinds == [ARG_POS]): expr_rtype = builder.node_type(expr.args[0]) if isinstance(expr_rtype, RTuple): # len() of fixed-length tuple can be trivially determined statically, # though we still need to evaluate it. builder.accept(expr.args[0]) return builder.add(LoadInt(len(expr_rtype.types))) return None
def _create_dict(self, keys: List[Value], values: List[Value], line: int) -> Value: """Create a dictionary(possibly empty) using keys and values""" # keys and values should have the same number of items size = len(keys) if size > 0: load_size_op = self.add(LoadInt(size, -1, c_pyssize_t_rprimitive)) # merge keys and values items = [i for t in list(zip(keys, values)) for i in t] return self.call_c(dict_build_op, [load_size_op] + items, line) else: return self.call_c(dict_new_op, [], line)
def init(self, expr_reg: Value, target_type: RType) -> None: builder = self.builder self.target_type = target_type # We add some variables to environment class, so they can be read across yield. self.expr_target = builder.maybe_spill(expr_reg) offset_reg = builder.add(LoadInt(0)) self.offset_target = builder.maybe_spill_assignable(offset_reg) self.size = builder.maybe_spill(self.load_len(self.expr_target)) # For dict class (not a subclass) this is the dictionary itself. iter_reg = builder.call_c(self.dict_iter_op, [expr_reg], self.line) self.iter_target = builder.maybe_spill(iter_reg)
def builtin_len(self, val: Value, line: int) -> Value: typ = val.type if is_list_rprimitive(typ) or is_tuple_rprimitive(typ): elem_address = self.add(GetElementPtr(val, PyVarObject, 'ob_size')) size_value = self.add(LoadMem(c_pyssize_t_rprimitive, elem_address, val)) offset = self.add(LoadInt(1, line, rtype=c_pyssize_t_rprimitive)) return self.binary_int_op(short_int_rprimitive, size_value, offset, BinaryIntOp.LEFT_SHIFT, line) elif is_dict_rprimitive(typ): size_value = self.call_c(dict_size_op, [val], line) offset = self.add(LoadInt(1, line, rtype=c_pyssize_t_rprimitive)) return self.binary_int_op(short_int_rprimitive, size_value, offset, BinaryIntOp.LEFT_SHIFT, line) elif is_set_rprimitive(typ): elem_address = self.add(GetElementPtr(val, PySetObject, 'used')) size_value = self.add(LoadMem(c_pyssize_t_rprimitive, elem_address, val)) offset = self.add(LoadInt(1, line, rtype=c_pyssize_t_rprimitive)) return self.binary_int_op(short_int_rprimitive, size_value, offset, BinaryIntOp.LEFT_SHIFT, line) # generic case else: return self.call_c(generic_len_op, [val], line)
def populate_switch_for_generator_class(builder: IRBuilder) -> None: cls = builder.fn_info.generator_class line = builder.fn_info.fitem.line builder.activate_block(cls.switch_block) for label, true_block in enumerate(cls.continuation_blocks): false_block = BasicBlock() comparison = builder.binary_op(cls.next_label_reg, builder.add(LoadInt(label)), '==', line) builder.add_bool_branch(comparison, true_block, false_block) builder.activate_block(false_block) builder.add( RaiseStandardError(RaiseStandardError.STOP_ITERATION, None, line)) builder.add(Unreachable())
def call_c(self, desc: CFunctionDescription, args: List[Value], line: int, result_type: Optional[RType] = None) -> Value: # handle void function via singleton RVoid instance coerced = [] # coerce fixed number arguments for i in range(min(len(args), len(desc.arg_types))): formal_type = desc.arg_types[i] arg = args[i] arg = self.coerce(arg, formal_type, line) coerced.append(arg) # reorder args if necessary if desc.ordering is not None: assert desc.var_arg_type is None coerced = [coerced[i] for i in desc.ordering] # coerce any var_arg var_arg_idx = -1 if desc.var_arg_type is not None: var_arg_idx = len(desc.arg_types) for i in range(len(desc.arg_types), len(args)): arg = args[i] arg = self.coerce(arg, desc.var_arg_type, line) coerced.append(arg) # add extra integer constant if any for item in desc.extra_int_constants: val, typ = item extra_int_constant = self.add(LoadInt(val, line, rtype=typ)) coerced.append(extra_int_constant) target = self.add( CallC(desc.c_function_name, coerced, desc.return_type, desc.steals, desc.is_borrowed, desc.error_kind, line, var_arg_idx)) if desc.truncated_type is None: result = target else: truncate = self.add( Truncate(target, desc.return_type, desc.truncated_type)) result = truncate if result_type and not is_runtime_subtype(result.type, result_type): if is_none_rprimitive(result_type): # Special case None return. The actual result may actually be a bool # and so we can't just coerce it. result = self.none() else: result = self.coerce(target, result_type, line) return result
def emit_yield(builder: IRBuilder, val: Value, line: int) -> Value: retval = builder.coerce(val, builder.ret_types[-1], line) cls = builder.fn_info.generator_class # Create a new block for the instructions immediately following the yield expression, and # set the next label so that the next time '__next__' is called on the generator object, # the function continues at the new block. next_block = BasicBlock() next_label = len(cls.continuation_blocks) cls.continuation_blocks.append(next_block) builder.assign(cls.next_label_target, builder.add(LoadInt(next_label)), line) builder.add(Return(retval)) builder.activate_block(next_block) add_raise_exception_blocks_to_generator_class(builder, line) assert cls.send_arg_reg is not None return cls.send_arg_reg
def gen_condition(self) -> None: builder = self.builder line = self.line if self.reverse: # If we are iterating in reverse order, we obviously need # to check that the index is still positive. Somewhat less # obviously we still need to check against the length, # since it could shrink out from under us. comparison = builder.binary_op(builder.read(self.index_target, line), builder.add(LoadInt(0)), '>=', line) second_check = BasicBlock() builder.add_bool_branch(comparison, second_check, self.loop_exit) builder.activate_block(second_check) # For compatibility with python semantics we recalculate the length # at every iteration. len_reg = self.load_len() comparison = builder.binary_op(builder.read(self.index_target, line), len_reg, '<', line) builder.add_bool_branch(comparison, self.body_block, self.loop_exit)
def test_register(self) -> None: self.env.temp_index = 0 op = LoadInt(5) self.block.ops.append(op) self.env.add_op(op) fn = FuncIR(FuncDecl('myfunc', None, 'mod', FuncSignature([self.arg], list_rprimitive)), [self.block], self.env) emitter = Emitter(EmitterContext(NameGenerator([['mod']]))) generate_native_function(fn, emitter, 'prog.py', 'prog', False) result = emitter.fragments assert_string_arrays_equal( [ 'PyObject *CPyDef_myfunc(CPyTagged cpy_r_arg) {\n', ' CPyTagged cpy_r_i0;\n', 'CPyL0: ;\n', ' cpy_r_i0 = 10;\n', '}\n', ], result, msg='Generated code invalid')
def instantiate_generator_class(builder: IRBuilder) -> Value: fitem = builder.fn_info.fitem generator_reg = builder.add( Call(builder.fn_info.generator_class.ir.ctor, [], fitem.line)) # Get the current environment register. If the current function is nested, then the # generator class gets instantiated from the callable class' '__call__' method, and hence # we use the callable class' environment register. Otherwise, we use the original # function's environment register. if builder.fn_info.is_nested: curr_env_reg = builder.fn_info.callable_class.curr_env_reg else: curr_env_reg = builder.fn_info.curr_env_reg # Set the generator class' environment attribute to point at the environment class # defined in the current scope. builder.add(SetAttr(generator_reg, ENV_ATTR_NAME, curr_env_reg, fitem.line)) # Set the generator class' environment class' NEXT_LABEL_ATTR_NAME attribute to 0. zero_reg = builder.add(LoadInt(0)) builder.add( SetAttr(curr_env_reg, NEXT_LABEL_ATTR_NAME, zero_reg, fitem.line)) return generator_reg
def split_blocks_at_errors(blocks: List[BasicBlock], default_error_handler: BasicBlock, func_name: Optional[str]) -> List[BasicBlock]: new_blocks = [] # type: List[BasicBlock] # First split blocks on ops that may raise. for block in blocks: ops = block.ops block.ops = [] cur_block = block new_blocks.append(cur_block) # If the block has an error handler specified, use it. Otherwise # fall back to the default. error_label = block.error_handler or default_error_handler block.error_handler = None for op in ops: target = op cur_block.ops.append(op) if isinstance(op, RegisterOp) and op.error_kind != ERR_NEVER: # Split new_block = BasicBlock() new_blocks.append(new_block) if op.error_kind == ERR_MAGIC: # Op returns an error value on error that depends on result RType. variant = Branch.IS_ERROR negated = False elif op.error_kind == ERR_FALSE: # Op returns a C false value on error. variant = Branch.BOOL negated = True elif op.error_kind == ERR_ALWAYS: variant = Branch.BOOL negated = True # this is a hack to represent the always fail # semantics, using a temporary bool with value false tmp = LoadInt(0, rtype=bool_rprimitive) cur_block.ops.append(tmp) target = tmp else: assert False, 'unknown error kind %d' % op.error_kind # Void ops can't generate errors since error is always # indicated by a special value stored in a register. if op.error_kind != ERR_ALWAYS: assert not op.is_void, "void op generating errors?" branch = Branch(target, true_label=error_label, false_label=new_block, op=variant, line=op.line) branch.negated = negated if op.line != NO_TRACEBACK_LINE_NO and func_name is not None: branch.traceback_entry = (func_name, op.line) cur_block.ops.append(branch) cur_block = new_block return new_blocks
def false(self) -> Value: """Load unboxed False value (type: bool_rprimitive).""" return self.add(LoadInt(0, -1, bool_rprimitive))
def unary_not(self, value: Value, line: int) -> Value: mask = self.add(LoadInt(1, line, rtype=bool_rprimitive)) return self.binary_int_op(bool_rprimitive, value, mask, BinaryIntOp.XOR, line)
def new_tuple(self, items: List[Value], line: int) -> Value: load_size_op = self.add(LoadInt(len(items), -1, c_pyssize_t_rprimitive)) return self.call_c(new_tuple_op, [load_size_op] + items, 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 process_iterator_tuple_assignment(self, target: AssignmentTargetTuple, rvalue_reg: Value, line: int) -> None: iterator = self.primitive_op(iter_op, [rvalue_reg], line) # This may be the whole lvalue list if there is no starred value split_idx = target.star_idx if target.star_idx is not None else len( target.items) # Assign values before the first starred value for litem in target.items[:split_idx]: ritem = self.primitive_op(next_op, [iterator], line) error_block, ok_block = BasicBlock(), BasicBlock() self.add(Branch(ritem, error_block, ok_block, Branch.IS_ERROR)) self.activate_block(error_block) self.add( RaiseStandardError(RaiseStandardError.VALUE_ERROR, 'not enough values to unpack', line)) self.add(Unreachable()) self.activate_block(ok_block) self.assign(litem, ritem, line) # Assign the starred value and all values after it if target.star_idx is not None: post_star_vals = target.items[split_idx + 1:] iter_list = self.primitive_op(to_list, [iterator], line) iter_list_len = self.primitive_op(list_len_op, [iter_list], line) post_star_len = self.add(LoadInt(len(post_star_vals))) condition = self.binary_op(post_star_len, iter_list_len, '<=', line) error_block, ok_block = BasicBlock(), BasicBlock() self.add(Branch(condition, ok_block, error_block, Branch.BOOL_EXPR)) self.activate_block(error_block) self.add( RaiseStandardError(RaiseStandardError.VALUE_ERROR, 'not enough values to unpack', line)) self.add(Unreachable()) self.activate_block(ok_block) for litem in reversed(post_star_vals): ritem = self.primitive_op(list_pop_last, [iter_list], line) self.assign(litem, ritem, line) # Assign the starred value self.assign(target.items[target.star_idx], iter_list, line) # There is no starred value, so check if there are extra values in rhs that # have not been assigned. else: extra = self.primitive_op(next_op, [iterator], line) error_block, ok_block = BasicBlock(), BasicBlock() self.add(Branch(extra, ok_block, error_block, Branch.IS_ERROR)) self.activate_block(error_block) self.add( RaiseStandardError(RaiseStandardError.VALUE_ERROR, 'too many values to unpack', line)) self.add(Unreachable()) self.activate_block(ok_block)
def test_load_int(self) -> None: self.assert_emit(LoadInt(5), "cpy_r_i0 = 10;") self.assert_emit(LoadInt(5, -1, c_int_rprimitive), "cpy_r_i0 = 5;")