def create_new_event(self, create_call: ast.Call) -> Event: event = Event('') event_args = create_call.args if len(event_args) < 0: self._log_error( CompilerError.UnfilledArgument(line=create_call.lineno, col=create_call.col_offset, param=list( Builtin.NewEvent.args)[0])) elif len(event_args) > 0: args_type = self.get_type(event_args[0]) if not Type.list.is_type_of(args_type): self._log_error( CompilerError.MismatchedTypes( line=event_args[0].lineno, col=event_args[0].col_offset, expected_type_id=Type.list.identifier, actual_type_id=args_type.identifier)) else: for value in event_args[0].elts: if not isinstance(value, ast.Tuple): CompilerError.MismatchedTypes( line=value.lineno, col=value.col_offset, expected_type_id=Type.tuple.identifier, actual_type_id=self.get_type(value).identifier) elif len(value.elts) < 2: self._log_error( CompilerError.UnfilledArgument( line=value.lineno, col=value.col_offset, param=list(Builtin.NewEvent.args)[0])) elif not (isinstance(value.elts[0], ast.Str) and (isinstance(value.elts[1], ast.Name) and isinstance( self.get_symbol(value.elts[1].id), IType))): CompilerError.MismatchedTypes( line=value.lineno, col=value.col_offset, expected_type_id=Type.tuple.identifier, actual_type_id=self.get_type(value).identifier) else: arg_name = value.elts[0].s arg_type = self.get_symbol(value.elts[1].id) event.args[arg_name] = Variable(arg_type) if len(event_args) > 1: if not isinstance(event_args[1], ast.Str): name_type = self.get_type(event_args[1]) CompilerError.MismatchedTypes( line=event_args[1].lineno, col=event_args[1].col_offset, expected_type_id=Type.str.identifier, actual_type_id=name_type.identifier) else: event.name = event_args[1].s return event
def validate_values(self, *params: Any) -> List[Any]: values = [] if len(params) != 2: return values origin, visitor = params from boa3.analyser.astanalyser import IAstAnalyser if not isinstance(origin, ast.Call) or not isinstance( visitor, IAstAnalyser): return [self.name, self.safe] # read the called arguments args_names = list(self.args.keys()) args_len = min(len(origin.args), len(args_names)) for x in range(args_len): values.append(visitor.visit(origin.args[x])) if len(values) <= 1: if len(values) == 0: values.append('') values.append(self.safe) # read the called keyword arguments for kwarg in origin.keywords: if kwarg.arg in args_names: x = args_names.index(kwarg.arg) value = visitor.visit(kwarg.value) values[x] = value from boa3.exception import CompilerError name, safe = values if not isinstance(name, str): visitor._log_error( CompilerError.MismatchedTypes( origin.lineno, origin.col_offset, expected_type_id=Type.str.identifier, actual_type_id=type(name).__name__)) if not isinstance(safe, bool): visitor._log_error( CompilerError.MismatchedTypes( origin.lineno, origin.col_offset, expected_type_id=Type.bool.identifier, actual_type_id=type(safe).__name__)) return values
def validate_type_variable_assign(self, node: ast.AST, value: Any, target: Any = None) -> bool: value_type: IType = self.get_type(value) if isinstance(value, ast.AST): value = self.visit(value) if target is not None: target_type = self.get_type(target) elif not isinstance(node, ast.Name): target_type = self.get_type(node) else: var: ISymbol = self.get_symbol(node.id) if not isinstance(var, Variable): self._log_error( CompilerError.UnresolvedReference( node.lineno, node.col_offset, symbol_id=node.id )) return False if var.type is None: # it is an declaration with assignment and the value is neither literal nor another variable var.set_type(value_type) target_type = var.type if not target_type.is_type_of(value_type) and value != target_type.default_value: self._log_error( CompilerError.MismatchedTypes( node.lineno, node.col_offset, actual_type_id=value_type.identifier, expected_type_id=target_type.identifier )) return False return True
def get_bin_op(self, operator: Operator, right: Any, left: Any) -> IOperation: """ Returns the binary operation specified by the operator and the types of the operands :param operator: the operator :param right: right operand :param left: left operand :return: Returns the corresponding :class:`BinaryOperation` if the types are valid. :raise MismatchedTypes: raised if the types aren't valid for the operator """ l_type: IType = self.get_type(left) r_type: IType = self.get_type(right) if l_type is None or r_type is None: return BinaryOp.get_operation_by_operator(operator, l_type if l_type is not None else r_type) actual_types = (l_type.identifier, r_type.identifier) operation: IOperation = BinaryOp.validate_type(operator, l_type, r_type) if operation is not None: return operation else: expected_op: BinaryOperation = BinaryOp.get_operation_by_operator(operator, l_type) expected_types = (expected_op.left_type.identifier, expected_op.right_type.identifier) raise CompilerError.MismatchedTypes(0, 0, expected_types, actual_types)
def _validate_IsInstanceMethod(self, method: IsInstanceMethod, args_types: List[IType]): """ Validates the arguments for `isinstance` method :param method: instance of the builtin method :param args_types: types of the arguments """ last_arg = self.call.args[-1] if isinstance(last_arg, ast.Tuple) and all( isinstance(tpe, ast.Name) for tpe in last_arg.elts): if len(last_arg.elts) == 1: # if the types tuple has only one type, remove it from inside the tuple last_arg = self.call.args[-1] = last_arg.elts[-1] args_types[-1] = args_types[-1].value_type elif len(last_arg.elts) > 1: # if there are more than one type, updates information in the instance of the method types: List[IType] = [ self.get_symbol(name.id) for name in last_arg.elts ] method.set_instance_type(types) self.call.args[-1] = last_arg.elts[-1] return if not isinstance(last_arg, ast.Name) or ( last_arg.id != args_types[-1].identifier and last_arg.id != args_types[-1].raw_identifier): # if the value is not the identifier of a type self._log_error( CompilerError.MismatchedTypes( last_arg.lineno, last_arg.col_offset, expected_type_id=type.__name__, actual_type_id=args_types[-1].identifier))
def visit_ExceptHandler(self, node: ast.ExceptHandler): """ Visitor of the try except node :param node: the python ast try except node """ logged_errors = False exception_type: IType = self.get_type(node.type) if node.type is not None and exception_type is not Type.exception: self._log_error( CompilerError.MismatchedTypes(line=node.type.lineno, col=node.type.col_offset, expected_type_id=Type.exception.identifier, actual_type_id=exception_type.identifier) ) logged_errors = True if node.name is not None: # TODO: remove when getting the exception is implemented self._log_error( CompilerError.NotSupportedOperation(line=node.lineno, col=node.col_offset, symbol_id='naming exceptions') ) logged_errors = True if not logged_errors and node.type is not None: self._log_using_specific_exception_warning(node.type) self.generic_visit(node)
def visit_For(self, for_node: ast.For): """ Verifies if the type of for iterator is valid :param for_node: the python ast for node """ iterator = self.visit(for_node.iter) iterator_type: IType = self.get_type(iterator) if not isinstance(iterator_type, Collection): self._log_error( CompilerError.MismatchedTypes( for_node.lineno, for_node.col_offset, actual_type_id=iterator_type.identifier, expected_type_id=Type.sequence.identifier) ) # TODO: change when optimizing for loops elif self.get_type(for_node.target) != iterator_type.item_type: target_id = self.visit(for_node.target) if isinstance(target_id, ast.Name): target_id = target_id.id symbol = self.get_symbol(target_id) symbol.set_type(iterator_type.item_type) # continue to walk through the tree for stmt in for_node.body: self.visit(stmt) for stmt in for_node.orelse: self.visit(stmt)
def validate_slice(self, subscript: ast.Subscript, slice_node: ast.Slice) -> IType: """ Verifies if the subscribed value is a sequence and if the slice is valid to this sequence :param subscript: the python ast subscript node :param slice_node: the subscript slice :return: the type of the accessed value if it is valid. Type.none otherwise. """ value = self.visit(subscript.value) lower, upper, step = (self.get_type(value) for value in self.visit(slice_node)) if step is not Type.none: # TODO: remove when slices with stride are implemented raise NotImplementedError # is not allowed to store into a slice if isinstance(subscript.ctx, ast.Store): self._log_error( CompilerError.NotSupportedOperation( subscript.lineno, subscript.col_offset, symbol_id=Operator.Subscript ) ) symbol_type: IType = self.get_type(value) # only collection types can be subscribed if not isinstance(symbol_type, Collection): self._log_error( CompilerError.UnresolvedOperation( subscript.lineno, subscript.col_offset, type_id=symbol_type.identifier, operation_id=Operator.Subscript) ) return Type.none lower = lower if lower is not Type.none else symbol_type.valid_key upper = upper if upper is not Type.none else symbol_type.valid_key # TODO: remove when slices of other sequence types are implemented if (not symbol_type.is_valid_key(lower) or not symbol_type.is_valid_key(upper) or (step is not Type.none and not symbol_type.is_valid_key(step)) ): actual: Tuple[IType, ...] = (lower, upper) if step is Type.none else (lower, upper, step) self._log_error( CompilerError.MismatchedTypes( subscript.lineno, subscript.col_offset, expected_type_id=[symbol_type.valid_key.identifier for value in actual], actual_type_id=[value.identifier for value in actual] ) ) else: return symbol_type return Type.none
def validate_get_or_set(self, subscript: ast.Subscript, index_node: ast.Index) -> IType: """ Verifies if the subscribed value is a sequence and if the index is valid to this sequence :param subscript: the python ast subscript node :param index_node: the subscript index :return: the type of the accessed value if it is valid. Type.none otherwise. """ value = self.visit(subscript.value) index = self.visit(index_node) if isinstance(value, ast.Name): value = self.get_symbol(value.id) if isinstance(index, ast.Name): index = self.get_symbol(index.id) if not isinstance(index, tuple): index = (index,) # if it is a type hint, returns the outer type if isinstance(value, IType) and all(isinstance(i, IType) for i in index): return value symbol_type: IType = self.get_type(value) index_type: IType = self.get_type(index[0]) # only sequence types can be subscribed if not isinstance(symbol_type, Collection): self._log_error( CompilerError.UnresolvedOperation( subscript.lineno, subscript.col_offset, type_id=symbol_type.identifier, operation_id=Operator.Subscript) ) return symbol_type # the sequence can't use the given type as index if not symbol_type.is_valid_key(index_type): self._log_error( CompilerError.MismatchedTypes( subscript.lineno, subscript.col_offset, actual_type_id=index_type.identifier, expected_type_id=symbol_type.valid_key.identifier) ) # it is setting a value in a sequence that doesn't allow reassign values elif isinstance(subscript.ctx, ast.Store) and not symbol_type.can_reassign_values: self._log_error( CompilerError.UnresolvedOperation( subscript.lineno, subscript.col_offset, type_id=symbol_type.identifier, operation_id=Operator.Subscript) ) return symbol_type.item_type
def _validate_IsInstanceMethod(self, method: IsInstanceMethod, args_types: List[IType]): """ Validates the arguments for `isinstance` method :param method: instance of the builtin method :param args_types: types of the arguments """ last_arg = self.call.args[-1] if isinstance(last_arg, ast.Tuple) and all( isinstance(tpe, (ast.Attribute, ast.Name)) for tpe in last_arg.elts): if len(last_arg.elts) == 1: # if the types tuple has only one type, remove it from inside the tuple last_arg = self.call.args[-1] = last_arg.elts[-1] args_types[-1] = args_types[-1].value_type elif len(last_arg.elts) > 1: # if there are more than one type, updates information in the instance of the method types: List[IType] = [ self.get_symbol_from_node(name) for name in last_arg.elts ] method.set_instance_type(types) self.call.args[-1] = last_arg.elts[-1] return from boa3.model.type.annotation.metatype import MetaType from boa3.model.type.type import Type is_ast_valid = (isinstance(last_arg, ast.Name) or (isinstance(last_arg, ast.NameConstant) and args_types[-1] is Type.none)) is_id_valid = (hasattr(last_arg, 'id') and last_arg.id != args_types[-1].identifier and last_arg.id != args_types[-1].raw_identifier and isinstance( self.get_type(last_arg, use_metadata=True), MetaType)) if not is_ast_valid or is_id_valid: # if the value is not the identifier of a type self._log_error( CompilerError.MismatchedTypes( last_arg.lineno, last_arg.col_offset, expected_type_id=type.__name__, actual_type_id=args_types[-1].identifier))
def visit_Raise(self, raise_node: ast.Raise): """ Visitor of the raise node Verifies if the raised object is a exception :param raise_node: the python ast raise node """ raised = self.visit(raise_node.exc) raised_type: IType = self.get_type(raised) if raised_type is not Type.exception: self._log_error( CompilerError.MismatchedTypes( raise_node.lineno, raise_node.col_offset, actual_type_id=raised_type.identifier, expected_type_id=Type.exception.identifier ))
def validate_if(self, if_node: ast.AST): """ Verifies if the type of if test is valid :param if_node: the python ast if statement node :type if_node: ast.If or ast.IfExp """ test = self.visit(if_node.test) test_type: IType = self.get_type(test) if test_type is not Type.bool: self._log_error( CompilerError.MismatchedTypes( if_node.lineno, if_node.col_offset, actual_type_id=test_type.identifier, expected_type_id=Type.bool.identifier) )
def validate_passed_arguments(self, call: ast.Call, args_types: List[IType], callable_id: str, callable: Callable): if isinstance(callable, IBuiltinMethod): builtin_analyser = BuiltinFunctionCallAnalyser(self, call, callable_id, callable) if builtin_analyser.validate(): self.errors.extend(builtin_analyser.errors) self.warnings.extend(builtin_analyser.warnings) return for index, (arg_id, arg_value) in enumerate(callable.args.items()): param = call.args[index] param_type = self.get_type(param) args_types.append(param_type) if not arg_value.type.is_type_of(param_type): self._log_error( CompilerError.MismatchedTypes( param.lineno, param.col_offset, arg_value.type.identifier, param_type.identifier ))
def get_un_op(self, operator: Operator, operand: Any) -> UnaryOperation: """ Returns the binary operation specified by the operator and the types of the operands :param operator: the operator :param operand: the operand :return: Returns the corresponding :class:`UnaryOperation` if the types are valid. :raise MismatchedTypes: raised if the types aren't valid for the operator """ op_type: IType = self.get_type(operand) actual_type: str = op_type.identifier operation: UnaryOperation = UnaryOp.validate_type(operator, op_type) if operation is not None: return operation else: expected_op: UnaryOperation = UnaryOp.get_operation_by_operator(operator) expected_type: str = expected_op.operand_type.identifier raise CompilerError.MismatchedTypes(0, 0, expected_type, actual_type)
def visit_While(self, while_node: ast.While): """ Verifies if the type of while test is valid :param while_node: the python ast while statement node """ test = self.visit(while_node.test) test_type: IType = self.get_type(test) if test_type is not Type.bool: self._log_error( CompilerError.MismatchedTypes( while_node.lineno, while_node.col_offset, actual_type_id=test_type.identifier, expected_type_id=Type.bool.identifier) ) # continue to walk through the tree for stmt in while_node.body: self.visit(stmt) for stmt in while_node.orelse: self.visit(stmt)
def visit_FunctionDef(self, function: ast.FunctionDef): """ Visitor of the function node Performs the type checking in the body of the function :param function: the python ast function definition node """ self.visit(function.args) method = self.symbols[function.name] from boa3.model.event import Event if isinstance(method, Method): self._current_method = method for stmt in function.body: self.visit(stmt) self._validate_return(function) if (len(function.body) > 0 and not isinstance(function.body[-1], ast.Return) and method.return_type is Type.none): # include return None in void functions default_value: str = str(method.return_type.default_value) node: ast.AST = ast.parse(default_value).body[0].value function.body.append( ast.Return(lineno=function.lineno, col_offset=function.col_offset, value=node) ) self._current_method = None elif (isinstance(method, Event) # events don't have return and function.returns is not None): return_type = self.get_type(function.returns) if return_type is not Type.none: self._log_error( CompilerError.MismatchedTypes(line=function.lineno, col=function.col_offset, expected_type_id=Type.none.identifier, actual_type_id=return_type.identifier) )
def _read_metadata_object(self, function: ast.FunctionDef): """ Gets the metadata object defined in this function :param function: """ if self._metadata is not None: # metadata function has been defined already self._log_warning( CompilerWarning.RedeclaredSymbol( line=function.lineno, col=function.col_offset, symbol_id=Builtin.Metadata.identifier)) # this function must have a return and no arguments elif len(function.args.args) != 0: self._log_error( CompilerError.UnexpectedArgument(line=function.lineno, col=function.col_offset)) elif not any(isinstance(stmt, ast.Return) for stmt in function.body): self._log_error( CompilerError.MissingReturnStatement(line=function.lineno, col=function.col_offset, symbol_id=function.name)) else: function.returns = None function.decorator_list = [] module: ast.Module = ast.parse('') module.body = [ node for node in self._tree.body if isinstance(node, (ast.ImportFrom, ast.Import)) ] module.body.append(function) ast.copy_location(module, function) # executes the function code = compile(module, filename='<boa3>', mode='exec') namespace = {} exec(code, namespace) obj: Any = namespace[function.name]() node: ast.AST = function.body[-1] if len( function.body) > 0 else function # return must be a NeoMetadata object if not isinstance(obj, NeoMetadata): obj_type = self.get_type(obj).identifier if self.get_type( obj) is not Type.any else type(obj).__name__ self._log_error( CompilerError.MismatchedTypes( line=node.lineno, col=node.col_offset, expected_type_id=NeoMetadata.__name__, actual_type_id=obj_type)) return # validates the metadata attributes types attributes: Dict[str, Any] = { attr: value for attr, value in dict(obj.__dict__).items() if attr in Builtin.metadata_fields } if any(not isinstance(value, Builtin.metadata_fields[attr]) for attr, value in attributes.items()): for expected, actual in [ (Builtin.metadata_fields[attr], type(v_type)) for attr, v_type in attributes.items() if not isinstance(v_type, Builtin.metadata_fields[attr]) ]: if isinstance(expected, Iterable): expected_id = 'Union[{0}]'.format(', '.join( [tpe.__name__ for tpe in expected])) elif hasattr(expected, '__name__'): expected_id = expected.__name__ else: expected_id = str(expected) self._log_error( CompilerError.MismatchedTypes( line=node.lineno, col=node.col_offset, expected_type_id=expected_id, actual_type_id=actual.__name__)) else: # if the function was defined correctly, sets the metadata object of the smart contract self._metadata = obj self._metadata_node = function # for error messages only