• 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

93.11
/boa3/analyser/moduleanalyser.py
1
import ast
2
import logging
3
import os
4
from typing import Any, Dict, Iterable, List, Optional, Tuple, Union
5

6
from boa3 import constants
7
from boa3.analyser.astanalyser import IAstAnalyser
8
from boa3.analyser.importanalyser import ImportAnalyser
9
from boa3.analyser.model.ManifestSymbol import ManifestSymbol
10
from boa3.analyser.model.functionarguments import FunctionArguments
11
from boa3.analyser.model.optimizer import UndefinedType
12
from boa3.analyser.model.symbolscope import SymbolScope
13
from boa3.builtin.compile_time import NeoMetadata
14
from boa3.exception import CompilerError, CompilerWarning
15
from boa3.model.builtin.builtin import Builtin
16
from boa3.model.builtin.decorator import ContractDecorator
17
from boa3.model.builtin.decorator.builtindecorator import IBuiltinDecorator
18
from boa3.model.builtin.method.builtinmethod import IBuiltinMethod
19
from boa3.model.callable import Callable
20
from boa3.model.decorator import IDecorator
21
from boa3.model.event import Event
22
from boa3.model.expression import IExpression
23
from boa3.model.imports.importsymbol import BuiltinImport, Import
24
from boa3.model.imports.package import Package
25
from boa3.model.method import Method
26
from boa3.model.module import Module
27
from boa3.model.property import Property
28
from boa3.model.symbol import ISymbol
29
from boa3.model.type.annotation.metatype import MetaType
30
from boa3.model.type.annotation.uniontype import UnionType
31
from boa3.model.type.classes.classscope import ClassScope
32
from boa3.model.type.classes.classtype import ClassType
33
from boa3.model.type.classes.contractinterfaceclass import ContractInterfaceClass
34
from boa3.model.type.classes.pythonclass import PythonClass
35
from boa3.model.type.classes.userclass import UserClass
36
from boa3.model.type.collection.icollection import ICollectionType as Collection
37
from boa3.model.type.collection.sequence.sequencetype import SequenceType
38
from boa3.model.type.type import IType, Type
39
from boa3.model.variable import Variable
40

41

42
class ModuleAnalyser(IAstAnalyser, ast.NodeVisitor):
43
    """
44
    This class is responsible for mapping the locals of the functions and modules
45

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

49
    :ivar modules: a dictionary that maps each module with its name. Empty by default.
50
    :ivar symbols: a dictionary that maps the global symbols.
51
    """
52

53
    def __init__(self, analyser, symbol_table: Dict[str, ISymbol],
54
                 filename: str = None, root_folder: str = None,
55
                 analysed_files: Optional[dict] = None,
56
                 import_stack: Optional[List[str]] = None,
57
                 log: bool = False):
58
        super().__init__(analyser.ast_tree, filename, root_folder, log)
59
        self.modules: Dict[str, Module] = {}
60
        self.symbols: Dict[str, ISymbol] = symbol_table
61

62
        from boa3.analyser.analyser import Analyser
63
        if isinstance(analysed_files, dict):
64
            for file_path, file_analyser in analysed_files.copy().items():
65
                fixed_path = file_path.replace(os.sep, constants.PATH_SEPARATOR)
66
                if file_path != fixed_path:
67
                    analysed_files.pop(file_path)
68
                    analysed_files[fixed_path] = file_analyser
69
        else:
70
            analysed_files = {}
71

72
        analysed_files[filename.replace(os.sep, constants.PATH_SEPARATOR)] = analyser
73
        self._analysed_files: Dict[str, Analyser] = analysed_files
74

75
        if isinstance(import_stack, list):
76
            import_stack = [file_path.replace(os.sep, constants.PATH_SEPARATOR)
77
                            if isinstance(file_path, str) else file_path
78
                            for file_path in import_stack]
79
        else:
80
            import_stack = []
81
        self._import_stack: List[str] = import_stack
82

83
        self._builtin_functions_to_visit: Dict[str, IBuiltinMethod] = {}
84
        self._current_module: Module = None
85
        self._current_class: UserClass = None
86
        self._current_method: Method = None
87
        self._current_event: Event = None
88

89
        self._deploy_method: Optional[Method] = None
90

91
        self._annotated_variables: List[str] = []
92
        self._global_assigned_variables: List[str] = []
93
        self._scope_stack: List[SymbolScope] = []
94

95
        self._metadata: NeoMetadata = None
96
        self._metadata_node: ast.AST = ast.parse('')
97
        self._manifest_symbols: Dict[Tuple[ManifestSymbol, str, int], Callable] = {}
98
        self.imported_nodes: List[ast.AST] = []
99

100
        if self.filename:
101
            self._tree.filename = self.filename
102
        self.visit(self._tree)
103

104
        analyser.metadata = self._metadata if self._metadata is not None else NeoMetadata()
105

106
    @property
107
    def _current_scope(self) -> Union[Method, Module, UserClass, None]:
108
        """
109
        Returns the scope that is currently being analysed
110

111
        :return: the current scope. Return None if it is the global scope
112
        :rtype: Method or Module or None
113
        """
114
        if self._current_method is not None:
115
            return self._current_method
116
        if self._current_class is not None:
117
            return self._current_class
118
        return self._current_module
119

120
    @property
121
    def _current_symbol_scope(self) -> Optional[SymbolScope]:
122
        if len(self._scope_stack) > 0:
123
            return self._scope_stack[-1]
124
        else:
125
            return None
126

127
    @property
128
    def global_symbols(self) -> Dict[str, ISymbol]:
129
        """
130
        Returns the symbols in global scope
131

132
        :return: the symbol table of the global scope
133
        """
134
        global_symbols: Dict[str, ISymbol] = {}
135

136
        global_symbols.update(self.symbols)
137
        for mod in self.modules.values():
138
            global_symbols.update(mod.symbols)
139

140
        return global_symbols
141

142
    @property
143
    def analysed_files(self) -> Dict[str, Any]:
144
        return self._analysed_files.copy()
145

146
    def __include_variable(self, var_id: str, var_type_id: Union[str, IType],
147
                           source_node: ast.AST,
148
                           var_enumerate_type: IType = Type.none, assignment: bool = True):
149
        """
150
        Includes the variable in the symbol table if the id was not used
151

152
        :param var_id: variable id
153
        :param var_type_id: variable type id
154
        :type var_type_id: str or IType
155
        :param var_enumerate_type: variable value type id if var_type_id is a SequenceType
156
        """
157
        if not isinstance(var_id, str) and isinstance(var_id, Iterable):
158
            variables = [var_name.id if isinstance(var_name, ast.Name) else self.visit(var_name).id
159
                         for var_name in source_node.targets[0].elts]
160
            var_types = self.visit(source_node.value)
161
        else:
162
            variables = [var_id]
163
            var_types = [var_type_id]
164

165
        for x in range(min(len(variables), len(var_types))):
166
            var_id, var_type_id = variables[x], var_types[x]
167

168
            outer_symbol = self.get_symbol(var_id)
169
            if var_id in self._current_symbol_scope.symbols:
170
                if hasattr(outer_symbol, 'set_is_reassigned'):
171
                    # don't mark as reassigned if it is outside of a function
172
                    is_module_scope = isinstance(self._current_scope, Module)
173
                    if not is_module_scope:
174
                        outer_symbol.set_is_reassigned()
175
                    self.__set_source_origin(source_node, is_module_scope)
176
            else:
177
                if (not isinstance(source_node, ast.Global) and
178
                        (not hasattr(source_node, 'targets') or not isinstance(source_node.targets[x], ast.Subscript))):
179
                    if outer_symbol is not None:
180
                        self._log_warning(
181
                            CompilerWarning.NameShadowing(source_node.lineno, source_node.col_offset, outer_symbol, var_id)
182
                        )
183

184
                var_type = None
185
                if isinstance(var_type_id, SequenceType):
186
                    var_type = var_type_id
187
                    var_enumerate_type = self.get_enumerate_type(var_type)
188
                elif isinstance(var_type_id, IType):
189
                    var_type = var_type_id
190

191
                # when setting a None value to a variable, set the variable as any type
192
                if var_type is Type.none:
193
                    var_type = Type.any
194

195
                if isinstance(var_type, IType) or var_type is None:
196
                    # if type is None, the variable type depends on the type of a expression
197
                    if isinstance(source_node, ast.Global):
198
                        var = outer_symbol
199
                    else:
200
                        if isinstance(var_type, SequenceType):
201
                            var_type = var_type.build_collection(var_enumerate_type)
202
                        var = Variable(var_type, origin_node=source_node)
203

204
                    self._current_symbol_scope.include_symbol(var_id, var)
205
                    if isinstance(source_node, ast.AnnAssign):
206
                        self._annotated_variables.append(var_id)
207
            if hasattr(self._current_scope, 'assign_variable') and assignment:
208
                self._global_assigned_variables.append(var_id)
209

210
    def __include_callable(self, callable_id: str, callable: Callable):
211
        """
212
        Includes the method in the symbol table if the id was not used
213

214
        :param callable_id: method id
215
        :param callable: method to be included
216
        """
217
        if ((self._current_scope is self._current_class or callable_id not in self._current_scope.symbols)
218
                and hasattr(self._current_scope, 'include_callable')):
219
            already_exists = not self._current_scope.include_callable(callable_id, callable)
220
        else:
221
            symbol = self.get_symbol(callable_id)
222
            already_exists = symbol is not None and not isinstance(symbol, IBuiltinMethod)
223

224
        if already_exists:
225
            self._log_error(CompilerError.DuplicatedIdentifier(callable.origin.lineno,
226
                                                               callable.origin.col_offset,
227
                                                               callable_id))
228

229
        if callable.is_public:
230
            # check if the external name + argument number is unique
231
            manifest_name = callable.external_name if callable.external_name is not None else callable_id
232
            manifest_id = (ManifestSymbol.get_manifest_symbol(callable), manifest_name, len(callable.args))
233

234
            if manifest_id in self._manifest_symbols:
235
                self._log_error(CompilerError.DuplicatedManifestIdentifier(callable.origin.lineno,
236
                                                                           callable.origin.col_offset,
237
                                                                           manifest_name, len(callable.args)
238
                                                                           ))
239
            else:
240
                self._manifest_symbols[manifest_id] = callable
241

242
    def __include_class_variable(self, cl_var_id: str, cl_var: Variable):
243
        """
244
        Includes the class variable in the current class
245

246
        :param cl_var_id: variable name
247
        :param cl_var: variable to be included
248
        """
249
        if cl_var_id not in self._current_scope.class_variables:
250
            self._current_class.include_symbol(cl_var_id, cl_var, ClassScope.CLASS)
251

252
    def __set_source_origin(self, source_node: ast.AST, scope_is_correct: bool = True):
253
        if scope_is_correct or self._current_scope == self._deploy_method:
254
            source_node.origin = self._tree
255

256
    def get_symbol(self, symbol_id: str,
257
                   is_internal: bool = False,
258
                   check_raw_id: bool = False,
259
                   origin_node: ast.AST = None) -> Optional[ISymbol]:
260
        for scope in reversed(self._scope_stack):
261
            if symbol_id in scope.symbols:
262
                return scope.symbols[symbol_id]
263

264
            if check_raw_id:
265
                found_symbol = self._search_by_raw_id(symbol_id, list(scope.symbols.values()))
266
                if found_symbol is not None:
267
                    return found_symbol
268

269
        if symbol_id in self._current_scope.symbols:
270
            # the symbol exists in the local scope
271
            return self._current_scope.symbols[symbol_id]
272
        elif symbol_id in self._current_module.symbols:
273
            # the symbol exists in the module scope
274
            return self._current_module.symbols[symbol_id]
275

276
        if check_raw_id:
277
            found_symbol = self._search_by_raw_id(symbol_id, list(self._current_scope.symbols.values()))
278
            if found_symbol is not None:
279
                return found_symbol
280

281
            found_symbol = self._search_by_raw_id(symbol_id, list(self._current_module.symbols.values()))
282
            if found_symbol is not None:
283
                return found_symbol
284

285
        return super().get_symbol(symbol_id, is_internal, check_raw_id, origin_node)
286

287
    def get_annotation(self, value: Any, use_metatype: bool = False, accept_none: bool = False) -> Optional[IType]:
288
        if not isinstance(value, ast.AST):
289
            return None
290

291
        annotation_type = self.get_type(value, use_metatype)
292
        if not isinstance(annotation_type, PythonClass):
293
            return annotation_type
294
        if hasattr(value, 'value') and value.value is None and annotation_type is Type.none:
295
            return annotation_type
296

297
        if isinstance(value, (ast.Constant, ast.NameConstant, ast.List, ast.Tuple, ast.Dict, ast.Set)):
298
            # annotated types should only accept types
299
            return None
300
        return annotation_type
301

302
    def _check_annotation_type(self, node: ast.AST, origin_node: Optional[ast.AST] = None):
303
        if node is None:
304
            return
305

306
        if origin_node is None:
307
            origin_node = node
308

309
        if self.get_annotation(node) is None:
310
            actual_type = self.get_type(node)
311
            self._log_error(
312
                CompilerError.MismatchedTypes(
313
                    origin_node.lineno, origin_node.col_offset,
314
                    expected_type_id=type.__name__,
315
                    actual_type_id=actual_type.identifier
316
                ))
317

318
    # region Log
319

320
    def _log_import(self, import_from: str):
321
        if self._log:
322
            logging.info("Importing '{0}'\t <{1}>".format(import_from, self.filename))
323

324
    def _log_unresolved_import(self, origin_node: ast.AST, import_id: str):
325
        if self._log:
326
            self._log_error(
327
                CompilerError.UnresolvedReference(
328
                    line=origin_node.lineno,
329
                    col=origin_node.col_offset,
330
                    symbol_id=import_id
331
                )
332
            )
333

334
    # endregion
335

336
    # region Metadata
337

338
    def _read_metadata_object(self, function: ast.FunctionDef):
339
        """
340
        Gets the metadata object defined in this function
341

342
        :param function:
343
        """
344
        if self._metadata is not None:
345
            # metadata function has been defined already
346
            self._log_warning(
347
                CompilerWarning.RedeclaredSymbol(
348
                    line=function.lineno, col=function.col_offset,
349
                    symbol_id=Builtin.Metadata.identifier
350
                )
351
            )
352
        # this function must have a return and no arguments
353
        elif len(function.args.args) != 0:
354
            self._log_error(
355
                CompilerError.UnexpectedArgument(
356
                    line=function.lineno, col=function.col_offset
357
                )
358
            )
359
        elif not any(isinstance(stmt, ast.Return) for stmt in function.body):
360
            self._log_error(
361
                CompilerError.MissingReturnStatement(
362
                    line=function.lineno, col=function.col_offset,
363
                    symbol_id=function.name
364
                )
365
            )
366
        else:
367
            function.returns = None
368
            function.decorator_list = []
369
            module: ast.Module = ast.parse('')
370
            module.body = [node for node in self._tree.body
371
                           if isinstance(node, (ast.ImportFrom, ast.Import))]
372
            module.body.append(function)
373
            ast.copy_location(module, function)
374

375
            try:
376
                # executes the function
377
                code = compile(module, filename='<boa3>', mode='exec')
378
                namespace = {}
379
                exec(code, namespace)
380
                obj: Any = namespace[function.name]()
381
            except ModuleNotFoundError:
382
                # will fail if any imports can't be executed
383
                # in this case, the error is already logged
384
                return
385

386
            node: ast.AST = function.body[-1] if len(function.body) > 0 else function
387
            # return must be a NeoMetadata object
388
            if not isinstance(obj, NeoMetadata):
389
                obj_type = self.get_type(obj).identifier if self.get_type(obj) is not Type.any else type(obj).__name__
390
                self._log_error(
391
                    CompilerError.MismatchedTypes(
392
                        line=node.lineno, col=node.col_offset,
393
                        expected_type_id=NeoMetadata.__name__,
394
                        actual_type_id=obj_type
395
                    )
396
                )
397
                return
398

399
            # validate if the extras field can be converted to json
400
            try:
401
                import json
402
                json.dumps(obj.extras)
403
            except BaseException as e:
404
                self._log_error(
405
                    CompilerError.InvalidType(
406
                        line=node.lineno, col=node.col_offset,
407
                        symbol_id=str(e)
408
                    )
409
                )
410
                return
411

412
            # validates the metadata attributes types
413
            attributes: Dict[str, Any] = {attr: value
414
                                          for attr, value in dict(obj.__dict__).items()
415
                                          if attr in Builtin.metadata_fields}
416
            if any(not isinstance(value, Builtin.metadata_fields[attr]) for attr, value in attributes.items()):
417
                for expected, actual in [(Builtin.metadata_fields[attr], type(v_type))
418
                                         for attr, v_type in attributes.items()
419
                                         if not isinstance(v_type, Builtin.metadata_fields[attr])]:
420
                    if isinstance(expected, Iterable):
421
                        expected_id = 'Union[{0}]'.format(', '.join([tpe.__name__ for tpe in expected]))
422
                    elif hasattr(expected, '__name__'):
423
                        expected_id = expected.__name__
424
                    else:
425
                        expected_id = str(expected)
426

427
                    self._log_error(
428
                        CompilerError.MismatchedTypes(
429
                            line=node.lineno, col=node.col_offset,
430
                            expected_type_id=expected_id,
431
                            actual_type_id=actual.__name__
432
                        )
433
                    )
434
            else:
435
                # if the function was defined correctly, sets the metadata object of the smart contract
436
                self._metadata = obj
437
                self._metadata_node = function  # for error messages only
438

439
    # endregion
440

441
    # region AST
442

443
    def visit_ImportFrom(self, import_from: ast.ImportFrom):
444
        """
445
        Includes methods and variables from other modules into the current scope
446

447
        :param import_from:
448
        """
449
        self._log_import(import_from.module)
450
        analyser = self._analyse_module_to_import(import_from, import_from.module)
451
        if analyser is not None:
452
            import_alias: Dict[str] = \
453
                {alias.name: alias.asname if alias.asname is not None else alias.name for alias in import_from.names}
454

455
            new_symbols: Dict[str, ISymbol] = analyser.export_symbols(list(import_alias.keys()))
456

457
            # check if the wildcard is used and filter the symbols
458
            if constants.IMPORT_WILDCARD in import_alias:
459
                import_alias.pop(constants.IMPORT_WILDCARD)
460
                for imported_symbol_id in new_symbols:
461
                    # add the symbols imported with the wildcard without specific aliases in the dict
462
                    if imported_symbol_id not in import_alias:
463
                        import_alias[imported_symbol_id] = imported_symbol_id
464

465
            # includes the module to be able to generate the functions
466
            imported_module = self._build_import(analyser.path, analyser.tree, analyser, import_alias)
467
            self._current_scope.include_symbol(import_from.module, imported_module)
468

469
            for name, alias in import_alias.items():
470
                if name in new_symbols:
471
                    self._current_scope.include_symbol(alias, imported_module.symbols[name])
472
                else:
473
                    # if there's a symbol that couldn't be loaded, log a compiler error
474
                    self._log_unresolved_import(import_from, name)
475

476
    def visit_Import(self, import_node: ast.Import):
477
        """
478
        Includes methods and variables from other modules into the current scope
479

480
        :param import_node:
481
        """
482
        import_alias: Dict[str] = \
483
            {alias.name: alias.asname if alias.asname is not None else alias.name for alias in import_node.names}
484

485
        for target, alias in import_alias.items():
486
            self._log_import(target)
487
            analyser = self._analyse_module_to_import(import_node, target)
488
            if analyser is not None:
489
                new_symbols: Dict[str, ISymbol] = analyser.export_symbols()
490
                for symbol in [symbol for symbol in analyser.symbols if symbol not in new_symbols]:
491
                    # if there's a symbol that couldn't be loaded, log a compiler error
492
                    self._log_unresolved_import(import_node, '{0}.{1}'.format(target, symbol))
493

494
                imported_module = self._build_import(analyser.path, analyser.tree, analyser)
495
                self._current_scope.include_symbol(alias, imported_module)
496

497
    def _build_import(self, origin: str, syntax_tree: ast.AST,
498
                      import_analyser: ImportAnalyser,
499
                      imported_symbols: Dict[str, ISymbol] = None) -> Import:
500

501
        if import_analyser.is_builtin_import:
502
            return BuiltinImport(origin, syntax_tree, import_analyser, imported_symbols)
503

504
        return Import(origin, syntax_tree, import_analyser, imported_symbols)
505

506
    def _analyse_module_to_import(self, origin_node: ast.AST, target: str) -> Optional[ImportAnalyser]:
507
        already_imported = {imported.origin: imported.analyser
508
                            for imported in self._current_module.symbols.values()
509
                            if isinstance(imported, Import) and imported.analyser is not None
510
                            }
511
        already_imported.update(self._analysed_files)
512

513
        analyser = ImportAnalyser(import_target=target,
514
                                  root_folder=self.root_folder,
515
                                  importer_file=self.filename,
516
                                  already_imported_modules=already_imported,
517
                                  import_stack=self._import_stack.copy(),
518
                                  log=self._log)
519

520
        if analyser.recursive_import:
521
            self._log_error(
522
                CompilerError.CircularImport(line=origin_node.lineno,
523
                                             col=origin_node.col_offset,
524
                                             target_import=target,
525
                                             target_origin=self.filename)
526
            )
527

528
        elif not analyser.can_be_imported:
529
            circular_import_error = next((error for error in analyser.errors
530
                                          if isinstance(error, CompilerError.CircularImport)),
531
                                         None)
532

533
            if circular_import_error is not None:
534
                # if the problem was a circular import, the error was already logged
535
                self.errors.append(circular_import_error)
536
            elif hasattr(analyser, 'is_namespace_package') and analyser.is_namespace_package:
537
                return analyser
538
            else:
539
                self._log_unresolved_import(origin_node, target)
540

541
        else:
542
            analyser.update_external_analysed_files(self._analysed_files)
543
            return analyser
544

545
    def visit_Module(self, module: ast.Module):
546
        """
547
        Visitor of the module node
548

549
        Fills module symbol table
550

551
        :param module:
552
        """
553
        mod: Module = Module()
554
        self._current_module = mod
555
        self._scope_stack.append(SymbolScope())
556

557
        global_stmts = []
558
        function_stmts = []
559
        for stmt in module.body:
560
            if isinstance(stmt, (ast.FunctionDef, ast.AsyncFunctionDef)):
561
                function_stmts.append(stmt)
562
            elif not (isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Constant)):
563
                # don't evaluate constant expression - for example: string for documentation
564
                if self.visit(stmt) is not Builtin.Event:
565
                    global_stmts.append(stmt)
566

567
        module.body = global_stmts + function_stmts
568
        for var_id, var in mod.variables.items():
569
            # all static fields must be initialized
570
            if not mod.is_variable_assigned(var_id):
571
                self._log_error(
572
                    CompilerError.UnresolvedReference(
573
                        line=var.origin.lineno if var.origin is not None else 0,
574
                        col=var.origin.col_offset if var.origin is not None else 0,
575
                        symbol_id=var_id
576
                    )
577
                )
578

579
        for stmt in function_stmts:
580
            result = self.visit(stmt)
581
            # don't evaluate the metadata function in the following analysers
582
            if result is Builtin.Metadata:
583
                module.body.remove(stmt)
584

585
        # TODO: include the body of the builtin methods to the ast
586
        self.modules['main'] = mod
587
        module_scope = self._scope_stack.pop()
588
        for symbol_id, symbol in module_scope.symbols.items():
589
            if symbol_id in self._global_assigned_variables:
590
                mod.include_symbol(symbol_id, symbol)
591
                mod.assign_variable(symbol_id)
592

593
        self._global_assigned_variables.clear()
594
        self._current_module = None
595

596
    def visit_ClassDef(self, class_node: ast.ClassDef):
597
        """
598
        Visitor of the class node
599

600
        Includes the class in the scope of its module
601
        """
602
        bases = []
603
        for base in class_node.bases:
604
            base_type_id: Any = ast.NodeVisitor.visit(self, base)
605
            if isinstance(base_type_id, ast.Name):
606
                base_type_id = base_type_id.id
607

608
            base_symbol = self.get_symbol(base_type_id)
609
            # TODO: change when class inheritance with builtin types is implemented
610
            if not isinstance(base_symbol, UserClass):
611
                self._log_error(
612
                    CompilerError.NotSupportedOperation(
613
                        class_node.lineno, class_node.col_offset,
614
                        symbol_id='class inheritance with builtins'
615
                    )
616
                )
617
            bases.append(base_symbol)
618

619
        # TODO: change when class inheritance with multiple bases is implemented
620
        if len(bases) > 1:
621
            self._log_error(
622
                CompilerError.NotSupportedOperation(
623
                    class_node.lineno, class_node.col_offset,
624
                    symbol_id='class inheritance with multiple bases'
625
                )
626
            )
627

628
        # TODO: change when base classes with keyword is implemented
629
        if len(class_node.keywords) > 0:
630
            self._log_error(
631
                CompilerError.NotSupportedOperation(
632
                    class_node.lineno, class_node.col_offset,
633
                    symbol_id='class keyword'
634
                )
635
            )
636

637
        # TODO: change when class decorators are implemented
638
        class_decorators: List[Method] = self._get_decorators(class_node)
639
        if not all(isinstance(decorator, IBuiltinDecorator) and decorator.is_class_decorator
640
                   for decorator in class_decorators):
641
            # only builtin decorator are currently accepted
642
            self._log_error(
643
                CompilerError.NotSupportedOperation(
644
                    class_node.lineno, class_node.col_offset,
645
                    symbol_id='class decorator'
646
                )
647
            )
648

649
        contract_interface_decorator = next((decorator for decorator in class_decorators
650
                                             if isinstance(decorator, ContractDecorator)),
651
                                            None)
652

653
        if contract_interface_decorator is not None:
654
            from boa3.model.type.classes.contractinterfaceclass import ContractInterfaceClass
655
            user_class = ContractInterfaceClass(contract_hash=contract_interface_decorator.contract_hash,
656
                                                identifier=class_node.name,
657
                                                decorators=class_decorators,
658
                                                bases=bases)
659
        else:
660
            user_class = UserClass(identifier=class_node.name,
661
                                   decorators=class_decorators,
662
                                   bases=bases)
663

664
        self._current_class = user_class
665
        if self._current_symbol_scope is not None:
666
            self._current_symbol_scope.include_symbol(class_node.name, user_class)
667
        self._scope_stack.append(SymbolScope())
668

669
        for stmt in class_node.body:
670
            self.visit(stmt)
671

672
        class_scope = self._scope_stack.pop()
673
        self._current_module.include_class(class_node.name, user_class)
674
        self._current_class = None
675

676
    def visit_FunctionDef(self, function: ast.FunctionDef):
677
        """
678
        Visitor of the function node
679

680
        Includes the method in the scope of its module
681

682
        :param function:
683
        """
684
        fun_decorators: List[Method] = self._get_decorators(function)
685
        if Builtin.Metadata in fun_decorators:
686
            self._read_metadata_object(function)
687
            return Builtin.Metadata
688

689
        if any(decorator is None for decorator in fun_decorators):
690
            self._log_error(
691
                CompilerError.NotSupportedOperation(
692
                    function.lineno, function.col_offset,
693
                    symbol_id='decorator'
694
                )
695
            )
696

697
        valid_decorators: List[IDecorator] = []
698
        for decorator in fun_decorators:
699
            if isinstance(decorator, IDecorator):
700
                decorator.update_args(function.args, self._current_scope)
701
                valid_decorators.append(decorator)
702

703
        is_static_method = (isinstance(self._current_scope, ClassType)
704
                            and Builtin.StaticMethodDecorator in valid_decorators)
705
        is_instance_method = (isinstance(self._current_scope, ClassType)
706
                              and Builtin.ClassMethodDecorator not in valid_decorators
707
                              and not is_static_method)
708
        is_class_constructor = is_instance_method and function.name == constants.INIT_METHOD_ID
709

710
        external_function_name = None
711
        if isinstance(self._current_class, ContractInterfaceClass):
712
            if not is_static_method:
713
                self._log_error(CompilerError
714
                                .InvalidUsage(function.lineno, function.col_offset,
715
                                              "Only static methods are accepted when defining contract interfaces"
716
                                              ))
717
            else:
718
                display_name_decorator = next((decorator for decorator in valid_decorators
719
                                               if isinstance(decorator, type(Builtin.ContractMethodDisplayName))),
720
                                              None)
721
                if display_name_decorator is not None:
722
                    external_function_name = display_name_decorator.external_name
723

724
        if is_instance_method:
725
            if Builtin.InstanceMethodDecorator not in valid_decorators:
726
                valid_decorators.append(Builtin.InstanceMethodDecorator)
727

728
            if len(function.args.args) > 0 and function.args.args[0].annotation is None:
729
                # set annotation to the self method
730
                from boa3.model import set_internal_call
731
                self_argument = function.args.args[0]
732
                self_annotation = self._current_class.identifier
733

734
                self_ast_annotation = ast.parse(self_annotation).body[0].value
735
                set_internal_call(self_ast_annotation)
736

737
                ast.copy_location(self_ast_annotation, self_argument)
738
                self_argument.annotation = self_ast_annotation
739

740
        if is_class_constructor:
741
            # __init__ method behave like class methods
742
            if Builtin.ClassMethodDecorator not in valid_decorators:
743
                valid_decorators.append(Builtin.ClassMethodDecorator)
744

745
        fun_args: FunctionArguments = self.visit(function.args)
746
        if function.returns is not None:
747
            fun_rtype_symbol = self.visit(function.returns)
748
            self._check_annotation_type(function.returns)
749
        else:
750
            fun_rtype_symbol = Type.none
751

752
        # TODO: remove when dictionary unpacking operator is implemented
753
        if function.args.kwarg is not None:
754
            self._log_error(
755
                CompilerError.NotSupportedOperation(
756
                    function.lineno, function.col_offset,
757
                    symbol_id='** variables'
758
                )
759
            )
760

761
        # TODO: remove when keyword-only arguments are implemented
762
        if len(function.args.kwonlyargs) > 0:
763
            self._log_error(
764
                CompilerError.NotSupportedOperation(
765
                    function.lineno, function.col_offset,
766
                    symbol_id='keyword-only arguments'
767
                )
768
            )
769

770
        if isinstance(fun_rtype_symbol, str):
771
            symbol = self.get_symbol(fun_rtype_symbol, origin_node=function.returns)
772
            fun_rtype_symbol = self.get_type(symbol)
773

774
        fun_return: IType = self.get_type(fun_rtype_symbol)
775

776
        method = Method(args=fun_args.args, defaults=function.args.defaults, return_type=fun_return,
777
                        vararg=fun_args.vararg,
778
                        origin_node=function,
779
                        is_public=any(isinstance(decorator, type(Builtin.Public)) for decorator in fun_decorators),
780
                        decorators=valid_decorators,
781
                        external_name=external_function_name,
782
                        is_init=is_class_constructor)
783

784
        if function.name in Builtin.internal_methods:
785
            internal_method = Builtin.internal_methods[function.name]
786
            if not internal_method.is_valid_deploy_method(method):
787
                self._log_error(
788
                    CompilerError.InternalIncorrectSignature(line=function.lineno,
789
                                                             col=function.col_offset,
790
                                                             expected_method=internal_method)
791
                )
792
        if function.name == constants.DEPLOY_METHOD_ID:
793
            self._deploy_method = method
794

795
        self._current_method = method
796
        self._scope_stack.append(SymbolScope())
797

798
        # don't evaluate constant expression - for example: string for documentation
799
        from boa3.constants import SYS_VERSION_INFO
800
        if SYS_VERSION_INFO >= (3, 8):
801
            function.body = [stmt for stmt in function.body
802
                             if not (isinstance(stmt, ast.Expr) and isinstance(stmt.value, ast.Constant))]
803
        else:
804
            function.body = [stmt for stmt in function.body
805
                             if not (isinstance(stmt, ast.Expr) and
806
                                     (hasattr(stmt.value, 'n') or hasattr(stmt.value, 's'))
807
                                     )]
808

809
        if isinstance(self._current_class, ClassType):
810
            method.origin_class = self._current_class
811
            if self._current_class.is_interface and len(function.body) > 0:
812
                first_instruction = function.body[0]
813
                if not isinstance(first_instruction, ast.Pass):
814
                    self._log_warning(CompilerWarning.UnreachableCode(first_instruction.lineno,
815
                                                                      first_instruction.col_offset))
816

817
        for stmt in function.body:
818
            self.visit(stmt)
819

820
        method_scope = self._scope_stack.pop()
821
        global_scope_symbols = self._scope_stack[0].symbols if len(self._scope_stack) > 0 else {}
822

823
        self._set_instance_variables(method_scope)
824
        self._set_properties(function)
825

826
        self._current_method = None
827
        self.__include_callable(function.name, method)
828

829
        for var_id, var in method_scope.symbols.items():
830
            if isinstance(var, Variable) and var_id not in self._annotated_variables:
831
                method.include_variable(var_id, Variable(UndefinedType, var.origin))
832
            else:
833
                method.include_symbol(var_id, var)
834

835
        self._annotated_variables.clear()
836

837
    def _get_decorators(self, node: ast.AST) -> List[Method]:
838
        """
839
        Gets a list of the symbols used to decorate the given node
840

841
        :param node: python ast node
842
        :return: a list with all decorators in the node. Empty if no decorator is found.
843
        """
844
        decorators = []
845
        if hasattr(node, 'decorator_list'):
846
            for decorator in node.decorator_list:
847
                decorator_visit = self.visit(decorator)
848
                if decorator_visit is None and hasattr(decorator, 'func'):
849
                    decorator_visit = self.visit(decorator.func)
850

851
                symbol = self.get_symbol(decorator_visit, origin_node=decorator)
852
                if hasattr(symbol, 'build'):
853
                    symbol = symbol.build(decorator, self)
854
                decorators.append(symbol)
855

856
        return decorators
857

858
    def _set_instance_variables(self, scope: SymbolScope):
859
        if (isinstance(self._current_class, UserClass)
860
                and isinstance(self._current_method, Method)
861
                and self._current_method.is_init
862
                and len(self._current_method.args) > 0):
863

864
            self_id = list(self._current_method.args)[0]
865
            for var_id, var in scope.symbols.items():
866
                if var_id.startswith(self_id):
867
                    split_name = var_id.split(constants.ATTRIBUTE_NAME_SEPARATOR)
868
                    if len(split_name) > 0:
869
                        instance_var_id = split_name[1]
870
                        self._current_class.include_symbol(instance_var_id, var)
871
                        scope.remove_symbol(var_id)
872

873
    def _set_properties(self, function: ast.FunctionDef):
874
        from boa3.model.builtin.decorator import PropertyDecorator
875
        if (isinstance(self._current_class, UserClass)
876
                and isinstance(self._current_method, Method)
877
                and any(isinstance(decorator, PropertyDecorator) for decorator in self._current_method.decorators)):
878
            if len(self._current_method.args) < 1 or not any('self' == arg for arg in self._current_method.args):
879
                self._log_error(
880
                    CompilerError.SelfArgumentError(function.lineno, function.col_offset)
881
                )
882
            self._current_class.include_symbol(self._current_method.origin.name, Property(self._current_method))
883

884
    def visit_arguments(self, arguments: ast.arguments) -> FunctionArguments:
885
        """
886
        Visitor of the function arguments node
887

888
        :param arguments:
889
        :return: a dictionary that maps each argument to its identifier
890
        """
891
        fun_args = FunctionArguments()
892

893
        for arg in arguments.args:
894
            var_id, var = self.visit_arg(arg)  # Tuple[str, Variable]
895
            fun_args.add_arg(var_id, var)
896

897
        if arguments.vararg is not None:
898
            var_id, var = self.visit_arg(arguments.vararg)  # Tuple[str, Variable]
899
            fun_args.set_vararg(var_id, var)
900

901
        if arguments.kwarg is not None:
902
            var_id, var = self.visit_arg(arguments.kwarg)  # Tuple[str, Variable]
903
            fun_args.add_kwarg(var_id, var)
904

905
        return fun_args
906

907
    def visit_arg(self, arg: ast.arg) -> Tuple[str, Variable]:
908
        """
909
        Visitor of a function argument node
910

911
        :param arg:
912
        :return: a tuple with the identifier and the argument
913
        """
914
        var_id = arg.arg
915
        var_type: IType = self.get_type(arg.annotation)
916

917
        if var_type is Type.none and isinstance(arg.annotation, ast.Name):
918
            var_symbol: ISymbol = self.get_symbol(arg.annotation.id, origin_node=arg)
919
            var_type = self.get_type(var_symbol)
920

921
        self._check_annotation_type(arg.annotation, origin_node=arg)
922

923
        return var_id, Variable(var_type)
924

925
    def visit_Return(self, ret: ast.Return):
926
        """
927
        Visitor of the function return node
928

929
        If the return is a name, verifies if the symbol is defined
930

931
        :param ret: the python ast return node
932
        """
933
        if isinstance(ret.value, ast.Name):
934
            symbol_id = self.visit(ret.value)
935
            symbol = self.get_symbol(symbol_id)
936
            if symbol is None:
937
                # the symbol doesn't exists
938
                self._log_error(
939
                    CompilerError.UnresolvedReference(ret.value.lineno, ret.value.col_offset, symbol_id)
940
                )
941

942
        if ret.value is not None:
943
            self.__set_source_origin(ret.value)
944

945
    def visit_type(self, target: ast.AST) -> Optional[IType]:
946
        """
947
        Gets the type by its identifier
948

949
        :param target: ast node to be evaluated
950
        :return: the type of the value inside the node. None by default
951
        """
952
        target_type = self.visit(target)  # Type:str or IType
953
        if isinstance(target_type, str) and not isinstance(target, ast.Str):
954
            symbol = self.get_symbol(target_type)
955
            if symbol is None:
956
                # the symbol doesn't exists
957
                self._log_error(
958
                    CompilerError.UnresolvedReference(target.lineno, target.col_offset, target_type)
959
                )
960
            target_type = symbol
961

962
        if target_type is None and not isinstance(target, ast.NameConstant):
963
            # the value type is invalid
964
            return None
965

966
        if not isinstance(target_type, (ast.AST, IType, IExpression)) and isinstance(target_type, ISymbol):
967
            self._log_error(
968
                CompilerError.UnresolvedReference(
969
                    line=target.lineno, col=target.col_offset,
970
                    symbol_id=str(target_type)
971
                )
972
            )
973

974
        if isinstance(target_type, ClassType):
975
            init = target_type.constructor_method()
976
            if hasattr(init, 'build'):
977
                args = []
978
                if hasattr(target, 'args'):
979
                    for arg in target.args:
980
                        result = self.visit(arg)
981
                        if (isinstance(result, str) and not isinstance(arg, (ast.Str, ast.Constant))
982
                                and result in self._current_scope.symbols):
983
                            result = self.get_type(self._current_scope.symbols[result])
984
                        args.append(result)
985

986
                init = init.build(args)
987
            target_type = init.return_type if init is not None else target_type
988

989
        return self.get_type(target_type)
990

991
    def get_enumerate_type(self, var_type: IType) -> IType:
992
        return var_type.value_type if isinstance(var_type, SequenceType) else Type.none
993

994
    def visit_Assign(self, assign: ast.Assign):
995
        """
996
        Visitor of the variable assignment node
997

998
        Includes the variable in its scope if it's the first use
999

1000
        :param assign:
1001
        """
1002
        # multiple assignments
1003
        if isinstance(assign.targets[0], ast.Tuple):
1004
            self._log_error(
1005
                CompilerError.NotSupportedOperation(assign.lineno, assign.col_offset, 'Multiple variable assignments')
1006
            )
1007

1008
        else:
1009
            var_type = self.visit_type(assign.value)
1010

1011
            if var_type is Type.none and isinstance(assign.value, ast.Name):
1012
                symbol = self.get_symbol(assign.value.id)
1013
                if isinstance(symbol, Event):
1014
                    var_type = Builtin.Event
1015
                    self._current_event = symbol
1016

1017
            return_type = var_type
1018
            for target in assign.targets:
1019
                var_id = self.visit(target)
1020
                if not isinstance(var_id, ISymbol):
1021
                    return_type = self.assign_value(var_id, var_type, source_node=assign)
1022

1023
            return return_type
1024

1025
    def visit_AnnAssign(self, ann_assign: ast.AnnAssign):
1026
        """
1027
        Visitor of the annotated variable assignment node
1028

1029
        Includes the variable in its scope if it's the first use
1030

1031
        :param ann_assign:
1032
        """
1033
        var_id: str = self.visit(ann_assign.target)
1034
        var_type: IType = self.visit_type(ann_assign.annotation)
1035
        if var_type is Builtin.Event:
1036
            self.visit(ann_assign.value)
1037

1038
        self._check_annotation_type(ann_assign.annotation, ann_assign)
1039

1040
        # TODO: check if the annotated type and the value type are the same
1041
        return self.assign_value(var_id, var_type, source_node=ann_assign, assignment=ann_assign.value is not None)
1042

1043
    def assign_value(self, var_id: str, var_type: IType, source_node: ast.AST, assignment: bool = True) -> IType:
1044
        if var_type is Builtin.Event and self._current_event is not None:
1045
            if '' in self._current_module.symbols and self._current_module.symbols[''] is self._current_event:
1046
                self._current_scope.callables[var_id] = self._current_scope.callables.pop('')
1047
                self._current_event.name = var_id
1048
            else:
1049
                self._current_scope.callables[var_id] = self._current_event
1050
            self._current_event = None
1051
        else:
1052
            if isinstance(self._current_scope, UserClass):
1053
                var = Variable(var_type, source_node)
1054
                self.__include_class_variable(var_id, var)
1055
            else:
1056
                self.__include_variable(var_id, var_type, source_node=source_node, assignment=assignment)
1057
        return var_type
1058

1059
    def visit_Global(self, global_node: ast.Global):
1060
        """
1061
        Visitor of the global identifier node
1062

1063
        :param global_node:
1064
        """
1065
        for var_id in global_node.names:
1066
            symbol = self.get_symbol(var_id)
1067
            if isinstance(symbol, Variable):
1068
                if self._current_method is not None:
1069
                    self._current_method.include_variable(var_id, symbol, is_global=True)
1070
                self.__include_variable(var_id, symbol.type, source_node=global_node)
1071

1072
    def visit_Expr(self, expr: ast.Expr):
1073
        """
1074
        Visitor of the atom expression node
1075

1076
        :param expr:
1077
        """
1078
        value = self.visit(expr.value)
1079
        if isinstance(expr.value, ast.Name):
1080
            if (
1081
                    # it is not a symbol of the method scope
1082
                    (self._current_method is not None and value not in self._current_method.symbols)
1083
                    # nor it is a symbol of the module scope
1084
                    or (self._current_module is not None and value not in self._current_module.symbols)
1085
                    # nor it is a symbol of the global scope
1086
                    or value not in self.symbols
1087
            ):
1088
                self._log_error(
1089
                    CompilerError.UnresolvedReference(expr.value.lineno, expr.value.col_offset, value)
1090
                )
1091

1092
    def visit_Subscript(self, subscript: ast.Subscript) -> Union[str, IType]:
1093
        """
1094
        Verifies if it is the types in the subscription are valid
1095

1096
        :param subscript: the python ast subscription node
1097
        :return: if the subscript is not a symbol, returns its type. Otherwise returns the symbol id.
1098
        :rtype: IType or str
1099
        """
1100
        is_internal = hasattr(subscript, 'is_internal_call') and subscript.is_internal_call
1101
        value = self.visit(subscript.value)
1102
        symbol = self.get_symbol(value, is_internal=is_internal, origin_node=subscript.value) if isinstance(value, str) else value
1103

1104
        if isinstance(subscript.ctx, ast.Load):
1105
            if (isinstance(symbol, (Collection, MetaType))
1106
                    and isinstance(subscript.value, (ast.Name, ast.NameConstant, ast.Attribute))):
1107
                # for evaluating names like List[str], Dict[int, bool], etc
1108
                value = subscript.slice.value if isinstance(subscript.slice, ast.Index) else subscript.slice
1109
                values_type: Iterable[IType] = self.get_values_type(value)
1110
                if isinstance(symbol, Collection):
1111
                    return symbol.build_collection(*values_type)
1112
                else:
1113
                    return symbol.build(*values_type)
1114

1115
            symbol_type = self.get_type(symbol)
1116
            if isinstance(subscript.slice, ast.Slice):
1117
                return symbol_type
1118

1119
            if isinstance(symbol, UnionType) or isinstance(symbol_type, UnionType):
1120
                if not isinstance(symbol_type, UnionType):
1121
                    symbol_type = symbol
1122
                index = subscript.slice.value if isinstance(subscript.slice, ast.Index) else subscript.slice
1123
                if isinstance(index, ast.Tuple):
1124
                    union_types = [self.get_type(value) for value in index.elts]
1125
                else:
1126
                    union_types = self.get_type(index)
1127
                return symbol_type.build(union_types)
1128

1129
            if isinstance(symbol_type, Collection):
1130
                return symbol_type.item_type
1131

1132
        return value
1133

1134
    def get_values_type(self, value: ast.AST) -> Iterable[Optional[IType]]:
1135
        """
1136
        Verifies if it is a multiple assignments statement
1137

1138
        :param value: the python ast subscription node
1139
        """
1140
        value_type: Optional[IType] = None
1141

1142
        if isinstance(value, (ast.Subscript, ast.Attribute, ast.Tuple)):
1143
            # index is another subscription
1144
            value_type = self.visit(value)
1145
        elif isinstance(value, ast.Name):
1146
            # index is an identifier
1147
            value_type = self.get_symbol(value.id)
1148

1149
        types: Iterable[Optional[IType]] = value_type if isinstance(value_type, Iterable) else [value_type]
1150
        for tpe in types:
1151
            if not isinstance(tpe, IType):
1152
                # type hint not using identifiers or using identifiers that are not types
1153
                index = self.visit(value)
1154
                if isinstance(index, str):
1155
                    self._log_error(
1156
                        CompilerError.UnresolvedReference(value.lineno, value.col_offset, index)
1157
                    )
1158

1159
        return types
1160

1161
    def visit_Call(self, call: ast.Call) -> Optional[IType]:
1162
        """
1163
        Visitor of a function call node
1164

1165
        :param call: the python ast function call node
1166
        :return: the result type of the called function. None if the function is not found
1167
        """
1168
        func_id = self.visit(call.func)
1169
        func_symbol = self.get_symbol(func_id)
1170

1171
        # if func_symbol is None, the called function may be a function written after in the code
1172
        # that's why it shouldn't log a compiler error here
1173
        if func_symbol is None:
1174
            return None
1175

1176
        if not isinstance(func_symbol, Callable):
1177
            # verify if it is a builtin method with its name shadowed
1178
            func = Builtin.get_symbol(func_id)
1179
            func_symbol = func if func is not None else func_symbol
1180

1181
            if func_symbol is Type.exception:
1182
                func_symbol = Builtin.Exception
1183
            elif isinstance(func_symbol, ClassType):
1184
                func_symbol = func_symbol.constructor_method()
1185

1186
        if not isinstance(func_symbol, Callable):
1187
            # the symbol doesn't exists
1188
            self._log_error(
1189
                CompilerError.UnresolvedReference(call.func.lineno, call.func.col_offset, func_id)
1190
            )
1191
        elif isinstance(func_symbol, IBuiltinMethod):
1192
            if func_symbol.body is not None:
1193
                self._builtin_functions_to_visit[func_id] = func_symbol
1194
            elif func_symbol is Builtin.NewEvent:
1195
                new_event = self.create_new_event(call)
1196
                self.__include_callable(new_event.identifier, new_event)
1197
                self._current_event = new_event
1198
            else:
1199
                args_types = [self.get_type(arg, use_metatype=True) for arg in call.args]
1200
                updated_symbol = func_symbol.build(args_types)
1201

1202
                if updated_symbol.identifier != func_id:
1203
                    self.__include_callable(updated_symbol.identifier, updated_symbol)
1204
                    return self.get_type(updated_symbol)
1205

1206
        return self.get_type(call.func)
1207

1208
    def create_new_event(self, create_call: ast.Call) -> Event:
1209
        event_args = create_call.args
1210
        args = {}
1211
        name = Builtin.Event.identifier
1212

1213
        if len(event_args) < 0:
1214
            self._log_error(
1215
                CompilerError.UnfilledArgument(line=create_call.lineno,
1216
                                               col=create_call.col_offset,
1217
                                               param=list(Builtin.NewEvent.args)[0])
1218
            )
1219
        elif len(event_args) > 0:
1220
            args_type = self.get_type(event_args[0])
1221
            if not Type.list.is_type_of(args_type):
1222
                self._log_error(
1223
                    CompilerError.MismatchedTypes(line=event_args[0].lineno,
1224
                                                  col=event_args[0].col_offset,
1225
                                                  expected_type_id=Type.list.identifier,
1226
                                                  actual_type_id=args_type.identifier)
1227
                )
1228
            else:
1229
                for value in event_args[0].elts:
1230
                    if not isinstance(value, ast.Tuple):
1231
                        CompilerError.MismatchedTypes(line=value.lineno,
1232
                                                      col=value.col_offset,
1233
                                                      expected_type_id=Type.tuple.identifier,
1234
                                                      actual_type_id=self.get_type(value).identifier)
1235
                    elif len(value.elts) < 2:
1236
                        self._log_error(
1237
                            CompilerError.UnfilledArgument(line=value.lineno,
1238
                                                           col=value.col_offset,
1239
                                                           param=list(Builtin.NewEvent.args)[0])
1240
                        )
1241
                    elif not (isinstance(value.elts[0], ast.Str) and
1242
                              ((isinstance(value.elts[1], ast.Name)  # if is name, get the type of its id
1243
                                and isinstance(self.get_symbol(value.elts[1].id), IType))
1244
                               or isinstance(self.visit(value.elts[1]), IType)  # otherwise, if the result is a type
1245
                               )):
1246
                        CompilerError.MismatchedTypes(line=value.lineno,
1247
                                                      col=value.col_offset,
1248
                                                      expected_type_id=Type.tuple.identifier,
1249
                                                      actual_type_id=self.get_type(value).identifier)
1250
                    else:
1251
                        arg_name = value.elts[0].s
1252
                        arg_type = (self.get_symbol(value.elts[1].id)
1253
                                    if isinstance(value.elts[1], ast.Name)
1254
                                    else self.visit(value.elts[1]))
1255
                        args[arg_name] = Variable(arg_type)
1256

1257
            if len(event_args) > 1:
1258
                if not isinstance(event_args[1], ast.Str):
1259
                    name_type = self.get_type(event_args[1])
1260
                    CompilerError.MismatchedTypes(line=event_args[1].lineno,
1261
                                                  col=event_args[1].col_offset,
1262
                                                  expected_type_id=Type.str.identifier,
1263
                                                  actual_type_id=name_type.identifier)
1264
                else:
1265
                    name = event_args[1].s
1266

1267
        event = Event(name, args)
1268
        event._origin_node = create_call
1269
        return event
1270

1271
    def visit_Attribute(self, attribute: ast.Attribute) -> Union[ISymbol, str]:
1272
        """
1273
        Gets the attribute inside the ast node
1274

1275
        :param attribute: the python ast attribute node
1276
        :return: returns the type of the value, the attribute symbol and its id if the attribute exists.
1277
                 Otherwise, returns None
1278
        """
1279
        value_id = attribute.value.id if isinstance(attribute.value, ast.Name) else None
1280
        value: ISymbol = self.get_symbol(value_id) if value_id is not None else self.visit(attribute.value)
1281

1282
        if isinstance(value, Variable):
1283
            value = value.type
1284
        if hasattr(value, 'symbols') and attribute.attr in value.symbols:
1285
            return value.symbols[attribute.attr]
1286
        elif isinstance(value, Package) and attribute.attr in value.inner_packages:
1287
            return value.inner_packages[attribute.attr]
1288
        elif Builtin.get_symbol(attribute.attr) is not None:
1289
            return Builtin.get_symbol(attribute.attr)
1290
        elif isinstance(value, UndefinedType):
1291
            return value
1292
        else:
1293
            return '{0}.{1}'.format(value_id, attribute.attr)
1294

1295
    def visit_For(self, for_node: ast.For):
1296
        """
1297
        Visitor of for statement node
1298

1299
        :param for_node: the python ast for node
1300
        """
1301
        iter_type = self.get_type(for_node.iter)
1302
        targets = self.visit(for_node.target)
1303
        iterator_type = iter_type.value_type if hasattr(iter_type, 'value_type') else iter_type
1304

1305
        if isinstance(targets, str):
1306
            self.__include_variable(targets, iterator_type, source_node=for_node.target)
1307
        elif isinstance(iter_type, SequenceType):
1308
            for target in targets:
1309
                if isinstance(target, str):
1310
                    self.__include_variable(target, iterator_type, source_node=for_node.target)
1311

1312
        # continue to walk through the tree
1313
        self.generic_visit(for_node)
1314

1315
    def visit_Name(self, name: ast.Name) -> str:
1316
        """
1317
        Visitor of a name node
1318

1319
        :param name:
1320
        :return: the identifier of the name
1321
        """
1322
        return name.id
1323

1324
    def visit_Starred(self, node: ast.Starred):
1325
        # TODO: refactor when starred variables are implemented
1326
        self._log_error(
1327
            CompilerError.NotSupportedOperation(
1328
                node.lineno, node.col_offset,
1329
                symbol_id='* variables'
1330
            )
1331
        )
1332
        return node.value
1333

1334
    def visit_Constant(self, constant: ast.Constant) -> Any:
1335
        """
1336
        Visitor of constant values node
1337

1338
        :param constant: the python ast constant value node
1339
        :return: the value of the constant
1340
        """
1341
        return constant.value
1342

1343
    def visit_NameConstant(self, constant: ast.NameConstant) -> Any:
1344
        """
1345
        Visitor of constant names node
1346

1347
        :param constant:
1348
        :return: the value of the constant
1349
        """
1350
        return constant.value
1351

1352
    def visit_Num(self, num: ast.Num) -> int:
1353
        """
1354
        Visitor of literal number node
1355

1356
        :param num:
1357
        :return: the value of the number
1358
        """
1359
        return num.n
1360

1361
    def visit_Str(self, str: ast.Str) -> str:
1362
        """
1363
        Visitor of literal string node
1364

1365
        :param str:
1366
        :return: the value of the string
1367
        """
1368
        return str.s
1369

1370
    def visit_Bytes(self, btes: ast.Bytes) -> bytes:
1371
        """
1372
        Visitor of literal string node
1373

1374
        :param btes:
1375
        :return: the value of the string
1376
        """
1377
        return btes.s
1378

1379
    def visit_Tuple(self, tup_node: ast.Tuple) -> Optional[Tuple[Any, ...]]:
1380
        """
1381
        Visitor of literal tuple node
1382

1383
        :param tup_node: the python ast string node
1384
        :return: the value of the tuple
1385
        """
1386
        result = [self.get_type(value) for value in tup_node.elts]
1387
        if Type.none in result and isinstance(tup_node.ctx, ast.Load):
1388
            # if can't define the type of any value, let it to be defined in the type checking
1389
            # only in load ctx because on store ctx means a tuple of variables to be assigned
1390
            return None
1391
        return tuple(result)
1392

1393
    def visit_List(self, list_node: ast.List) -> Optional[List[Any]]:
1394
        """
1395
        Visitor of literal list node
1396

1397
        :param list_node: the python ast list node
1398
        :return: the value of the list
1399
        """
1400
        result = [self.get_type(value) for value in list_node.elts]
1401
        if Type.none in result:
1402
            # if can't define the type of any value, let it to be defined in the type checking
1403
            return None
1404
        return result
1405

1406
    def visit_Dict(self, dict_node: ast.Dict) -> Optional[Dict[Any, Any]]:
1407
        """
1408
        Visitor of literal dict node
1409

1410
        :param dict_node: the python ast dict node
1411
        :return: the value of the dict
1412
        """
1413
        dictionary = {}
1414
        size = min(len(dict_node.keys), len(dict_node.values))
1415
        for index in range(size):
1416
            key = self.get_type(dict_node.keys[index])
1417
            value = self.get_type(dict_node.values[index])
1418
            if key in dictionary and dictionary[key] != value:
1419
                dictionary[key] = Type.get_generic_type(dictionary[key], value)
1420
            else:
1421
                dictionary[key] = value
1422

1423
        keys = set(dictionary.keys())
1424
        values = set(dictionary.values())
1425

1426
        if Type.none in keys or Type.none in values:
1427
            # if can't define the type of any key or value, let it to be defined in the type checking
1428
            return None
1429
        return dictionary
1430

1431
    # endregion
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