def visit_BinOp(self, node): """ Args: node: Returns: """ # Handle left and right left = self.visit(node.left) right = self.visit(node.right) # Handle operation if isinstance(left, UnknownType) or isinstance(right, UnknownType): return UnknownType() elif type(node.op) in VALID_BINOP_TYPES: op_lookup = VALID_BINOP_TYPES[type(node.op)] if type(left) in op_lookup: op_lookup = op_lookup[type(left)] if type(right) in op_lookup: op_lookup = op_lookup[type(right)] return op_lookup(left, right) self._issue( incompatible_types(self.locate(), node.op, left, right, report=self.report)) return UnknownType()
def visit_Name(self, node): """ Args: node: Returns: """ name = node.id if name == "___": self._issue(unconnected_blocks(self.locate())) if isinstance(node.ctx, ast.Load): if name == "True" or name == "False": return BoolType() elif name == "None": return NoneType() else: variable = self.find_variable_scope(name) builtin = get_builtin_function(name) if not variable.exists and builtin: return builtin else: state = self.load_variable(name) return state.type else: variable = self.find_variable_scope(name) if variable.exists: return variable.state.type else: 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(position, "lambda", report=self.report)) # TODO: Handle special types of parameters for arg, parameter in zip(args, parameters): name = arg.arg if parameter is not None: parameter = parameter.clone_mutably() self.store_variable(name, parameter, position) if len(args) < len(parameters): for undefined_parameter in parameters[len(args):]: self.store_variable(name, UnknownType(), position) return_value = self.visit(node.body) return return_value
def visit(self, node): """ Process this node by calling its appropriate visit_* Args: node (AST): The node to visit Returns: Type: The type calculated during the visit. """ # Start processing the node self.node_chain.append(node) self.ast_id += 1 # Actions after return? if len(self.scope_chain) > 1: return_state = self.find_variable_scope("*return") if return_state.exists and return_state.in_scope: if return_state.state.set == "yes": self._issue( action_after_return(self.locate(), report=self.report)) # No? All good, let's enter the node self.final_node = node result = ast.NodeVisitor.visit(self, node) # If a node failed to return something, return the UNKNOWN TYPE if result is None: result = UnknownType() self.analysis.node_types[node] = result # Pop the node out of the chain self.ast_id -= 1 self.node_chain.pop() return result
def visit_AugAssign(self, node): """ Args: node: Returns: """ # Handle value right = self.visit(node.value) # Handle target left = self.visit(node.target) # Target is always a Name, Subscript, or Attribute name = self.identify_caller(node.target) # Handle operation self.load_variable(name) if isinstance(left, UnknownType) or isinstance(right, UnknownType): return UnknownType() elif type(node.op) in VALID_BINOP_TYPES: op_lookup = VALID_BINOP_TYPES[type(node.op)] if type(left) in op_lookup: op_lookup = op_lookup[type(left)] if type(right) in op_lookup: op_lookup = op_lookup[type(right)] result_type = op_lookup(left, right) self.assign_target(node.target, result_type) return result_type self._issue( incompatible_types(self.locate(), node.op, left, right, report=self.report))
def visit_Call(self, node): """ Args: node: Returns: """ # Handle func part (Name or Attribute) function_type = self.visit(node.func) # TODO: Need to grab the actual type in some situations callee = self.identify_caller(node) # Handle args arguments = [self.visit(arg) for arg in node.args] if node.args else [] # Check special common mistakes # TODO: Handle keywords # TODO: Handle star args # TODO: Handle kwargs if isinstance(function_type, FunctionType): # Test if we have called this definition before if function_type.definition not in self.definition_chain: self.definition_chain.append(function_type.definition) # Function invocation result = function_type.definition(self, function_type, callee, arguments, self.locate()) self.definition_chain.pop() return result else: self._issue( recursive_call(self.locate(), callee, report=self.report)) elif isinstance(function_type, ClassType): constructor = function_type.get_constructor().definition self.definition_chain.append(constructor) result = constructor(self, constructor, callee, arguments, self.locate()) self.definition_chain.pop() if '__init__' in function_type.fields: initializer = function_type.fields['__init__'] if isinstance(initializer, FunctionType): self.definition_chain.append(initializer) initializer.definition(self, initializer, result, [result] + arguments, self.locate()) self.definition_chain.pop() return result elif isinstance(function_type, (NumType, StrType, BoolType, NoneType)): self._issue( not_a_function(self.locate(), callee, function_type, report=self.report)) return UnknownType()
def get_builtin_module(name): """ Given the name of the module, retrieve its TIFA representation. Args: name: Returns: """ return BUILTIN_MODULES.get(name, UnknownType())
def visit_UnaryOp(self, node): """ Args: node: Returns: """ # Handle operand operand = self.visit(node.operand) if isinstance(node.op, ast.Not): return BoolType() elif isinstance(operand, UnknownType): return UnknownType() elif type(node.op) in VALID_UNARYOP_TYPES: op_lookup = VALID_UNARYOP_TYPES[type(node.op)] if type(operand) in op_lookup: return op_lookup[type(operand)]() return UnknownType()
def load_variable(self, name, position=None): """ Retrieve the variable with the given name. Args: position: name (str): The unqualified name of the variable. If the variable is not found in the current scope or an enclosing scope, all other scopes will be searched to see if it was read out of scope. Returns: State: The current state of the variable. """ full_name = self._scope_chain_str(name) current_path = self.path_chain[0] variable = self.find_variable_scope(name) if position is None: position = self.locate() if not variable.exists: out_of_scope_var = self.find_variable_out_of_scope(name) # Create a new instance of the variable on the current path if out_of_scope_var.exists: self._issue(read_out_of_scope(self.locate(), name)) else: self._issue(initialization_problem(self.locate(), name)) new_state = State(name, [], UnknownType(), 'load', position, read='yes', set='no', over='no') self.name_map[current_path][full_name] = new_state else: new_state = self.trace_state(variable.state, "load", position) if variable.state.set == 'no': self._issue(initialization_problem(self.locate(), name)) if variable.state.set == 'maybe': if name != '*return': self._issue( possible_initialization_problem(self.locate(), name)) new_state.read = 'yes' if not variable.in_scope: self.name_map[current_path][variable.scoped_name] = new_state else: self.name_map[current_path][full_name] = new_state return new_state
def get_pedal_type_from_value(value, type_space=None) -> Type: """ Converts the Python value to a Pedal Type """ if isinstance(value, bool): return BoolType() if isinstance(value, (int, float, complex)): return NumType() if isinstance(value, str): return StrType() if isinstance(value, type(None)): return NoneType() if isinstance(value, tuple): return TupleType( (get_pedal_type_from_value(t, type_space) for t in value)) if isinstance(value, set): return SetType( (get_pedal_type_from_value(t, type_space) for t in value)) if isinstance(value, frozenset): return FrozenSetType( (get_pedal_type_from_value(t, type_space) for t in value)) if isinstance(value, list): if value: return ListType(empty=False, subtype=get_pedal_type_from_value(value[0])) else: return ListType(empty=True) if isinstance(value, dict): if not value: return DictType(empty=True) if all(isinstance(value, str) for k in value.keys()): return DictType(literals=[LiteralStr(s) for s in value.keys()], values=[ get_pedal_type_from_value(vv, type_space) for vv in value.values() ]) return DictType(keys=[ get_pedal_type_from_ast(k, type_space) for k in value.keys() ], values=[ get_pedal_type_from_ast(vv, type_space) for vv in value.values() ]) return UnknownType()
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 get_builtin_function(name): """ Args: name: Returns: """ # Void Functions if name == "print": return FunctionType(name="print", returns=NoneType()) # Math Functions elif name in ("int", "abs", "float", "len", "ord", "pow", "round", "sum"): return FunctionType(name=name, returns=NumType()) # Boolean Functions elif name in ("bool", "all", "any", "isinstance"): return FunctionType(name=name, returns=BoolType()) # String Functions elif name in ("str", 'chr', 'bin', 'repr', 'input'): return FunctionType(name=name, returns=StrType()) # File Functions elif name == "open": return FunctionType(name="open", returns=FileType()) # List Functions elif name == "map": return FunctionType(name="map", returns=ListType(empty=False)) elif name == "list": return FunctionType(name="list", definition=_builtin_sequence_constructor(ListType)) # Set Functions elif name == "set": return FunctionType(name="set", definition=_builtin_sequence_constructor(SetType)) # Dict Functions elif name == "dict": return FunctionType(name="dict", returns=DictType()) # Pass through elif name == "sorted": return FunctionType(name="sorted", returns='identity') elif name == "reversed": return FunctionType(name="reversed", returns='identity') elif name == "filter": return FunctionType(name="filter", returns='identity') # Special Functions elif name == "type": return FunctionType(name="type", returns=UnknownType()) elif name == "range": return FunctionType(name="range", returns=ListType(NumType(), empty=False)) elif name == "dir": return FunctionType(name="dir", returns=ListType(StrType(), empty=False)) elif name == "max": return FunctionType(name="max", returns='element') elif name == "min": return FunctionType(name="min", returns='element') elif name == "zip": return FunctionType(name="zip", returns=_builtin_zip) elif name == "__import__": return FunctionType(name="__import__", returns=ModuleType()) elif name == "globals": return FunctionType(name="globals", returns=DictType(keys=StrType(), values=UnknownType(), empty=False)) elif name in ("classmethod", "staticmethod"): return FunctionType(name=name, returns='identity') elif name in ("__name__", ): return StrType()
""" from pedal.types.definitions import (UnknownType, FunctionType, NumType, NoneType, BoolType, TupleType, ListType, StrType, FileType, DictType, ModuleType, SetType, DayType, TimeType, LiteralNum) BUILTIN_MODULES = { 'pprint': ModuleType( 'pprint', fields={'pprint': FunctionType(name='pprint', returns=NoneType())}), 'json': ModuleType('json', fields={ 'loads': FunctionType(name='loads', returns=UnknownType()), 'dumps': FunctionType(name='dumps', returns=StrType()) }), 'random': ModuleType( 'random', fields={'randint': FunctionType(name='randint', returns=NumType())}), 'string': ModuleType('string', fields={ 'letters': StrType(empty=False), 'digits': StrType(empty=False), 'ascii_letters': StrType(empty=False), 'punctuation': StrType(empty=False), 'printable': StrType(empty=False), 'whitespace': StrType(empty=False),
def get_pedal_type_from_ast(value: ast.AST, type_space=None) -> Type: """ Determines the Pedal Type from this ast node. Args: value (ast.AST): An AST node. Returns: Type: A Pedal Type """ try: if isinstance(value, ast.Constant): return get_pedal_type_from_value(value.value, type_space) except AttributeError as e: pass if isinstance(value, ast.Name): return get_pedal_type_from_str(value.id, type_space) elif isinstance(value, ast.Str): return StrType(bool(value.s)) elif isinstance(value, ast.List): return ListType(subtype=(get_pedal_type_from_ast( value.elts[0], type_space) if value.elts else None), empty=not bool(value.elts)) elif isinstance(value, ast.Set): return SetType(subtype=(get_pedal_type_from_ast( value.elts[0], type_space) if value.elts else None), empty=not bool(value.elts)) elif isinstance(value, ast.Tuple): return TupleType(subtypes=[ get_pedal_type_from_ast(e, type_space) for e in value.elts ]) elif isinstance(value, ast.Dict): if not value.keys: return DictType(empty=True) if all(isinstance(k, ast.Str) for k in value.keys): return DictType(literals=[LiteralStr(s.s) for s in value.keys], values=[ get_pedal_type_from_ast(vv, type_space) for vv in value.values ]) return DictType( keys=[get_pedal_type_from_ast(k, type_space) for k in value.keys], values=[ get_pedal_type_from_ast(vv, type_space) for vv in value.values ]) # Support new style subscripts (e.g., ``list[int]``) elif ((IS_PYTHON_39 and isinstance(value, ast.Subscript)) or isinstance(value, ast.Subscript) and isinstance(value.slice, ast.Index)): if IS_PYTHON_39: slice = value.slice else: slice = value.slice.value if isinstance(value.value, ast.Name): if isinstance(slice, ast.Name): subtype = get_pedal_type_from_str(slice.id) if value.value.id == "list": return ListType(subtype=subtype, empty=False) if value.value.id == "set": return SetType(subtype=subtype, empty=False) if value.value.id == "tuple": return TupleType(subtypes=(subtype, )) if value.value.id == "frozenset": return FrozenSetType(subtype=subtype, empty=False) elif isinstance(slice, ast.Tuple): subtypes = [ get_pedal_type_from_ast(e, type_space) for e in slice.elts ] if value.value.id == "tuple": return TupleType(subtypes=subtypes) elif value.value.id == "dict" and len(subtypes) == 2: return DictType(keys=subtypes[0], values=subtypes[1]) # Top-level Module, parse it and get it back if isinstance(value, ast.Module) and value.body: if isinstance(value.body[0], ast.Expr): return get_pedal_type_from_ast(value.body[0].value, type_space) return UnknownType()