• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

CityOfZion / neo3-boa / 6a87883d-74ff-49e7-a466-56d20f5bd8c0

pending completion
6a87883d-74ff-49e7-a466-56d20f5bd8c0

push

circleci

GitHub
Merge pull request #996 from CityOfZion/CU-864drmwgd

5 of 5 new or added lines in 1 file covered. (100.0%)

17863 of 19547 relevant lines covered (91.38%)

0.94 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.04
/boa3/analyser/typeanalyser.py
1
import ast
1✔
2
from typing import Any, Dict, Iterable, List, Optional, Set, Tuple, Union
1✔
3

4
from boa3 import constants
1✔
5
from boa3.analyser.astanalyser import IAstAnalyser
1✔
6
from boa3.analyser.builtinfunctioncallanalyser import BuiltinFunctionCallAnalyser
1✔
7
from boa3.analyser.model.optimizer import Undefined, UndefinedType
1✔
8
from boa3.analyser.model.symbolscope import SymbolScope
1✔
9
from boa3.exception import CompilerError, CompilerWarning
1✔
10
from boa3.model.attribute import Attribute
1✔
11
from boa3.model.builtin.builtin import Builtin
1✔
12
from boa3.model.builtin.method.builtinmethod import IBuiltinMethod
1✔
13
from boa3.model.callable import Callable
1✔
14
from boa3.model.expression import IExpression
1✔
15
from boa3.model.imports.importsymbol import Import
1✔
16
from boa3.model.imports.package import Package
1✔
17
from boa3.model.method import Method
1✔
18
from boa3.model.module import Module
1✔
19
from boa3.model.operation.binary.binaryoperation import BinaryOperation
1✔
20
from boa3.model.operation.binaryop import BinaryOp
1✔
21
from boa3.model.operation.operation import IOperation
1✔
22
from boa3.model.operation.operator import Operator
1✔
23
from boa3.model.operation.unary.unaryoperation import UnaryOperation
1✔
24
from boa3.model.operation.unaryop import UnaryOp
1✔
25
from boa3.model.property import Property
1✔
26
from boa3.model.symbol import ISymbol
1✔
27
from boa3.model.type.annotation.metatype import MetaType
1✔
28
from boa3.model.type.classes.classtype import ClassType
1✔
29
from boa3.model.type.classes.pythonclass import PythonClass
1✔
30
from boa3.model.type.classes.userclass import UserClass
1✔
31
from boa3.model.type.collection.icollection import ICollectionType as Collection
1✔
32
from boa3.model.type.type import IType, Type
1✔
33
from boa3.model.type.typeutils import TypeUtils
1✔
34
from boa3.model.variable import Variable
1✔
35

36

37
class TypeAnalyser(IAstAnalyser, ast.NodeVisitor):
1✔
38
    """
39
    This class is responsible for the type checking of the code
40

41
    The methods with the name starting with 'visit_' are implementations of methods from the :class:`NodeVisitor` class.
42
    These methods are used to walk through the Python abstract syntax tree.
43

44
    :ivar type_errors: a list with the found type errors. Empty by default.
45
    :ivar modules: a list with the analysed modules. Empty by default.
46
    :ivar symbols: a dictionary that maps the global symbols.
47
    """
48

49
    def __init__(self, analyser, symbol_table: Dict[str, ISymbol], log: bool = False):
1✔
50
        super().__init__(analyser.ast_tree, analyser.filename, log=log)
1✔
51
        self.type_errors: List[Exception] = []
1✔
52
        self.modules: Dict[str, Module] = {}
1✔
53
        self.symbols: Dict[str, ISymbol] = symbol_table
1✔
54

55
        self._current_class: UserClass = None
1✔
56
        self._current_method: Method = None
1✔
57
        self._scope_stack: List[SymbolScope] = []
1✔
58

59
        self._super_calls: List[IBuiltinMethod] = []
1✔
60
        self.visit(self._tree)
1✔
61

62
    @property
1✔
63
    def _current_method_id(self) -> str:
1✔
64
        """
65
        Get the string identifier of the current method
66

67
        :return: The name identifier of the method. If the current method is None, returns None.
68
        :rtype: str or None
69
        """
70
        if self._current_method in self.symbols.values():
×
71
            index = list(self.symbols.values()).index(self._current_method)
×
72
            return list(self.symbols.keys())[index]
×
73

74
    @property
1✔
75
    def _current_scope(self) -> Optional[SymbolScope]:
1✔
76
        return self._scope_stack[-1] if len(self._scope_stack) > 0 else None
1✔
77

78
    @property
1✔
79
    def _modules_symbols(self) -> Dict[str, ISymbol]:
1✔
80
        """
81
        Gets all the symbols in the modules scopes.
82

83
        :return: Returns a dictionary that maps the modules symbols.
84
        """
85
        symbols = {}
×
86
        for module in self.modules.values():
×
87
            symbols.update(module.symbols)
×
88
        return symbols
×
89

90
    def get_symbol(self, symbol_id: str,
1✔
91
                   is_internal: bool = False,
92
                   check_raw_id: bool = False,
93
                   origin_node: ast.AST = None) -> Optional[ISymbol]:
94
        if symbol_id is None:
1✔
95
            return None
1✔
96
        if isinstance(symbol_id, ISymbol) and not isinstance(symbol_id, (IType, IExpression)):
1✔
97
            return symbol_id
1✔
98
        if not isinstance(symbol_id, str):
1✔
99
            return Variable(self.get_type(symbol_id))
1✔
100

101
        if len(self._scope_stack) > 0:
1✔
102
            for scope in self._scope_stack[::-1]:
1✔
103
                if symbol_id in scope:
1✔
104
                    return scope[symbol_id]
1✔
105

106
                if check_raw_id:
1✔
107
                    found_symbol = self._search_by_raw_id(symbol_id, list(scope.symbols.values()))
1✔
108
                    if found_symbol is not None:
1✔
109
                        return found_symbol
×
110

111
        if self._current_method is not None and symbol_id in self._current_method.symbols:
1✔
112
            # the symbol exists in the local scope
113
            return self._current_method.symbols[symbol_id]
1✔
114
        elif self._current_class is not None and symbol_id in self._current_class.symbols:
1✔
115
            # the symbol exists in the class
116
            return self._current_class.symbols[symbol_id]
1✔
117
        elif symbol_id in self.modules:
1✔
118
            # the symbol exists in the modules scope
119
            return self.modules[symbol_id]
×
120

121
        if self._current_method is not None:
1✔
122
            cur_symbols = self._current_method.symbols
1✔
123
        elif self._current_class is not None:
1✔
124
            cur_symbols = self._current_class.symbols
1✔
125
        else:
126
            cur_symbols = self.modules
1✔
127

128
        if check_raw_id:
1✔
129
            found_symbol = self._search_by_raw_id(symbol_id, list(cur_symbols.values()))
1✔
130
            if found_symbol is not None:
1✔
131
                return found_symbol
×
132

133
        return super().get_symbol(symbol_id, is_internal, check_raw_id, origin_node)
1✔
134

135
    def new_local_scope(self, symbols: Dict[str, ISymbol] = None):
1✔
136
        if symbols is None:
1✔
137
            symbols = self._current_scope.symbols if self._current_scope is not None else {}
×
138

139
        self._scope_stack.append(SymbolScope(symbols))
1✔
140

141
    def pop_local_scope(self) -> SymbolScope:
1✔
142
        return self._scope_stack.pop()
1✔
143

144
    def visit_Module(self, module: ast.Module):
1✔
145
        """
146
        Visitor of the module node
147

148
        Performs the type checking in the body of the method
149

150
        :param module: the python ast module node
151
        """
152
        for stmt in module.body:
1✔
153
            self.visit(stmt)
1✔
154

155
    def visit_ClassDef(self, node: ast.ClassDef) -> Any:
1✔
156
        if node.name in self.symbols:
1✔
157
            class_symbol = self.symbols[node.name]
1✔
158
            if isinstance(class_symbol, UserClass):
1✔
159
                self._current_class = class_symbol
1✔
160

161
        self.generic_visit(node)
1✔
162
        self._current_class = None
1✔
163

164
    def visit_FunctionDef(self, function: ast.FunctionDef):
1✔
165
        """
166
        Visitor of the function node
167

168
        Performs the type checking in the body of the function
169

170
        :param function: the python ast function definition node
171
        """
172
        self.visit(function.args)
1✔
173

174
        symbols = self.symbols if self._current_class is None else self._current_class.symbols
1✔
175
        method = symbols[function.name]
1✔
176

177
        from boa3.model.event import Event
1✔
178
        if isinstance(method, Property):
1✔
179
            method = method.getter
1✔
180

181
        if isinstance(method, Method):
1✔
182
            self._current_method = method
1✔
183
            self.new_local_scope({var_id: var for var_id, var in method.symbols.items()
1✔
184
                                  if isinstance(var, Variable) and var.type is not UndefinedType})
185

186
            if self._current_class is not None and self._current_class.is_interface and len(function.body) > 0:
1✔
187
                first_instruction = function.body[0]
1✔
188
                if not isinstance(first_instruction, ast.Pass):
1✔
189
                    self._log_warning(CompilerWarning.UnreachableCode(first_instruction.lineno,
×
190
                                                                      first_instruction.col_offset))
191
            else:
192
                for stmt in function.body:
1✔
193
                    self.visit(stmt)
1✔
194

195
            if method.is_init:
1✔
196
                self._check_base_init_call(function)
1✔
197
            else:
198
                self._validate_return(function)
1✔
199

200
            method_scope = self.pop_local_scope()
1✔
201
            for symbol_id, symbol in self._current_method.symbols.items():
1✔
202
                if isinstance(symbol, Variable) and symbol.type is UndefinedType and symbol_id in method_scope:
1✔
203
                    new_scope_symbol = method_scope[symbol_id]
1✔
204
                    if hasattr(new_scope_symbol, 'type') and new_scope_symbol.type is not UndefinedType:
1✔
205
                        symbol.set_type(new_scope_symbol.type)
1✔
206

207
            self._current_method = None
1✔
208
        elif (isinstance(method, Event)  # events don't have return
×
209
              and function.returns is not None):
210
            return_type = self.get_type(function.returns)
×
211
            if return_type is not Type.none:
×
212
                self._log_error(
×
213
                    CompilerError.MismatchedTypes(line=function.lineno, col=function.col_offset,
214
                                                  expected_type_id=Type.none.identifier,
215
                                                  actual_type_id=return_type.identifier)
216
                )
217

218
    def visit_arguments(self, arguments: ast.arguments):
1✔
219
        """
220
        Verifies if each argument of a function has a type annotation
221

222
        :param arguments: the python ast function arguments node
223
        """
224
        for arg in arguments.args:
1✔
225
            self.visit(arg)
1✔
226

227
        # continue to walk through the tree
228
        self.generic_visit(arguments)
1✔
229

230
    def visit_arg(self, arg: ast.arg):
1✔
231
        """
232
        Verifies if the argument of a function has a type annotation
233

234
        :param arg: the python ast arg node
235
        """
236
        if arg.annotation is None:
1✔
237
            self._log_error(
1✔
238
                CompilerError.TypeHintMissing(arg.lineno, arg.col_offset, symbol_id=arg.arg)
239
            )
240

241
        # continue to walk through the tree
242
        self.generic_visit(arg)
1✔
243

244
    def visit_Return(self, ret: ast.Return):
1✔
245
        """
246
        Verifies if the return of the function is the same type as the return type annotation
247

248
        :param ret: the python ast return node
249
        """
250
        ret_value: Any = self.visit(ret.value) if ret.value is not None else None
1✔
251
        if ret.value is not None and self.get_type(ret.value) is not Type.none:
1✔
252
            # multiple returns are not allowed
253
            if isinstance(ret.value, ast.Tuple):
1✔
254
                self._log_error(
1✔
255
                    CompilerError.TooManyReturns(ret.lineno, ret.col_offset)
256
                )
257
                return
1✔
258
            # it is returning something, but there is no type hint for return
259
            elif self._current_method.return_type is Type.none:
1✔
260
                self._log_error(
×
261
                    CompilerError.TypeHintMissing(ret.lineno, ret.col_offset, symbol_id=self._current_method_id)
262
                )
263
                return
×
264
        self.validate_type_variable_assign(ret, ret_value, self._current_method)
1✔
265

266
        # continue to walk through the tree
267
        self.generic_visit(ret)
1✔
268

269
    def _validate_return(self, node: ast.AST):
1✔
270
        if (self._current_method.return_type is not Type.none
1✔
271
                and hasattr(node, 'body')
272
                and not self._has_return(node)):
273
            lst = list(self.symbols.items())
1✔
274
            keys, values = [key_value[0] for key_value in lst], [key_value[-1] for key_value in lst]
1✔
275
            self._log_error(
1✔
276
                CompilerError.MissingReturnStatement(
277
                    line=node.lineno, col=node.col_offset,
278
                    symbol_id=keys[values.index(self._current_method)]
279
                )
280
            )
281

282
    def _has_return(self, node: ast.AST) -> bool:
1✔
283
        if not hasattr(node, 'body'):
1✔
284
            return False
×
285

286
        has_else_stmt: bool = hasattr(node, 'orelse')
1✔
287
        has_body_return: bool = any(isinstance(stmt, (ast.Return, ast.Pass)) for stmt in node.body)
1✔
288
        if has_body_return and not has_else_stmt:
1✔
289
            # if any statement in the function body is a return, all flow has a return
290
            return True
1✔
291

292
        body = [stmt for stmt in node.body if hasattr(stmt, 'body')]
1✔
293
        body_has_inner_return: bool = len(body) > 0 and all(self._has_return(stmt) for stmt in body)
1✔
294
        if not has_body_return and not body_has_inner_return:
1✔
295
            # for and while nodes must to check if there is a return inside the else statement
296
            if not isinstance(node, (ast.For, ast.AsyncFor, ast.While)):
1✔
297
                return False
1✔
298

299
        if has_else_stmt:
1✔
300
            if any(isinstance(stmt, (ast.Return, ast.Pass)) for stmt in node.orelse):
1✔
301
                return True
1✔
302
            else:
303
                orelse = [stmt for stmt in node.orelse if hasattr(stmt, 'body')]
1✔
304
                return len(orelse) > 0 and all(self._has_return(stmt) for stmt in orelse)
1✔
305
        return body_has_inner_return
1✔
306

307
    def _check_base_init_call(self, node: ast.AST):
1✔
308
        if not self._current_method.is_init or not isinstance(self._current_class, ClassType):
1✔
309
            # if the method is not an user class __init__, don't check
310
            return
×
311

312
        if len(self._current_class.bases) == 0:
1✔
313
            # nothing to check if class has no bases
314
            return
1✔
315

316
        if len(self._super_calls) == 0:
1✔
317
            self._log_error(CompilerError.MissingInitCall(line=node.lineno,
1✔
318
                                                          col=node.col_offset))
319
        else:
320
            self._super_calls.clear()
1✔
321

322
    def visit_Assign(self, assign: ast.Assign):
1✔
323
        """
324
        Verifies if it is a multiple assignments statement
325

326
        :param assign: the python ast variable assignment node
327
        """
328
        for target in assign.targets:
1✔
329
            self.validate_type_variable_assign(target, assign.value)
1✔
330

331
    def visit_AnnAssign(self, ann_assign: ast.AnnAssign):
1✔
332
        """
333
        Verifies if the assigned type is the same as the variable type
334

335
        :param ann_assign: the python ast variable annotated assignment node
336
        """
337
        # if value is None, it is a declaration
338
        if ann_assign.value is not None:
1✔
339
            self.validate_type_variable_assign(ann_assign.target, ann_assign.value, implicit_cast=True)
1✔
340

341
    def visit_AugAssign(self, aug_assign: ast.AugAssign):
1✔
342
        """
343
        Verifies if the types of the target variable and the value are compatible with the operation
344

345
        If the operation is valid, changes de Python operator by the Boa operator in the syntax tree
346

347
        :param aug_assign: the python ast augmented assignment node
348
        """
349
        operation = self.validate_binary_operation(aug_assign, aug_assign.target, aug_assign.value)
1✔
350
        if operation is not None:
1✔
351
            self.validate_type_variable_assign(aug_assign.target, operation)
1✔
352
            aug_assign.op = operation
1✔
353

354
    def validate_type_variable_assign(self, node: ast.AST, value: Any, target: Any = None,
1✔
355
                                      implicit_cast: bool = False) -> bool:
356
        value_type: IType = self.get_type(value)
1✔
357
        if isinstance(value, ast.AST):
1✔
358
            value = self.visit(value)
1✔
359
            if isinstance(value, ast.Name):
1✔
360
                symbol: ISymbol = self.get_symbol(value.id)
1✔
361
                if isinstance(symbol, IExpression):
1✔
362
                    value_type = symbol.type
1✔
363
                elif isinstance(symbol, IType):
1✔
364
                    value_type = symbol
1✔
365
                else:
366
                    self._log_error(
1✔
367
                        CompilerError.UnresolvedReference(
368
                            value.lineno, value.col_offset,
369
                            symbol_id=value.id
370
                        ))
371

372
        if target is not None:
1✔
373
            target_type = self.get_type(target)
1✔
374
        else:
375
            target_type = None
1✔
376
            var: ISymbol = self.get_symbol(node.id if hasattr(node, 'id') else node)
1✔
377
            if isinstance(var, Variable):
1✔
378
                if (var.type is UndefinedType
1✔
379
                        or (var.type is Undefined and var not in self.symbols.values())):
380
                    var = var.copy()
1✔
381

382
                if var.type in (None, UndefinedType, Undefined):
1✔
383
                    # it is an declaration with assignment and the value is neither literal nor another variable
384
                    var.set_type(value_type)
1✔
385
                target_type = var.type
1✔
386

387
            elif isinstance(node, ast.Name):
×
388
                self._log_error(
×
389
                    CompilerError.UnresolvedReference(
390
                        node.lineno, node.col_offset,
391
                        symbol_id=node.id
392
                    ))
393
                return False
×
394

395
        if self._current_scope is not None:
1✔
396
            if isinstance(node, ast.Name):
1✔
397
                if node.id not in self._current_scope:
1✔
398
                    if implicit_cast:
1✔
399
                        value_type = target_type
1✔
400
                    elif not isinstance(value_type, Collection):
1✔
401
                        target_type = value_type
1✔
402
                    elif not (isinstance(type(value_type), type(target_type)) and
1✔
403
                              (value_type.value_type is Type.any or value_type.valid_key is Type.any)):
404
                        # if the collection is generic, let the validation for the outer scope
405
                        value_type = target_type
1✔
406

407
                    self._current_scope.include_symbol(node.id, Variable(value_type))
1✔
408

409
                else:
410
                    if self._current_method is not None and node.id in self._current_method.symbols:
1✔
411
                        can_change_target_type = (self._current_method.symbols[node.id].type is UndefinedType
1✔
412
                                                  or node.id in self._current_method.args)
413
                        can_change_original = node.id not in self._current_method.args
1✔
414
                    else:
415
                        can_change_target_type = True
×
416
                        can_change_original = True
×
417

418
                    if can_change_target_type:
1✔
419
                        if (not target_type.is_type_of(value_type) and
1✔
420
                                value != target_type.default_value):
421
                            target_type = value_type
1✔
422
                        self._current_scope.include_symbol(node.id, Variable(value_type),
1✔
423
                                                           reassign_original=can_change_original)
424

425
        if not target_type.is_type_of(value_type) and value != target_type.default_value:
1✔
426
            if not implicit_cast:
1✔
427
                self._log_error(
1✔
428
                    CompilerError.MismatchedTypes(
429
                        node.lineno, node.col_offset,
430
                        actual_type_id=value_type.identifier,
431
                        expected_type_id=target_type.identifier
432
                    ))
433
                return False
1✔
434
            else:
435
                self._log_warning(
1✔
436
                    CompilerWarning.TypeCasting(
437
                        node.lineno, node.col_offset,
438
                        origin_type_id=value_type.identifier,
439
                        cast_type_id=target_type.identifier
440
                    )
441
                )
442

443
        return True
1✔
444

445
    def visit_Subscript(self, subscript: ast.Subscript) -> IType:
1✔
446
        """
447
        Verifies if the subscribed value is a sequence
448

449
        :param subscript: the python ast subscript node
450
        :return: the type of the accessed value if it is valid. Type.none otherwise.
451
        """
452
        if isinstance(subscript.slice, ast.Slice):
1✔
453
            return self.validate_slice(subscript, subscript.slice)
1✔
454

455
        return self.validate_get_or_set(subscript, subscript.slice)
1✔
456

457
    def validate_get_or_set(self, subscript: ast.Subscript, index_node: ast.AST) -> IType:
1✔
458
        """
459
        Verifies if the subscribed value is a sequence and if the index is valid to this sequence
460

461
        :param subscript: the python ast subscript node
462
        :param index_node: the subscript index
463
        :return: the type of the accessed value if it is valid. Type.none otherwise.
464
        """
465
        is_internal = hasattr(subscript, 'is_internal_call') and subscript.is_internal_call
1✔
466

467
        value = self.visit(subscript.value)
1✔
468
        index = self.visit(index_node)
1✔
469

470
        if isinstance(value, ast.Name):
1✔
471
            value = self.get_symbol(value.id, is_internal=is_internal)
1✔
472
        if isinstance(index, ast.Name):
1✔
473
            index = self.get_symbol(index.id, is_internal=is_internal)
1✔
474
        if not isinstance(index, tuple):
1✔
475
            index = (index,)
1✔
476

477
        # if it is a type hint, returns the outer type
478
        if isinstance(value, IType) and all(isinstance(i, IType) for i in index):
1✔
479
            if isinstance(value, Collection):
1✔
480
                value = value.build_collection(*index)
1✔
481

482
            value_to_be_built = index if isinstance(value, MetaType) else value
1✔
483
            if not isinstance(value_to_be_built, tuple):
1✔
484
                value_to_be_built = (value_to_be_built,)
1✔
485
            return TypeUtils.type.build(*value_to_be_built)
1✔
486

487
        symbol_type: IType = self.get_type(value)
1✔
488
        index_type: IType = self.get_type(index[0])
1✔
489

490
        # only sequence types can be subscribed
491
        if not isinstance(symbol_type, Collection):
1✔
492
            self._log_error(
1✔
493
                CompilerError.UnresolvedOperation(
494
                    subscript.lineno, subscript.col_offset,
495
                    type_id=symbol_type.identifier,
496
                    operation_id=Operator.Subscript)
497
            )
498
            return symbol_type
1✔
499
        # the sequence can't use the given type as index
500
        if not symbol_type.is_valid_key(index_type):
1✔
501
            self._log_error(
1✔
502
                CompilerError.MismatchedTypes(
503
                    subscript.lineno, subscript.col_offset,
504
                    actual_type_id=index_type.identifier,
505
                    expected_type_id=symbol_type.valid_key.identifier)
506
            )
507
        # it is setting a value in a sequence that doesn't allow reassign values
508
        elif isinstance(subscript.ctx, ast.Store) and not symbol_type.can_reassign_values:
1✔
509
            self._log_error(
1✔
510
                CompilerError.UnresolvedOperation(
511
                    subscript.lineno, subscript.col_offset,
512
                    type_id=symbol_type.identifier,
513
                    operation_id=Operator.Subscript)
514
            )
515
        return symbol_type.item_type
1✔
516

517
    def validate_slice(self, subscript: ast.Subscript, slice_node: ast.Slice) -> IType:
1✔
518
        """
519
        Verifies if the subscribed value is a sequence and if the slice is valid to this sequence
520

521
        :param subscript: the python ast subscript node
522
        :param slice_node: the subscript slice
523
        :return: the type of the accessed value if it is valid. Type.none otherwise.
524
        """
525
        value = self.visit(subscript.value)
1✔
526
        lower, upper, step = (self.get_type(value) for value in self.visit(slice_node))
1✔
527

528
        symbol_type: IType = self.get_type(value)
1✔
529
        # only collection types can be subscribed
530
        if not isinstance(symbol_type, Collection):
1✔
531
            self._log_error(
×
532
                CompilerError.UnresolvedOperation(
533
                    subscript.lineno, subscript.col_offset,
534
                    type_id=symbol_type.identifier,
535
                    operation_id=Operator.Subscript)
536
            )
537
            return Type.none
×
538

539
        lower = lower if lower is not Type.none else symbol_type.valid_key
1✔
540
        upper = upper if upper is not Type.none else symbol_type.valid_key
1✔
541
        step = step if step is not Type.none else symbol_type.valid_key
1✔
542

543
        # TODO: remove when slices of other sequence types are implemented
544
        if (not symbol_type.is_valid_key(lower)
1✔
545
                or not symbol_type.is_valid_key(upper)
546
                or (step is not Type.none and not symbol_type.is_valid_key(step))
547
            ):
548
            actual: Tuple[IType, ...] = (lower, upper) if step is Type.none else (lower, upper, step)
×
549
            self._log_error(
×
550
                CompilerError.MismatchedTypes(
551
                    subscript.lineno, subscript.col_offset,
552
                    expected_type_id=[symbol_type.valid_key.identifier for value in actual],
553
                    actual_type_id=[value.identifier for value in actual]
554
                )
555
            )
556
        else:
557
            return symbol_type
1✔
558

559
        return Type.none
×
560

561
    def visit_While(self, while_node: ast.While):
1✔
562
        """
563
        Verifies if the type of while test is valid
564

565
        :param while_node: the python ast while statement node
566
        """
567
        test = self.visit(while_node.test)
1✔
568
        test_type: IType = self.get_type(test)
1✔
569

570
        if test_type is not Type.bool:
1✔
571
            self._log_error(
1✔
572
                CompilerError.MismatchedTypes(
573
                    while_node.lineno, while_node.col_offset,
574
                    actual_type_id=test_type.identifier,
575
                    expected_type_id=Type.bool.identifier)
576
            )
577

578
        # continue to walk through the tree
579
        for stmt in while_node.body:
1✔
580
            self.visit(stmt)
1✔
581
        for stmt in while_node.orelse:
1✔
582
            self.visit(stmt)
1✔
583

584
    def visit_For(self, for_node: ast.For):
1✔
585
        """
586
        Verifies if the type of for iterator is valid
587

588
        :param for_node: the python ast for node
589
        """
590
        iterator = self.visit(for_node.iter)
1✔
591
        iterator_type: IType = self.get_type(iterator)
1✔
592

593
        if not isinstance(iterator_type, Collection):
1✔
594
            self._log_error(
1✔
595
                CompilerError.MismatchedTypes(
596
                    for_node.lineno, for_node.col_offset,
597
                    actual_type_id=iterator_type.identifier,
598
                    expected_type_id=Type.sequence.identifier)
599
            )
600

601
        # TODO: change when optimizing for loops
602
        elif self.get_type(for_node.target) != iterator_type.item_type:
1✔
603
            target_id = self.visit(for_node.target)
1✔
604
            if isinstance(target_id, ast.Name):
1✔
605
                target_id = target_id.id
1✔
606
            symbol = self.get_symbol(target_id)
1✔
607
            symbol.set_type(iterator_type.item_type)
1✔
608

609
        # continue to walk through the tree
610
        for stmt in for_node.body:
1✔
611
            self.visit(stmt)
1✔
612
        for stmt in for_node.orelse:
1✔
613
            self.visit(stmt)
1✔
614

615
    def visit_If(self, if_node: ast.If):
1✔
616
        """
617
        Verifies if the type of if test is valid
618

619
        :param if_node: the python ast if statement node
620
        """
621
        is_instance_if, is_instance_else = self.validate_if(if_node)
1✔
622

623
        # continue to walk through the tree
624
        self.new_local_scope(is_instance_if)
1✔
625
        for stmt in if_node.body:
1✔
626
            self.visit(stmt)
1✔
627
        is_instance_if = self.pop_local_scope().symbols
1✔
628

629
        self.new_local_scope(is_instance_else)
1✔
630
        for stmt in if_node.orelse:
1✔
631
            self.visit(stmt)
1✔
632
        is_instance_else = self.pop_local_scope().symbols
1✔
633

634
        last_scope = self._current_scope if len(self._scope_stack) > 0 else self._current_method
1✔
635
        # updates the outer scope for each variable that is changed in both branches
636
        intersected_ids = is_instance_if.keys() & is_instance_else.keys()
1✔
637
        for changed_symbol in intersected_ids:
1✔
638
            new_value_type_if_branch = self.get_type(is_instance_if[changed_symbol])
1✔
639
            new_value_type_else_branch = self.get_type(is_instance_else[changed_symbol])
1✔
640
            new_type = Type.union.build([new_value_type_if_branch, new_value_type_else_branch])
1✔
641
            last_scope.include_symbol(changed_symbol, Variable(new_type))
1✔
642

643
        # updates with the variables assigned in a branch that doesn't exist in the outer scope
644
        self._include_symbols_to_scope(last_scope, is_instance_if, intersected_ids)
1✔
645
        self._include_symbols_to_scope(last_scope, is_instance_else, intersected_ids)
1✔
646

647
    def _include_symbols_to_scope(self, scope: SymbolScope, other_scope: Dict[str, ISymbol], items_filter: Set[str]):
1✔
648
        for new_symbol_id in {key for key in other_scope if key not in items_filter}:
1✔
649
            new_symbol = other_scope[new_symbol_id]
1✔
650
            new_value_type = self.get_type(new_symbol)
1✔
651
            outer_symbol = self.get_symbol(new_symbol_id)
1✔
652
            outer_value_type = outer_symbol.type if isinstance(outer_symbol, IExpression) else Type.none
1✔
653

654
            if (isinstance(outer_symbol, Variable) and isinstance(new_symbol, Variable)
1✔
655
                    and not new_symbol.is_reassigned):
656
                continue
1✔
657

658
            if isinstance(outer_symbol, IType):
1✔
659
                new_type = Type.union.build([new_value_type, outer_value_type])
×
660
            else:
661
                new_type = new_value_type
1✔
662
            scope.include_symbol(new_symbol_id, Variable(new_type))
1✔
663

664
    def visit_IfExp(self, if_node: ast.IfExp):
1✔
665
        """
666
        Verifies if the type of if test is valid
667

668
        :param if_node: the python ast if expression node
669
        """
670
        is_instance_if, is_instance_else = self.validate_if(if_node)
1✔
671
        body = if_node.body
1✔
672
        orelse = if_node.orelse
1✔
673
        if_value = body[-1] if isinstance(body, list) and len(body) > 0 else body
1✔
674
        else_value = orelse[-1] if isinstance(orelse, list) and len(orelse) > 0 else orelse
1✔
675

676
        self.new_local_scope(is_instance_if)
1✔
677
        if_type: IType = self.get_type(if_value)
1✔
678
        self.pop_local_scope()
1✔
679

680
        self.new_local_scope(is_instance_else)
1✔
681
        else_type: IType = self.get_type(else_value)
1✔
682
        self.pop_local_scope()
1✔
683

684
        return Type.get_generic_type(if_type, else_type)
1✔
685

686
    def validate_if(self, if_node: ast.AST) -> Tuple[Dict[str, ISymbol], Dict[str, ISymbol]]:
1✔
687
        """
688
        Verifies if the type of if test is valid
689

690
        :param if_node: the python ast if statement node
691
        :type if_node: ast.If or ast.IfExp
692
        """
693
        test = self.visit(if_node.test)
1✔
694
        test_type: IType = self.get_type(test)
1✔
695

696
        return self._get_is_instance_function_calls(if_node.test)
1✔
697

698
    def _get_is_instance_function_calls(self, node: ast.AST) -> Tuple[Dict[str, ISymbol], Dict[str, ISymbol]]:
1✔
699
        from boa3.model.builtin.method.isinstancemethod import IsInstanceMethod
1✔
700
        is_instance_objs = [x for x, symbol in self.symbols.items()
1✔
701
                            if isinstance(symbol, IsInstanceMethod)]
702
        is_instance_objs.append(isinstance.__name__)
1✔
703

704
        is_instance_symbols = {}
1✔
705
        is_not_instance_symbols = {}
1✔
706

707
        if isinstance(node, ast.BoolOp) and isinstance(node.op, type(BinaryOp.And)):
1✔
708
            conditions = node.values
1✔
709
        else:
710
            conditions = [node]
1✔
711

712
        for condition in conditions:
1✔
713
            negate = False
1✔
714
            if isinstance(condition, ast.UnaryOp) and isinstance(condition.op, type(UnaryOp.Not)):
1✔
715
                condition = condition.operand
1✔
716
                negate = True
1✔
717

718
            # verifies if condition is an is_instance condition
719
            is_instance_condition = (isinstance(condition, ast.Call) and isinstance(condition.func, ast.Name)
1✔
720
                                     and condition.func.id in is_instance_objs and len(condition.args) == 2)
721

722
            # verifies if condition is a identity condition (is None or is not None)
723
            identity_condition = (isinstance(condition, ast.Compare)
1✔
724
                                  and isinstance(condition.ops[0], (type(BinaryOp.IsNone), type(BinaryOp.IsNotNone))))
725

726
            if identity_condition and isinstance(condition.ops[0], type(BinaryOp.IsNotNone)):
1✔
727
                negate = True
1✔
728

729
            if is_instance_condition or identity_condition:
1✔
730
                if is_instance_condition:
1✔
731
                    left_value = condition.args[0]
1✔
732
                    right_value = condition.args[1]
1✔
733

734
                else:
735
                    left_value = condition.left
1✔
736
                    right_value = Type.none
1✔
737

738
                original = self.get_symbol(left_value)
1✔
739
                if isinstance(original, Variable) and isinstance(left_value, ast.Name):
1✔
740
                    original_id = left_value.id
1✔
741
                    original_type = (original.type
1✔
742
                                     if original_id not in is_instance_symbols
743
                                     else is_instance_symbols[original_id])
744

745
                    instance_obj = self.get_symbol(condition.func.id) if is_instance_condition else None
1✔
746
                    if isinstance(instance_obj, type(Builtin.IsInstance)):
1✔
747
                        is_instance_type = instance_obj._instances_type
1✔
748
                        if isinstance(is_instance_type, list):
1✔
749
                            is_instance_type = Type.union.build(is_instance_type)
1✔
750
                    else:
751
                        is_instance_type = self.get_type(right_value)
1✔
752
                    is_instance_type = is_instance_type.intersect_type(original_type)
1✔
753
                    negation = original_type.except_type(is_instance_type)
1✔
754
                    resulting_type = is_instance_type if not negate else negation
1✔
755

756
                    if original_id not in is_not_instance_symbols:
1✔
757
                        is_instance_symbols[original_id] = resulting_type
1✔
758
                    else:
759
                        is_instance_symbols[original_id] = is_instance_symbols[original_id].except_type(resulting_type)
×
760

761
                    if len(is_instance_symbols) == 1:
1✔
762
                        is_not_instance_symbols[original_id] = is_instance_type if negate else negation
1✔
763
                    elif len(is_not_instance_symbols) == 1:
1✔
764
                        # if there is more than one isinstance it's not possible to determine in compiler time what is
765
                        # the type of the variables on the else body
766
                        is_not_instance_symbols.clear()
1✔
767

768
        for key, value in is_instance_symbols.copy().items():
1✔
769
            if value == self.get_type(self.get_symbol(key)):
1✔
770
                is_instance_symbols.pop(key)
1✔
771
            else:
772
                is_instance_symbols[key] = Variable(value)
1✔
773

774
        for key, value in is_not_instance_symbols.copy().items():
1✔
775
            if value == self.get_type(self.get_symbol(key)):
1✔
776
                is_not_instance_symbols.pop(key)
1✔
777
            else:
778
                is_not_instance_symbols[key] = Variable(value)
1✔
779
        return is_instance_symbols, is_not_instance_symbols
1✔
780

781
    def visit_BinOp(self, bin_op: ast.BinOp) -> Optional[IType]:
1✔
782
        """
783
        Verifies if the types of the operands are valid to the operation
784

785
        If the operation is valid, changes de Python operator by the Boa operator in the syntax tree
786

787
        :param bin_op: the python ast binary operation node
788
        :return: the type of the result of the operation if the operation is valid. Otherwise, returns None
789
        :rtype: IType or None
790
        """
791
        operation = self.validate_binary_operation(bin_op, bin_op.left, bin_op.right)
1✔
792
        if operation is not None:
1✔
793
            bin_op.op = operation
1✔
794
            return operation.result
1✔
795

796
    def validate_binary_operation(self, node: ast.AST, left_op: ast.AST, right_op: ast.AST) -> Optional[IOperation]:
1✔
797
        """
798
        Validates a ast node that represents a binary operation
799

800
        :param node: ast node that represents a binary operation
801
        :param left_op: ast node that represents the left operand of the operation
802
        :param right_op: ast node that represents the right operand of the operation
803
        :return: the corresponding :class:`BinaryOperation` if is valid. None otherwise.
804
        """
805
        if not hasattr(node, 'op'):
1✔
806
            return
×
807

808
        operator: Operator = self.get_operator(node.op)
1✔
809
        l_operand = self.visit(left_op)
1✔
810
        r_operand = self.visit(right_op)
1✔
811

812
        if not isinstance(operator, Operator):
1✔
813
            # the operator is invalid or it was not implemented yet
814
            self._log_error(
×
815
                CompilerError.UnresolvedReference(node.lineno, node.col_offset, type(node.op).__name__)
816
            )
817

818
        try:
1✔
819
            operation: IOperation = self.get_bin_op(operator, r_operand, l_operand)
1✔
820
            if operation is None:
1✔
821
                self._log_error(
×
822
                    CompilerError.NotSupportedOperation(node.lineno, node.col_offset, operator)
823
                )
824
            elif not operation.is_supported:
1✔
825
                # number float division is not supported by Neo VM
826
                self._log_error(
1✔
827
                    CompilerError.NotSupportedOperation(node.lineno, node.col_offset, operator)
828
                )
829
            else:
830
                return operation
1✔
831
        except CompilerError.MismatchedTypes as raised_error:
1✔
832
            raised_error.line = node.lineno
1✔
833
            raised_error.col = node.col_offset
1✔
834
            # raises the exception with the line/col info
835
            self._log_error(raised_error)
1✔
836

837
    def get_bin_op(self, operator: Operator, right: Any, left: Any) -> IOperation:
1✔
838
        """
839
        Returns the binary operation specified by the operator and the types of the operands
840

841
        :param operator: the operator
842
        :param right: right operand
843
        :param left: left operand
844

845
        :return: Returns the corresponding :class:`BinaryOperation` if the types are valid.
846
        :raise MismatchedTypes: raised if the types aren't valid for the operator
847
        """
848
        l_type: IType = self.get_type(left)
1✔
849
        r_type: IType = self.get_type(right)
1✔
850

851
        if l_type is None or r_type is None:
1✔
852
            return BinaryOp.get_operation_by_operator(operator, l_type if l_type is not None else r_type)
×
853

854
        actual_types = (l_type.identifier, r_type.identifier)
1✔
855
        operation: IOperation = BinaryOp.validate_type(operator, l_type, r_type)
1✔
856

857
        if operation is not None:
1✔
858
            return operation
1✔
859
        else:
860
            if operator.requires_right_operand_for_validation():
1✔
861
                left = l_type
1✔
862
                right = r_type
1✔
863
            else:
864
                left = l_type if left is not None else r_type
1✔
865
                right = None
1✔
866

867
            expected_op: BinaryOperation = BinaryOp.get_operation_by_operator(operator, left, right)
1✔
868
            expected_types = (expected_op.left_type.identifier, expected_op.right_type.identifier)
1✔
869
            raise CompilerError.MismatchedTypes(0, 0, expected_types, actual_types)
1✔
870

871
    def visit_UnaryOp(self, un_op: ast.UnaryOp) -> Optional[IType]:
1✔
872
        """
873
        Verifies if the type of the operand is valid to the operation
874

875
        If the operation is valid, changes de Python operator by the Boa operator in the syntax tree
876

877
        :param un_op: the python ast unary operation node
878
        :return: the type of the result of the operation if the operation is valid. Otherwise, returns None
879
        :rtype: IType or None
880
        """
881
        operator: Operator = self.get_operator(un_op.op)
1✔
882
        operand = self.visit(un_op.operand)
1✔
883

884
        if not isinstance(operator, Operator):
1✔
885
            # the operator is invalid or it was not implemented yet
886
            self._log_error(
×
887
                CompilerError.UnresolvedReference(un_op.lineno, un_op.col_offset, type(un_op.op).__name__)
888
            )
889

890
        try:
1✔
891
            operation: UnaryOperation = self.get_un_op(operator, operand)
1✔
892
            if operation is None:
1✔
893
                self._log_error(
×
894
                    CompilerError.NotSupportedOperation(un_op.lineno, un_op.col_offset, operator)
895
                )
896
            elif not operation.is_supported:
1✔
897
                self._log_error(
×
898
                    CompilerError.NotSupportedOperation(un_op.lineno, un_op.col_offset, operator)
899
                )
900
            else:
901
                un_op.op = operation
1✔
902
                return operation.result
1✔
903
        except CompilerError.MismatchedTypes as raised_error:
1✔
904
            raised_error.line = un_op.lineno
1✔
905
            raised_error.col = un_op.col_offset
1✔
906
            # raises the exception with the line/col info
907
            self._log_error(raised_error)
1✔
908

909
    def get_un_op(self, operator: Operator, operand: Any) -> UnaryOperation:
1✔
910
        """
911
        Returns the binary operation specified by the operator and the types of the operands
912

913
        :param operator: the operator
914
        :param operand: the operand
915

916
        :return: Returns the corresponding :class:`UnaryOperation` if the types are valid.
917
        :raise MismatchedTypes: raised if the types aren't valid for the operator
918
        """
919
        op_type: IType = self.get_type(operand)
1✔
920

921
        actual_type: str = op_type.identifier
1✔
922
        operation: UnaryOperation = UnaryOp.validate_type(operator, op_type)
1✔
923

924
        if operation is not None:
1✔
925
            return operation
1✔
926
        else:
927
            expected_op: UnaryOperation = UnaryOp.get_operation_by_operator(operator)
1✔
928
            expected_type: str = expected_op.operand_type.identifier
1✔
929
            raise CompilerError.MismatchedTypes(0, 0, expected_type, actual_type)
1✔
930

931
    def visit_Assert(self, assert_: ast.Assert):
1✔
932
        """
933
        Verifies if the types of condition and error message in the assert are valid
934

935
        If the operations are valid, changes de Python operator by the Boa operator in the syntax tree
936

937
        :param assert_: the python ast assert operation node
938
        :return: the type of the result of the operation if the operation is valid. Otherwise, returns None
939
        """
940
        self.visit(assert_.test)
1✔
941

942
        if assert_.msg is not None:
1✔
943
            msg = self.visit(assert_.msg)
1✔
944
            msg_type = self.get_type(msg)
1✔
945

946
            if not Type.str.is_type_of(msg_type) and not Type.bytes.is_type_of(msg_type):
1✔
947

948
                # TODO: remove this error when str constructor is implemented
949
                self._log_error(
1✔
950
                    CompilerError.MismatchedTypes(
951
                        assert_.msg.lineno, assert_.msg.col_offset,
952
                        expected_type_id=Type.str.identifier,
953
                        actual_type_id=str(msg_type)
954
                    )
955
                )
956

957
    def visit_Compare(self, compare: ast.Compare) -> Optional[IType]:
1✔
958
        """
959
        Verifies if the types of the operands are valid to the compare operations
960

961
        If the operations are valid, changes de Python operator by the Boa operator in the syntax tree
962

963
        :param compare: the python ast compare operation node
964
        :return: the type of the result of the operation if the operation is valid. Otherwise, returns None
965
        :rtype: IType or None
966
        """
967
        if len(compare.comparators) != len(compare.ops):
1✔
968
            self._log_error(
×
969
                CompilerError.IncorrectNumberOfOperands(
970
                    compare.lineno,
971
                    compare.col_offset,
972
                    len(compare.ops),
973
                    len(compare.comparators) + 1  # num comparators + compare.left
974
                )
975
            )
976

977
        line = compare.lineno
1✔
978
        col = compare.col_offset
1✔
979
        try:
1✔
980
            return_type = None
1✔
981
            l_operand = self.visit_value(compare.left)
1✔
982
            for index, op in enumerate(compare.ops):
1✔
983
                operator: Operator = self.get_operator(op)
1✔
984
                r_operand = self.visit_value(compare.comparators[index])
1✔
985

986
                if not isinstance(operator, Operator):
1✔
987
                    # the operator is invalid or it was not implemented yet
988
                    self._log_error(
×
989
                        CompilerError.UnresolvedReference(line, col, type(op).__name__)
990
                    )
991

992
                operation: IOperation = self.get_bin_op(operator, r_operand, l_operand)
1✔
993
                if operation is None:
1✔
994
                    self._log_error(
×
995
                        CompilerError.NotSupportedOperation(line, col, operator)
996
                    )
997
                elif not operation.is_supported:
1✔
998
                    self._log_error(
×
999
                        CompilerError.NotSupportedOperation(line, col, operator)
1000
                    )
1001
                else:
1002
                    compare.ops[index] = operation
1✔
1003
                    return_type = operation.result
1✔
1004

1005
                line = compare.comparators[index].lineno
1✔
1006
                col = compare.comparators[index].col_offset
1✔
1007
                l_operand = r_operand
1✔
1008

1009
            return return_type
1✔
1010
        except CompilerError.MismatchedTypes as raised_error:
1✔
1011
            raised_error.line = line
1✔
1012
            raised_error.col = col
1✔
1013
            # raises the exception with the line/col info
1014
            self._log_error(raised_error)
1✔
1015

1016
    def visit_BoolOp(self, bool_op: ast.BoolOp) -> Optional[IType]:
1✔
1017
        """
1018
        Verifies if the types of the operands are valid to the boolean operations
1019

1020
        If the operations are valid, changes de Python operator by the Boa operator in the syntax tree
1021

1022
        :param bool_op: the python ast boolean operation node
1023
        :return: the type of the result of the operation if the operation is valid. Otherwise, returns None
1024
        :rtype: IType or None
1025
        """
1026
        lineno: int = bool_op.lineno
1✔
1027
        col_offset: int = bool_op.col_offset
1✔
1028
        try:
1✔
1029
            return_type: IType = None
1✔
1030
            bool_operation: IOperation = None
1✔
1031
            operator: Operator = self.get_operator(bool_op.op)
1✔
1032

1033
            if not isinstance(operator, Operator):
1✔
1034
                # the operator is invalid or it was not implemented yet
1035
                self._log_error(
×
1036
                    CompilerError.UnresolvedReference(lineno, col_offset, type(operator).__name__)
1037
                )
1038

1039
            l_operand = self.visit(bool_op.values[0])
1✔
1040
            for index, operand in enumerate(bool_op.values[1:]):
1✔
1041
                r_operand = self.visit(operand)
1✔
1042

1043
                operation: IOperation = self.get_bin_op(operator, r_operand, l_operand)
1✔
1044
                if operation is None:
1✔
1045
                    self._log_error(
×
1046
                        CompilerError.NotSupportedOperation(lineno, col_offset, operator)
1047
                    )
1048
                elif bool_operation is None:
1✔
1049
                    return_type = operation.result
1✔
1050
                    bool_operation = operation
1✔
1051

1052
                lineno = operand.lineno
1✔
1053
                col_offset = operand.col_offset
1✔
1054
                l_operand = r_operand
1✔
1055

1056
            bool_op.op = bool_operation
1✔
1057
            return return_type
1✔
1058
        except CompilerError.MismatchedTypes as raised_error:
1✔
1059
            raised_error.line = lineno
1✔
1060
            raised_error.col = col_offset
1✔
1061
            # raises the exception with the line/col info
1062
            self._log_error(raised_error)
1✔
1063

1064
    def get_operator(self, node: Union[ast.operator, Operator, IOperation]) -> Optional[Operator]:
1✔
1065
        """
1066
        Gets the :class:`Operator` equivalent to given Python ast operator
1067

1068
        :param node: object with the operator data
1069
        :type node: ast.operator or Operator or IOperation
1070
        :return: the Boa operator equivalent to the node. None if it doesn't exit.
1071
        :rtype: Operator or None
1072
        """
1073
        # the node has already been visited
1074
        if isinstance(node, Operator):
1✔
1075
            return node
×
1076
        elif isinstance(node, IOperation):
1✔
1077
            return node.operator
1✔
1078

1079
        return Operator.get_operation(node)
1✔
1080

1081
    def visit_Call(self, call: ast.Call):
1✔
1082
        """
1083
        Verifies if the number of arguments is correct
1084

1085
        :param call: the python ast function call node
1086
        :return: the result type of the called function
1087
        """
1088
        if isinstance(call.func, ast.Name):
1✔
1089
            callable_id: str = call.func.id
1✔
1090
            is_internal = hasattr(call, 'is_internal_call') and call.is_internal_call
1✔
1091
            callable_target = self.get_symbol(callable_id, is_internal)
1✔
1092
        else:
1093
            callable_id, callable_target = self.get_callable_and_update_args(call)  # type: str, ISymbol
1✔
1094

1095
        callable_target = self.validate_builtin_callable(callable_id, callable_target, call.args)
1✔
1096

1097
        if not isinstance(callable_target, Callable):
1✔
1098
            # if the outer call is a builtin, enable call even without the import
1099
            builtin_symbol = Builtin.get_symbol(callable_id)
1✔
1100
            if builtin_symbol is not None:
1✔
1101
                callable_target = builtin_symbol
×
1102

1103
        callable_method_id = None
1✔
1104
        if isinstance(callable_target, ClassType):
1✔
1105
            callable_target = callable_target.constructor_method()
1✔
1106
            callable_method_id = constants.INIT_METHOD_ID
1✔
1107

1108
        if not isinstance(callable_target, Callable):
1✔
1109
            # the symbol doesn't exists or is not a function
1110
            # if it is None, the error was already logged
1111
            if callable_id is not None:
1✔
1112
                if callable_method_id is not None:
1✔
1113
                    callable_id = '{0}.{1}()'.format(callable_id, callable_method_id)
1✔
1114
                self._log_error(
1✔
1115
                    CompilerError.UnresolvedReference(call.func.lineno, call.func.col_offset, callable_id)
1116
                )
1117
        else:
1118
            if not self.check_call_scope(call, callable_target, callable_id):
1✔
1119
                # errors are logged and handled by the method itself
1120
                return self.get_type(callable_target)
1✔
1121

1122
            if callable_target is Builtin.NewEvent:
1✔
1123
                return callable_target.return_type
×
1124

1125
            private_identifier = None  # used for validating internal builtin methods
1✔
1126
            if self.validate_callable_arguments(call, callable_target):
1✔
1127
                args = [self.get_type(param, use_metatype=True) for param in call.args]
1✔
1128
                if isinstance(callable_target, IBuiltinMethod):
1✔
1129
                    # if the arguments are not generic, build the specified method
1130
                    if callable_target.raw_identifier.startswith('-'):
1✔
1131
                        private_identifier = callable_target.raw_identifier
1✔
1132
                    callable_target: IBuiltinMethod = callable_target.build(args)
1✔
1133
                    if not callable_target.is_supported:
1✔
1134
                        self._log_error(
1✔
1135
                            CompilerError.NotSupportedOperation(call.lineno, call.col_offset,
1136
                                                                callable_target.not_supported_str(callable_id))
1137
                        )
1138
                        return callable_target.return_type
1✔
1139

1140
                    if callable_target.is_cast:
1✔
1141
                        # every typing cast raises a warning
1142
                        cast_types = callable_target.cast_types
1✔
1143
                        if cast_types is None:
1✔
1144
                            origin_type_id = 'unknown'
×
1145
                            cast_type_id = callable_target.type.identifier
×
1146
                        else:
1147
                            origin_type_id = cast_types[0].identifier
1✔
1148
                            cast_type_id = cast_types[1].identifier
1✔
1149

1150
                        if not hasattr(call, 'is_internal_call') or not call.is_internal_call:
1✔
1151
                            self._log_warning(
1✔
1152
                                CompilerWarning.TypeCasting(call.lineno, call.col_offset,
1153
                                                            origin_type_id=origin_type_id,
1154
                                                            cast_type_id=cast_type_id)
1155
                            )
1156

1157
                self.validate_passed_arguments(call, args, callable_id, callable_target)
1✔
1158

1159
            if private_identifier is not None and callable_target.identifier != private_identifier:
1✔
1160
                # updates the callable_id for validation of internal builtin that accepts generic variables
1161
                callable_id = private_identifier
×
1162
            self.update_callable_after_validation(call, callable_id, callable_target)
1✔
1163

1164
        return self.get_type(callable_target)
1✔
1165

1166
    def get_callable_and_update_args(self, call: ast.Call) -> Tuple[str, ISymbol]:
1✔
1167
        attr: Attribute = self.visit(call.func)
1✔
1168
        if not isinstance(attr, Attribute):
1✔
1169
            attr_id = str(attr)
×
1170
            attr_id_split = attr_id.split(constants.ATTRIBUTE_NAME_SEPARATOR)
×
1171
            attr_call_id = attr_id
×
1172
            if len(attr_id_split) > 0:
×
1173
                attr_call_id = constants.ATTRIBUTE_NAME_SEPARATOR.join(attr_id_split[:-1])
×
1174

1175
            self._log_error(
×
1176
                CompilerError.UnresolvedReference(call.func.lineno, call.func.col_offset, attr_call_id)
1177
            )
1178

1179
            return attr_id, None
×
1180

1181
        arg0, callable_target, callable_id = attr.values
1✔
1182

1183
        if isinstance(arg0, Package):
1✔
1184
            # visit works only with ast classes
1185
            package = arg0
1✔
1186
            package_symbol = self.get_symbol(package.identifier, check_raw_id=True)
1✔
1187

1188
            while package_symbol is None and package.parent is not None:
1✔
1189
                package = package.parent
1✔
1190
                package_symbol = self.get_symbol(package.identifier, check_raw_id=True)
1✔
1191

1192
            if package_symbol is None:
1✔
1193
                arg0_identifier = package.identifier
×
1194
            elif isinstance(package_symbol, Package):
1✔
1195
                arg0_identifier = package_symbol
1✔
1196
            else:
1197
                arg0_identifier = package
1✔
1198

1199
        elif isinstance(arg0, UserClass):
1✔
1200
            arg0_identifier = arg0.identifier
×
1201
        elif isinstance(arg0, ast.AST):
1✔
1202
            arg0_identifier = self.visit(arg0)
1✔
1203
        else:
1204
            arg0_identifier = None
1✔
1205

1206
        if isinstance(arg0_identifier, ast.Name):
1✔
1207
            arg0_identifier = arg0_identifier.id
1✔
1208

1209
        if (callable_target is not None
1✔
1210
                and not isinstance(self.get_symbol(arg0_identifier), (IType, Import, Package))
1211
                and not isinstance(arg0_identifier, UserClass)
1212
                and (len(call.args) < 1 or call.args[0] != arg0)):
1213
            # move self to the arguments
1214
            # don't move if it's class method
1215
            if not (isinstance(arg0, ClassType) and callable_id in arg0.class_methods):
1✔
1216
                call.args.insert(0, arg0)
1✔
1217

1218
        if len(call.args) > 0 and isinstance(callable_target, IBuiltinMethod) and callable_target.has_self_argument:
1✔
1219
            self_type: IType = self.get_type(call.args[0])
1✔
1220
            caller = self.get_symbol(arg0_identifier)
1✔
1221
            if isinstance(caller, IType) and not caller.is_type_of(self_type):
1✔
1222
                self_type = caller
1✔
1223
            callable_target = callable_target.build(self_type)
1✔
1224

1225
        return callable_id, callable_target
1✔
1226

1227
    def check_call_scope(self, call: ast.Call, callable: Callable, callable_id: str):
1✔
1228
        error_count = len(self.errors)
1✔
1229
        if not isinstance(call.func, ast.Attribute):
1✔
1230
            # if the call doesn't came from an attribute, there's nothing to be validated
1231
            return True
1✔
1232

1233
        attr_node: ast.Attribute = call.func
1✔
1234
        if isinstance(attr_node.value, ast.Name):
1✔
1235
            attribute_symbol = self.get_symbol(attr_node.value.id)
1✔
1236
        else:
1237
            attribute_symbol = self.get_symbol(attr_node.value)
1✔
1238
        is_from_type_name = isinstance(attribute_symbol, IType)
1✔
1239

1240
        attribute_type = self.get_type(attribute_symbol)
1✔
1241
        if not isinstance(attribute_type, UserClass):
1✔
1242
            # TODO: change when class specific scopes are implemented in the built-ins
1243
            return True
1✔
1244

1245
        # TODO: remove this verification when calling an instance function from a class is implemented
1246
        if is_from_type_name and isinstance(callable, Method) and hasattr(attribute_symbol, 'instance_methods') \
1✔
1247
                and callable_id in attribute_symbol.instance_methods:
1248
            callable_complete_id = f'{attribute_type.identifier}.{callable_id}'
1✔
1249
            self._log_error(
1✔
1250
                CompilerError.NotSupportedOperation(call.func.lineno, call.func.col_offset, callable_complete_id)
1251
            )
1252
        elif is_from_type_name and callable_id not in attribute_type.class_symbols:
1✔
1253
            # the current symbol doesn't exist in the class scope
1254
            callable_complete_id = f'{attribute_type.identifier}.{callable_id}'
×
1255
            self._log_error(
×
1256
                CompilerError.UnresolvedReference(call.func.lineno, call.func.col_offset, callable_complete_id)
1257
            )
1258
        elif not is_from_type_name and callable_id not in attribute_type.instance_symbols:
1✔
1259
            # the current symbol doesn't exist in the instance scope
1260
            callable_complete_id = f'{attribute_type.identifier}().{callable_id}'
1✔
1261
            self._log_error(
1✔
1262
                CompilerError.UnresolvedReference(call.func.lineno, call.func.col_offset, callable_complete_id)
1263
            )
1264

1265
        return len(self.errors) == error_count
1✔
1266

1267
    def validate_builtin_callable(self, callable_id: str, callable_target: ISymbol,
1✔
1268
                                  call_args: List[ast.AST] = None) -> ISymbol:
1269
        if call_args is None:
1✔
1270
            call_args = []
×
1271

1272
        if not isinstance(callable_target, Callable):
1✔
1273
            # verify if it is a builtin method with its name shadowed
1274
            call_target = Builtin.get_symbol(callable_id)
1✔
1275
            if not isinstance(call_target, Callable) and self.is_exception(callable_id):
1✔
1276
                call_target = Builtin.Exception
1✔
1277

1278
            callable_target = call_target if call_target is not None else callable_target
1✔
1279
        if isinstance(callable_target, IBuiltinMethod):
1✔
1280
            # verify if it's a variation of the default builtin method
1281
            args = [self.get_type(param, use_metatype=True) for param in call_args]
1✔
1282

1283
            from boa3.model.builtin.method import SuperMethod
1✔
1284
            # TODO: change when implementing super() with args
1285
            if (isinstance(callable_target, SuperMethod)
1✔
1286
                    and isinstance(self._current_method, Method) and self._current_method.has_cls_or_self):
1287
                args.insert(0, self._current_class)
1✔
1288

1289
            new_target = callable_target.build(args)
1✔
1290
            if new_target is not None:
1✔
1291
                callable_target = new_target
1✔
1292

1293
            if isinstance(callable_target, SuperMethod) and callable_target not in self._super_calls:
1✔
1294
                self._super_calls.append(callable_target)
1✔
1295
        return callable_target
1✔
1296

1297
    def validate_callable_arguments(self, call: ast.Call, callable_target: Callable) -> bool:
1✔
1298
        if callable_target.has_starred_argument and not hasattr(call, 'checked_starred_args'):
1✔
1299

1300
            if (len(call.args) >= len(callable_target.args)
1✔
1301
                    and (len(call.args) == 0 or not isinstance(call.args[0], ast.Starred))):
1302
                # starred argument is always the last argument
1303
                len_args_without_starred = len(callable_target.args) - 1
1✔
1304
                args = self.parse_to_node(str(Type.tuple.default_value), call)
1✔
1305

1306
                # include the arguments into a tuple to be assigned to the starred argument
1307
                args.elts = call.args[len_args_without_starred:]
1✔
1308
                call.args[len_args_without_starred:] = [args]
1✔
1309

1310
            call.checked_starred_args = True
1✔
1311

1312
        ignore_first_argument = int(callable_target.has_cls_or_self)  # 1 if True, 0 otherwise
1✔
1313
        len_call_args = len(call.args)
1✔
1314
        len_call_keywords = len(call.keywords)
1✔
1315
        callable_required_args = len(callable_target.args_without_default) - ignore_first_argument
1✔
1316

1317
        # verifies if a non-default arg is being called as a keyword
1318
        necessary_kwargs = list(callable_target.args_without_default.keys())[len_call_args:callable_required_args]
1✔
1319
        keywords_names_used = []
1✔
1320
        all_required_arg_have_values = True
1✔
1321
        for keyword in call.keywords:
1✔
1322
            keywords_names_used.append(keyword.arg)
1✔
1323
        index = 0
1✔
1324
        while index < len(necessary_kwargs) and all_required_arg_have_values:
1✔
1325
            if necessary_kwargs[index] not in keywords_names_used:
1✔
1326
                all_required_arg_have_values = False
1✔
1327
            index += 1
1✔
1328

1329
        # verifies if a kwarg is being used but is not an argument for the function
1330
        index = 0
1✔
1331
        unexpected_kwarg = None
1✔
1332
        while unexpected_kwarg is None and index < len(call.keywords):
1✔
1333
            if call.keywords[index].arg not in list(callable_target.args.keys()):
1✔
1334
                unexpected_kwarg = call.keywords[index].value
1✔
1335
            index += 1
1✔
1336

1337
        # verifies if a kwarg that was already used as a positional argument is being used again
1338
        implicit_args = list(callable_target.args_without_default.keys())[:len_call_args]
1✔
1339
        index = 0
1✔
1340
        already_called_arg = None
1✔
1341
        kwargs_used_names = []
1✔
1342
        for keyword in call.keywords:
1✔
1343
            kwargs_used_names.append(keyword.arg)
1✔
1344
        while already_called_arg is None and index < len(implicit_args):
1✔
1345
            if implicit_args[index] in kwargs_used_names:
1✔
1346
                already_called_arg = call.keywords[index].value
1✔
1347
            index += 1
1✔
1348

1349
        if len_call_args > len(callable_target.args) or unexpected_kwarg is not None or already_called_arg is not None:
1✔
1350
            if unexpected_kwarg is not None:
1✔
1351
                unexpected_arg = unexpected_kwarg
1✔
1352
            elif already_called_arg is not None:
1✔
1353
                unexpected_arg = already_called_arg
1✔
1354
            else:
1355
                unexpected_arg = call.args[len(callable_target.args) + ignore_first_argument]
1✔
1356
            self._log_error(
1✔
1357
                CompilerError.UnexpectedArgument(unexpected_arg.lineno, unexpected_arg.col_offset)
1358
            )
1359
            return False
1✔
1360
        elif len_call_args + len_call_keywords < callable_required_args or not all_required_arg_have_values:
1✔
1361
            missed_arg = list(callable_target.args)[len(call.args) + ignore_first_argument]
1✔
1362
            self._log_error(
1✔
1363
                CompilerError.UnfilledArgument(call.lineno, call.col_offset, missed_arg)
1364
            )
1365
            return False
1✔
1366

1367
        if isinstance(callable_target, IBuiltinMethod) and callable_target.requires_reordering:
1✔
1368
            if not hasattr(call, 'was_reordered') or not call.was_reordered:
1✔
1369
                callable_target.reorder(call.args)
1✔
1370
                call.was_reordered = True
1✔
1371

1372
        if callable_required_args <= len_call_args < len(callable_target.args):
1✔
1373
            included_args = len_call_args - callable_required_args
1✔
1374
            for default in callable_target.defaults[included_args:]:
1✔
1375
                call.args.append(self.clone(default))
1✔
1376
        return True
1✔
1377

1378
    def validate_passed_arguments(self, call: ast.Call, args_types: List[IType], callable_id: str, callable: Callable):
1✔
1379
        if isinstance(callable, IBuiltinMethod):
1✔
1380
            builtin_analyser = BuiltinFunctionCallAnalyser(self, call, callable_id, callable, self._log)
1✔
1381
            if builtin_analyser.validate():
1✔
1382
                self.errors.extend(builtin_analyser.errors)
1✔
1383
                self.warnings.extend(builtin_analyser.warnings)
1✔
1384
                return
1✔
1385

1386
        param_types = []
1✔
1387
        ignore_first_argument = int(callable.has_cls_or_self)  # 1 if True, 0 otherwise
1✔
1388
        is_first_arg_cls_of_self = ignore_first_argument and len(call.args) == len(callable.args)
1✔
1389

1390
        # validate positional parameters
1391
        if is_first_arg_cls_of_self:
1✔
1392
            (self_id, self_value) = list(callable.args.items())[0]
1✔
1393
            param_type = self._validate_argument_type(call.args[0], self_value.type, use_metatype=True)
1✔
1394
            param_types.append(param_type)
1✔
1395

1396
        for index, param in enumerate(call.args[is_first_arg_cls_of_self:]):
1✔
1397
            (arg_id, arg_value) = list(callable.args.items())[ignore_first_argument + index]
1✔
1398

1399
            param_type = self._validate_argument_type(param, arg_value.type, use_metatype=True)
1✔
1400
            param_types.append(param_type)
1✔
1401

1402
        # validate keyword arguments
1403
        for param in call.keywords:
1✔
1404
            arg_value = callable.args[param.arg]
1✔
1405
            param = param.value
1✔
1406
            param_type = self._validate_argument_type(param, arg_value.type)
1✔
1407
            param_types.append(param_type)
1✔
1408

1409
    def _validate_argument_type(self, param: ast.AST, arg_type: IType, use_metatype: bool = False) -> Optional[IType]:
1✔
1410
        param_type = self.get_type(param, use_metatype=use_metatype)
1✔
1411
        if arg_type.is_type_of(param_type):
1✔
1412
            return param_type
1✔
1413
        else:
1414
            self._log_error(
1✔
1415
                CompilerError.MismatchedTypes(
1416
                    param.lineno, param.col_offset,
1417
                    arg_type.identifier,
1418
                    param_type.identifier
1419
                ))
1420

1421
        return None
1✔
1422

1423
    def update_callable_after_validation(self, call: ast.Call, callable_id: str, callable_target: Callable):
1✔
1424
        callable_target.add_call_origin(call)
1✔
1425

1426
        # if the arguments are not generic, include the specified method in the symbol table
1427
        if (isinstance(callable_target, IBuiltinMethod)
1✔
1428
                and callable_target.identifier != callable_id
1429
                and callable_target.raw_identifier == callable_id):
1430
            if callable_target.identifier not in self.symbols:
1✔
1431
                self.symbols[callable_target.identifier] = callable_target
1✔
1432
            call.func = ast.Name(lineno=call.func.lineno, col_offset=call.func.col_offset,
1✔
1433
                                 ctx=ast.Load(), id=callable_target.identifier)
1434

1435
    def visit_Raise(self, raise_node: ast.Raise):
1✔
1436
        """
1437
        Visitor of the raise node
1438

1439
        Verifies if the raised object is a exception
1440

1441
        :param raise_node: the python ast raise node
1442
        """
1443
        raised = self.visit(raise_node.exc)
1✔
1444
        raised_type: IType = self.get_type(raised)
1✔
1445
        if raised_type is not Type.exception:
1✔
1446
            self._log_error(
1✔
1447
                CompilerError.MismatchedTypes(
1448
                    raise_node.lineno, raise_node.col_offset,
1449
                    actual_type_id=raised_type.identifier,
1450
                    expected_type_id=Type.exception.identifier
1451
                ))
1452

1453
    def visit_Try(self, try_node: ast.Try):
1✔
1454
        """
1455
        Visitor of the try node
1456

1457
        :param try_node: the python ast try node
1458
        """
1459
        self.validate_except_handlers(try_node)
1✔
1460

1461
        for stmt in try_node.body:
1✔
1462
            self.visit(stmt)
1✔
1463

1464
        for exception_handler in try_node.handlers:
1✔
1465
            self.visit(exception_handler)
1✔
1466

1467
        for stmt in try_node.orelse:
1✔
1468
            self.visit(stmt)
1✔
1469

1470
        for stmt in try_node.finalbody:
1✔
1471
            self.visit(stmt)
1✔
1472

1473
    def validate_except_handlers(self, try_node: ast.Try):
1✔
1474
        if len(try_node.handlers) > 1:
1✔
1475
            exception_handlers: List[ast.ExceptHandler] = try_node.handlers.copy()
×
1476
            general_exc_handler: ast.ExceptHandler = next(
×
1477
                (handler
1478
                 for handler in exception_handlers
1479
                 if (handler.type is None or  # no specified exception or is BaseException
1480
                     (isinstance(handler.type, ast.Name) and handler.type.id == BaseException.__name__)
1481
                     )
1482
                 ), exception_handlers[0]
1483
            )
1484

1485
            try_node.handlers = [general_exc_handler]
×
1486
            exception_handlers.remove(general_exc_handler)
×
1487

1488
            for handler in exception_handlers:
×
1489
                warnings = len(self.warnings)
×
1490
                self.visit(handler)
×
1491
                if warnings == len(self.warnings):
×
1492
                    self._log_using_specific_exception_warning(handler.type)
×
1493

1494
        if len(try_node.handlers) == 1:
1✔
1495
            self.visit(try_node.handlers[0])
1✔
1496

1497
    def visit_ExceptHandler(self, node: ast.ExceptHandler):
1✔
1498
        """
1499
        Visitor of the try except node
1500

1501
        :param node: the python ast try except node
1502
        """
1503
        logged_errors = False
1✔
1504

1505
        exception_type: IType = self.get_type(node.type)
1✔
1506
        if node.type is not None and exception_type is not Type.exception:
1✔
1507
            self._log_error(
×
1508
                CompilerError.MismatchedTypes(line=node.type.lineno,
1509
                                              col=node.type.col_offset,
1510
                                              expected_type_id=Type.exception.identifier,
1511
                                              actual_type_id=exception_type.identifier)
1512
            )
1513
            logged_errors = True
×
1514

1515
        if node.name is not None:
1✔
1516
            # TODO: remove when getting the exception is implemented
1517
            self._log_error(
1✔
1518
                CompilerError.NotSupportedOperation(line=node.lineno,
1519
                                                    col=node.col_offset,
1520
                                                    symbol_id='naming exceptions')
1521
            )
1522
            logged_errors = True
1✔
1523

1524
        if not logged_errors and node.type is not None:
1✔
1525
            self._log_using_specific_exception_warning(node.type)
1✔
1526

1527
        self.generic_visit(node)
1✔
1528

1529
    def _log_using_specific_exception_warning(self, node: ast.AST):
1✔
1530
        if node is None:
1✔
1531
            exc_id = 'Exception'
×
1532
        elif isinstance(node, ast.Name):
1✔
1533
            exc_id = node.id
1✔
1534
        else:
1535
            exc_id = self.get_type(node).identifier
×
1536

1537
        self._log_warning(
1✔
1538
            CompilerWarning.UsingSpecificException(line=node.lineno,
1539
                                                   col=node.col_offset,
1540
                                                   exception_id=exc_id)
1541
        )
1542

1543
    def visit_value(self, node: ast.AST):
1✔
1544
        result = self.visit(node)
1✔
1545

1546
        if isinstance(node, ast.Attribute):
1✔
1547
            if isinstance(result, str):
1✔
1548
                return self.get_symbol(result)
×
1549
            else:
1550
                origin, value, attribute = result.values
1✔
1551
                if value is None and isinstance(origin, ast.Name):
1✔
1552
                    value = self.get_symbol(origin.id)
×
1553
                if value is not None:
1✔
1554
                    return value
1✔
1555

1556
        return result
1✔
1557

1558
    def visit_Attribute(self, attribute: ast.Attribute) -> Union[str, Attribute]:
1✔
1559
        """
1560
        Gets the attribute inside the ast node
1561

1562
        :param attribute: the python ast attribute node
1563
        :return: returns the type of the value, the attribute symbol and its id if the attribute exists.
1564
                 Otherwise, returns None
1565
        """
1566
        is_internal = hasattr(attribute, 'is_internal_call') and attribute.is_internal_call
1✔
1567
        value: Optional[Union[str, ISymbol]] = self.get_symbol(attribute.value.id, is_internal) \
1✔
1568
            if isinstance(attribute.value, ast.Name) else self.visit(attribute.value)
1569

1570
        if value is None and isinstance(attribute.value, ast.Name):
1✔
1571
            return '{0}.{1}'.format(attribute.value.id, attribute.attr)
1✔
1572

1573
        symbol = None
1✔
1574
        if isinstance(value, str) and not isinstance(attribute.value, ast.Str):
1✔
1575
            symbol = self.get_symbol(value)
1✔
1576
            if symbol is None:
1✔
1577
                return '{0}.{1}'.format(value, attribute.attr)
1✔
1578
        if isinstance(value, ISymbol):
1✔
1579
            symbol = value
1✔
1580

1581
        if isinstance(symbol, Attribute):
1✔
1582
            symbol = symbol.attr_symbol
1✔
1583
        if isinstance(symbol, IExpression):
1✔
1584
            symbol = symbol.type
1✔
1585
        if hasattr(symbol, 'symbols') and attribute.attr in symbol.symbols:
1✔
1586
            attr_symbol = symbol.symbols[attribute.attr]
1✔
1587
        elif isinstance(symbol, Package) and attribute.attr in symbol.inner_packages:
1✔
1588
            attr_symbol = symbol.inner_packages[attribute.attr]
1✔
1589
        else:
1590
            attr_symbol: Optional[ISymbol] = self.get_symbol(attribute.attr)
1✔
1591

1592
        origin = value
1✔
1593
        module_symbols = origin.symbols if isinstance(origin, Package) else symbol.methods if isinstance(symbol, Import) else None
1✔
1594
        if isinstance(origin, Attribute):
1✔
1595
            while isinstance(origin, Attribute):
1✔
1596
                origin = origin.value
1✔
1597
        else:
1598
            origin = attribute.value
1✔
1599

1600
        is_invalid_method = module_symbols is not None and isinstance(attr_symbol, Method) and attribute.attr not in module_symbols
1✔
1601
        is_from_class_name = isinstance(origin, ast.Name) and isinstance(self.get_symbol(origin.id), UserClass)
1✔
1602
        is_instance_variable_from_class = (isinstance(symbol, UserClass)
1✔
1603
                                           and attribute.attr in symbol.instance_variables)
1604
        is_class_variable_from_class = isinstance(symbol, UserClass) and attribute.attr in symbol.class_variables and not symbol.is_interface
1✔
1605
        is_property_from_class = isinstance(symbol, UserClass) and attribute.attr in symbol.properties and not symbol.is_interface
1✔
1606

1607
        if ((attr_symbol is None and hasattr(symbol, 'symbols'))
1✔
1608
                or is_invalid_method
1609
                or (is_from_class_name and is_instance_variable_from_class)
1610
                or (is_from_class_name and is_property_from_class)):
1611
            # if it couldn't find the symbol in the attribute symbols, raise unresolved reference
1612
            self._log_error(
1✔
1613
                CompilerError.UnresolvedReference(
1614
                    attribute.lineno, attribute.col_offset,
1615
                    symbol_id='{0}.{1}'.format(symbol.identifier if module_symbols is None else attribute.value.id, attribute.attr)
1616
                ))
1617
            return Attribute(attribute.value, None, attr_symbol, attribute)
1✔
1618

1619
        if is_class_variable_from_class and isinstance(attribute.ctx, ast.Store):
1✔
1620
            # reassign class variables in objects is not supported yet
1621
            self._log_error(
1✔
1622
                CompilerError.NotSupportedOperation(
1623
                    attribute.lineno, attribute.col_offset,
1624
                    symbol_id='reassign class variables'
1625
                ))
1626
            return Attribute(attribute.value, None, attr_symbol, attribute)
1✔
1627

1628
        if not is_from_class_name and is_property_from_class and isinstance(attribute.ctx, ast.Store):
1✔
1629
            # setting values for properties in objects is not supported yet
1630
            # @property.setter is not implemented
1631
            self._log_error(
×
1632
                CompilerError.NotSupportedOperation(
1633
                    attribute.lineno, attribute.col_offset,
1634
                    symbol_id='setting values for properties'
1635
                ))
1636
            return Attribute(attribute.value, None, attr_symbol, attribute)
×
1637

1638
        attr_type = value.type if isinstance(value, IExpression) else value
1✔
1639
        # for checking during the code generation
1640
        if (self.is_implemented_class_type(attr_type) and
1✔
1641
                not (isinstance(attribute.value, ast.Name) and attribute.value.id == attr_type.identifier) and
1642
                (not hasattr(attribute, 'generate_value') or not attribute.generate_value)):
1643
            attribute.generate_value = True
1✔
1644

1645
        if (isinstance(symbol, (Package, Attribute))
1✔
1646
                or (isinstance(symbol, ClassType) and isinstance(value, (Package, Attribute)))):
1647
            attr_value = symbol if not isinstance(symbol, PythonClass) else attribute.value
1✔
1648
        else:
1649
            attr_value = attribute.value
1✔
1650

1651
        if isinstance(attr_symbol, Property):
1✔
1652
            if isinstance(attribute.ctx, ast.Load):
1✔
1653
                attr_symbol.getter.add_call_origin(attribute)
1✔
1654
        return Attribute(attr_value, attribute.attr, attr_symbol, attribute)
1✔
1655

1656
    def visit_Constant(self, constant: ast.Constant) -> Any:
1✔
1657
        """
1658
        Visitor of constant values node
1659

1660
        :param constant: the python ast constant value node
1661
        :return: the value of the constant
1662
        """
1663
        if isinstance(constant, ast.Num) and not isinstance(constant.value, int):
1✔
1664
            # only integer numbers are allowed
1665
            self._log_error(
×
1666
                CompilerError.InvalidType(constant.lineno, constant.col_offset, symbol_id=type(constant.value).__name__)
1667
            )
1668
        return constant.value
1✔
1669

1670
    def visit_Num(self, num: ast.Num) -> int:
1✔
1671
        """
1672
        Verifies if the number is an integer
1673

1674
        :param num: the python ast number node
1675
        :return: returns the value of the number
1676
        """
1677
        if not isinstance(num.n, int):
×
1678
            # only integer numbers are allowed
1679
            self._log_error(
×
1680
                CompilerError.InvalidType(num.lineno, num.col_offset, symbol_id=type(num.n).__name__)
1681
            )
1682
        return num.n
×
1683

1684
    def visit_Str(self, string: ast.Str) -> str:
1✔
1685
        """
1686
        Visitor of literal string node
1687

1688
        :param string: the python ast string node
1689
        :return: the value of the string
1690
        """
1691
        return string.s
×
1692

1693
    def visit_Bytes(self, bts: ast.Bytes) -> bytes:
1✔
1694
        """
1695
        Visitor of literal bytes node
1696

1697
        :param bts: the python ast bytes node
1698
        :return: the value of the bytes
1699
        """
1700
        return bts.s
×
1701

1702
    def visit_Tuple(self, tup_node: ast.Tuple) -> Tuple[Any, ...]:
1✔
1703
        """
1704
        Visitor of literal tuple node
1705

1706
        :param tup_node: the python ast tuple node
1707
        :return: the value of the tuple
1708
        """
1709
        return tuple(self.get_type(value) for value in tup_node.elts)
1✔
1710

1711
    def visit_List(self, list_node: ast.List) -> List[Any]:
1✔
1712
        """
1713
        Visitor of literal list node
1714

1715
        :param list_node: the python ast list node
1716
        :return: the value of the list
1717
        """
1718
        return [self.get_type(value) for value in list_node.elts]
1✔
1719

1720
    def visit_Dict(self, dict_node: ast.Dict) -> Dict[Any, Any]:
1✔
1721
        """
1722
        Visitor of literal dict node
1723

1724
        :param dict_node: the python ast dict node
1725
        :return: a list with each key and value type
1726
        """
1727
        dictionary = {}
1✔
1728
        size = min(len(dict_node.keys), len(dict_node.values))
1✔
1729
        for index in range(size):
1✔
1730
            key = self.get_type(dict_node.keys[index])
1✔
1731
            value = self.get_type(dict_node.values[index])
1✔
1732
            if key in dictionary and dictionary[key] != value:
1✔
1733
                dictionary[key] = Type.get_generic_type(dictionary[key], value)
1✔
1734
            else:
1735
                dictionary[key] = value
1✔
1736
        return dictionary
1✔
1737

1738
    def visit_NameConstant(self, constant: ast.NameConstant) -> Any:
1✔
1739
        """
1740
        Visitor of constant names node
1741

1742
        :param constant: the python ast name constant node
1743
        :return: the value of the constant
1744
        """
1745
        return constant.value
×
1746

1747
    def visit_Name(self, name: ast.Name) -> ast.Name:
1✔
1748
        """
1749
        Visitor of a name node
1750

1751
        :param name:
1752
        :return: the object with the name node information
1753
        """
1754
        return name
1✔
1755

1756
    def visit_Starred(self, node: ast.Starred) -> ast.AST:
1✔
1757
        value_type = self.get_type(node.value)
1✔
1758
        if not Type.sequence.is_type_of(value_type):
1✔
1759
            self._log_error(
×
1760
                CompilerError.MismatchedTypes(line=node.lineno, col=node.col_offset,
1761
                                              expected_type_id=Type.sequence.identifier,
1762
                                              actual_type_id=value_type.identifier)
1763
            )
1764

1765
        return Type.tuple.build_collection(value_type.value_type)
1✔
1766

1767
    def visit_Index(self, index: ast.Index) -> Any:
1✔
1768
        """
1769
        Visitor of an index node
1770

1771
        :param index:
1772
        :return: the object with the index value information
1773
        """
1774
        index = self.visit(index.value)
×
1775
        if isinstance(index, (Iterable, Tuple)):
×
1776
            return tuple(index)
×
1777
        return index
×
1778

1779
    def visit_Slice(self, slice_node: ast.Slice) -> Tuple[Any, Any, Any]:
1✔
1780
        """
1781
        Visitor of an slice node
1782

1783
        :param slice_node:
1784
        :return: the object with the index value information
1785
        """
1786
        return slice_node.lower, slice_node.upper, slice_node.step
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc