def visit_Compare(self, node): """ Args: node: Returns: """ # Handle left and right left = self.visit(node.left) comparators = [self.visit(compare) for compare in node.comparators] # Handle ops for op, right in zip(node.ops, comparators): if isinstance(op, (ast.Eq, ast.NotEq, ast.Is, ast.IsNot)): continue elif isinstance(op, (ast.Lt, ast.LtE, ast.GtE, ast.Gt)): if are_types_equal(left, right): if isinstance(left, ORDERABLE_TYPES): continue elif isinstance(op, (ast.In, ast.NotIn)): if isinstance(right, INDEXABLE_TYPES): continue self._issue( incompatible_types(self.locate(), op, left, right, report=self.report)) return BoolType()
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)
def visit_IfExp(self, node): """ Args: node: Returns: """ # Visit the conditional self.visit(node.test) # Visit the body body = self.visit(node.body) # Visit the orelse orelse = self.visit(node.orelse) if are_types_equal(body, orelse): return body # TODO: Union type? return UnknownType()
def definition(tifa, call_type, call_name, parameters, call_position): """ Args: tifa: call_type: call_name: parameters: call_position: Returns: """ function_scope = NewScope(self, definitions_scope) with function_scope: # Process arguments args = node.args.args if len(args) != len(parameters): self._issue( incorrect_arity(self.locate(), function_name, report=self.report)) # TODO: Handle special types of parameters for arg, parameter in zip(args, parameters): name = arg.arg if arg.annotation: self.visit(arg.annotation) annotation = get_pedal_type_from_annotation( arg.annotation, self) # TODO: Use parameter information to "fill in" empty lists if isinstance(parameter, ListType) and isinstance( annotation, ListType): if isinstance(parameter.subtype, UnknownType): parameter.subtype = annotation.subtype # TODO: Check that arg.type and parameter type match! if not are_types_equal(annotation, parameter, True): self._issue( parameter_type_mismatch( self.locate(), name, annotation, parameter)) if parameter is not None: parameter = parameter.clone_mutably() self.create_variable(name, parameter, position) # Too many arguments if len(args) < len(parameters): for undefined_parameter in parameters[len(args):]: self.create_variable(name, UnknownType(), position) # Not enough arguments if len(args) > len(parameters): for arg in args[len(parameters):]: if arg.annotation: self.visit(arg.annotation) annotation = get_pedal_type_from_annotation( arg.annotation, self) else: annotation = UnknownType() self.create_variable(arg.arg, annotation, position) self.visit_statements(node.body) return_state = self.find_variable_scope("*return") return_value = NoneType() # TODO: Figure out if we are not returning something when we should # If the pseudo variable exists, we load it and get its type if return_state.exists and return_state.in_scope: return_state = self.load_variable("*return", call_position) return_value = return_state.type if node.returns: # self.visit(node.returns) returns = get_pedal_type_from_annotation( node.returns, self) if not are_types_equal(return_value, returns, True): self._issue( multiple_return_types( return_state.position, returns.precise_description(), return_value.precise_description(), report=self.report)) return return_value
def condition(self, value, expected_type): """ Tests if the left and right are equal """ value_type = self.fields['value_type'] expected_type = self.fields['expected_type'] return not are_types_equal(value_type, expected_type)
def ensure_function(name, arity=None, parameters=None, returns=None, root=None, compliment=False, **kwargs): """ Checks that the function exists and has the right signature. """ report = kwargs.get('report', MAIN_REPORT) root = root or parse_program(report=report) defs = root.find_all('FunctionDef') defs = [a_def for a_def in defs if a_def._name == name] if not defs: return missing_function(name, **kwargs) if len(defs) > 1: lines = [Location.from_ast(a_def) for a_def in defs] return duplicate_function_definition(name, lines, **kwargs) definition = defs[0] # Actual logic # 1.2.1 'arity' style - simply checks number of parameters if arity is not None or parameters is not None: expected_arity = arity if arity is not None else len(parameters) actual_arity = len(definition.args.args) if actual_arity < expected_arity: return too_few_parameters(name, actual_arity, expected_arity, **kwargs) elif actual_arity > expected_arity: return too_many_parameters(name, actual_arity, expected_arity, **kwargs) # 1.2.2 'parameters' style - checks each parameter's name and type if parameters is not None: actual_parameters = definition.args.args for expected_parameter, actual_parameter in zip( parameters, actual_parameters): expected_parameter_type = normalize_type(expected_parameter) actual_parameter_name = (actual_parameter.id if actual_parameter.id is not None else actual_parameter.arg) if actual_parameter.annotation is None: return missing_parameter_type(name, actual_parameter_name, expected_parameter_type, **kwargs) try: actual_parameter_type = normalize_type( actual_parameter.annotation.ast_node) except ValueError as e: return invalid_parameter_type(name, actual_parameter_name, actual_parameter.annotation, expected_parameter_type, **kwargs) if not are_types_equal(actual_parameter_type, expected_parameter_type): return wrong_parameter_type(name, actual_parameter_name, actual_parameter_type, expected_parameter_type, **kwargs) # 1.2.3. 'returns' style - checks the return type explicitly if returns is not None: expected_returns = normalize_type(returns) if definition.returns is None: return missing_return_type(name, expected_returns, **kwargs) try: actual_returns = normalize_type(definition.returns.ast_node) except ValueError as e: return invalid_return_type(name, definition.returns, expected_returns, **kwargs) if not are_types_equal(actual_returns, expected_returns): return wrong_return_type(name, actual_returns, expected_returns, **kwargs) # Alternatively, returns positive FF? if compliment: if isinstance(compliment, str): core_compliment(compliment, label="function_defined", **kwargs) elif compliment is True: core_compliment(f"Defined {name}", label="function_defined", **kwargs) elif kwargs.get("score"): give_partial(kwargs.pop("score"), label="function_defined", **kwargs) return None