def test_runtime_subtype(self) -> None: # right type to check with r = RStruct("Foo", ["a", "b"], [bool_rprimitive, int_rprimitive]) # using the exact same fields r1 = RStruct("Foo", ["a", "b"], [bool_rprimitive, int_rprimitive]) # names different r2 = RStruct("Bar", ["c", "b"], [bool_rprimitive, int_rprimitive]) # name different r3 = RStruct("Baz", ["a", "b"], [bool_rprimitive, int_rprimitive]) # type different r4 = RStruct("FooBar", ["a", "b"], [bool_rprimitive, int32_rprimitive]) # number of types different r5 = RStruct("FooBarBaz", ["a", "b", "c"], [bool_rprimitive, int_rprimitive, bool_rprimitive]) assert is_runtime_subtype(r1, r) is True assert is_runtime_subtype(r2, r) is False assert is_runtime_subtype(r3, r) is False assert is_runtime_subtype(r4, r) is False assert is_runtime_subtype(r5, r) is False
def test_runtime_subtype(self) -> None: # right type to check with info = StructInfo("Foo", ["a", "b"], [bool_rprimitive, int_rprimitive]) r = RStruct(info) # using the same StructInfo r1 = RStruct(info) # names different info2 = StructInfo("Bar", ["c", "b"], [bool_rprimitive, int_rprimitive]) r2 = RStruct(info2) # name different info3 = StructInfo("Baz", ["a", "b"], [bool_rprimitive, int_rprimitive]) r3 = RStruct(info3) # type different info4 = StructInfo("FooBar", ["a", "b"], [bool_rprimitive, int32_rprimitive]) r4 = RStruct(info4) # number of types different info5 = StructInfo("FooBarBaz", ["a", "b", "c"], [bool_rprimitive, int_rprimitive, bool_rprimitive]) r5 = RStruct(info5) assert is_runtime_subtype(r1, r) is True assert is_runtime_subtype(r2, r) is False assert is_runtime_subtype(r3, r) is False assert is_runtime_subtype(r4, r) is False assert is_runtime_subtype(r5, r) is False
def matching_primitive_op(self, candidates: List[OpDescription], args: List[Value], line: int, result_type: Optional[RType] = None) -> Optional[Value]: # Find the highest-priority primitive op that matches. matching = None # type: Optional[OpDescription] for desc in candidates: if len(desc.arg_types) != len(args): continue if all(is_subtype(actual.type, formal) for actual, formal in zip(args, desc.arg_types)): if matching: assert matching.priority != desc.priority, 'Ambiguous:\n1) %s\n2) %s' % ( matching, desc) if desc.priority > matching.priority: matching = desc else: matching = desc if matching: target = self.primitive_op(matching, args, line) if result_type and not is_runtime_subtype(target.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. target = self.none() else: target = self.coerce(target, result_type, line) return target return None
def coerce(self, src: Value, target_type: RType, line: int, force: bool = False) -> Value: """Generate a coercion/cast from one type to other (only if needed). For example, int -> object boxes the source int; int -> int emits nothing; object -> int unboxes the object. All conversions preserve object value. If force is true, always generate an op (even if it is just an assignment) so that the result will have exactly target_type as the type. Returns the register with the converted value (may be same as src). """ if src.type.is_unboxed and not target_type.is_unboxed: return self.box(src) if ((src.type.is_unboxed and target_type.is_unboxed) and not is_runtime_subtype(src.type, target_type)): # To go from one unboxed type to another, we go through a boxed # in-between value, for simplicity. tmp = self.box(src) return self.unbox_or_cast(tmp, target_type, line) if ((not src.type.is_unboxed and target_type.is_unboxed) or not is_subtype(src.type, target_type)): return self.unbox_or_cast(src, target_type, line) elif force: tmp = self.alloc_temp(target_type) self.add(Assign(tmp, src)) return tmp return src
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) # 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) target = self.add( CallC(desc.c_function_name, coerced, desc.return_type, desc.steals, desc.error_kind, line, var_arg_idx)) if result_type and not is_runtime_subtype(target.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. target = self.none() else: target = self.coerce(target, result_type, line) return target
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 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 call_c(self, desc: CFunctionDescription, args: List[Value], line: int, result_type: Optional[RType] = None) -> Value: # handle void function via singleton RVoid instance coerced = [] for i, arg in enumerate(args): formal_type = desc.arg_types[i] arg = self.coerce(arg, formal_type, line) coerced.append(arg) target = self.add(CallC(desc.c_function_name, coerced, desc.return_type, desc.steals, desc.error_kind, line)) if result_type and not is_runtime_subtype(target.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. target = self.none() else: target = self.coerce(target, result_type, line) return target
def test_bool(self) -> None: assert not is_runtime_subtype(bool_rprimitive, bit_rprimitive) assert not is_runtime_subtype(bool_rprimitive, int_rprimitive)