def combine_states(self, left, right): """ Args: left: right: Returns: """ state = State(left.name, [left], left.type, 'branch', self.locate(), read=left.read, set=left.set, over=left.over, over_position=left.over_position) if right is None: state.read = 'no' if left.read == 'no' else 'maybe' state.set = 'no' if left.set == 'no' else 'maybe' state.over = 'no' if left.over == 'no' else 'maybe' else: if not are_types_equal(left.type, right.type): self._issue(type_changes(self.locate(), left.name, left.type, right.type)) state.read = self.match_rso(left.read, right.read) state.set = self.match_rso(left.set, right.set) state.over = self.match_rso(left.over, right.over) if left.over == 'no': state.over_position = right.over_position state.trace.append(right) return state
def store_variable(self, name, store_type, position=None, force_create=False): """ Update the variable with the given name to now have the new type. Args: name (str): The unqualified name of the variable. The variable will be assumed to be in the current scope. store_type (Type): The new type of this variable. position: The location that this store occurred at Returns: State: The new state of the variable. """ if position is None: position = self.locate() full_name = self._scope_chain_str(name) current_path = self.path_chain[0] variable = self.find_variable_scope(name) if not variable.exists or force_create: # Create a new instance of the variable on the current path new_state = State(name, [], store_type, 'store', position, read='no', set='yes', over='no') self.name_map[current_path][full_name] = new_state else: new_state = self.trace_state(variable.state, "store", position) if not variable.in_scope: self._issue( write_out_of_scope(self.locate(), name, report=self.report)) # Type change? if not are_types_equal(store_type, variable.state.type): self._issue( type_changes(position, name, variable.state.type, store_type)) new_state.type = store_type # Overwritten? if variable.state.set == 'yes' and variable.state.read == 'no': new_state.over_position = position new_state.over = 'yes' else: new_state.set = 'yes' new_state.read = 'no' self.name_map[current_path][full_name] = new_state # If this is a class scope... current_scope = self.scope_chain[0] if current_scope in self.class_scopes: self.class_scopes[current_scope].add_attr(name, new_state.type) return new_state
def assign_target(self, target, target_type): """ Assign the type to the target, handling all kinds of assignment statements, including Names, Tuples/Lists, Subscripts, and Attributes. Args: target (ast.AST): The target AST Node. target_type (Type): The TIFA type. Returns: """ if isinstance(target, ast.Name): self.store_variable(target.id, target_type) elif isinstance(target, (ast.Tuple, ast.List)): for i, elt in enumerate(target.elts): elt_type = target_type.iterate(LiteralNum(i)) self.assign_target(elt, elt_type) elif isinstance(target, ast.Subscript): left_hand_type = self.visit(target.value) if isinstance(left_hand_type, ListType): # TODO: Handle updating value in list pass elif isinstance(left_hand_type, DictType): # TODO: Update this for Python 3.9, now that Slice notation has changed if not isinstance(target.slice, ast.Index): # TODO: Can't subscript a dictionary assignment return None literal = self.get_literal(target.slice.value) if not literal: key_type = self.visit(target.slice.value) left_hand_type.empty = False left_hand_type.keys = [key_type.clone()] left_hand_type.values = [target_type.clone()] elif left_hand_type.literals: original_type = left_hand_type.has_literal(literal) if not original_type: left_hand_type.update_key(literal, target_type.clone()) elif not are_types_equal(original_type, target_type): # TODO: Fix "Dictionary" to be the name of the variable self._issue( type_changes(self.locate(), 'Dictionary', original_type, target_type)) elif isinstance(target, ast.Attribute): left_hand_type = self.visit(target.value) if isinstance(left_hand_type, InstanceType): left_hand_type.add_attr(target.attr, target_type)