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

CityOfZion / neo3-boa / 0f033062-832e-4352-844e-60026154ee10

28 Nov 2023 08:29PM CUT coverage: 92.019% (+0.02%) from 92.002%
0f033062-832e-4352-844e-60026154ee10

push

circleci

web-flow
Merge pull request #1151 from CityOfZion/CU-2ewf14b

CU-2ewf14b - Add support to String Interpolation (f-string)

99 of 109 new or added lines in 10 files covered. (90.83%)

110 existing lines in 4 files now uncovered.

20547 of 22329 relevant lines covered (92.02%)

1.84 hits per line

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

95.93
/boa3/internal/compiler/codegenerator/codegeneratorvisitor.py
1
import ast
2✔
2
import os.path
2✔
3
from inspect import isclass
2✔
4
from typing import Dict, List, Optional
2✔
5

6
from boa3.internal import constants
2✔
7
from boa3.internal.analyser.astanalyser import IAstAnalyser
2✔
8
from boa3.internal.compiler.codegenerator.codegenerator import CodeGenerator
2✔
9
from boa3.internal.compiler.codegenerator.generatordata import GeneratorData
2✔
10
from boa3.internal.compiler.codegenerator.variablegenerationdata import VariableGenerationData
2✔
11
from boa3.internal.compiler.codegenerator.vmcodemapping import VMCodeMapping
2✔
12
from boa3.internal.model.builtin.builtin import Builtin
2✔
13
from boa3.internal.model.builtin.method.builtinmethod import IBuiltinMethod
2✔
14
from boa3.internal.model.expression import IExpression
2✔
15
from boa3.internal.model.imports.package import Package
2✔
16
from boa3.internal.model.method import Method
2✔
17
from boa3.internal.model.operation.binary.binaryoperation import BinaryOperation
2✔
18
from boa3.internal.model.operation.binaryop import BinaryOp
2✔
19
from boa3.internal.model.operation.operation import IOperation
2✔
20
from boa3.internal.model.operation.unary.unaryoperation import UnaryOperation
2✔
21
from boa3.internal.model.property import Property
2✔
22
from boa3.internal.model.symbol import ISymbol
2✔
23
from boa3.internal.model.type.classes.classtype import ClassType
2✔
24
from boa3.internal.model.type.classes.userclass import UserClass
2✔
25
from boa3.internal.model.type.collection.sequence.sequencetype import SequenceType
2✔
26
from boa3.internal.model.type.type import IType, Type
2✔
27
from boa3.internal.model.variable import Variable
2✔
28

29

30
class VisitorCodeGenerator(IAstAnalyser):
2✔
31
    """
32
    This class is responsible for walk through the ast.
33

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

37
    :ivar generator:
38
    """
39

40
    def __init__(self, generator: CodeGenerator, filename: str = None, root: str = None):
2✔
41
        super().__init__(ast.parse(""), filename=filename, root_folder=root, log=True, fail_fast=True)
2✔
42

43
        self.generator = generator
2✔
44
        self.current_method: Optional[Method] = None
2✔
45
        self.current_class: Optional[UserClass] = None
2✔
46
        self.symbols = generator.symbol_table
2✔
47

48
        self.global_stmts: List[ast.AST] = []
2✔
49
        self._is_generating_initialize = False
2✔
50
        self._root_module: ast.AST = self._tree
2✔
51

52
    @property
2✔
53
    def _symbols(self) -> Dict[str, ISymbol]:
2✔
54
        symbol_table = self.symbols.copy()
2✔
55

56
        if isinstance(self.current_class, UserClass):
2✔
57
            symbol_table.update(self.current_class.symbols)
2✔
58

59
        return symbol_table
2✔
60

61
    def include_instruction(self, node: ast.AST, address: int):
2✔
62
        if self.current_method is not None and address in VMCodeMapping.instance().code_map:
2✔
63
            bytecode = VMCodeMapping.instance().code_map[address]
2✔
64
            from boa3.internal.model.debuginstruction import DebugInstruction
2✔
65
            self.current_method.include_instruction(DebugInstruction.build(node, bytecode))
2✔
66

67
    def build_data(self, origin_node: Optional[ast.AST],
2✔
68
                   symbol_id: Optional[str] = None,
69
                   symbol: Optional[ISymbol] = None,
70
                   result_type: Optional[IType] = None,
71
                   index: Optional[int] = None,
72
                   origin_object_type: Optional[ISymbol] = None,
73
                   already_generated: bool = False) -> GeneratorData:
74

75
        if isinstance(symbol, IType) and result_type is None:
2✔
76
            result_type = symbol
2✔
77
            symbol = None
2✔
78

79
        if symbol is None and symbol_id is not None:
2✔
80
            # try to find the symbol if the id is known
81
            if symbol_id in self._symbols:
2✔
82
                found_symbol = self._symbols[symbol_id]
2✔
83
            else:
84
                _, found_symbol = self.generator.get_symbol(symbol_id)
2✔
85
                if found_symbol is Type.none:
2✔
86
                    # generator get_symbol returns Type.none if the symbol is not found
87
                    found_symbol = None
2✔
88

89
            if found_symbol is not None:
2✔
90
                symbol = found_symbol
2✔
91

92
        return GeneratorData(origin_node, symbol_id, symbol, result_type, index, origin_object_type, already_generated)
2✔
93

94
    def visit_and_update_analyser(self, node: ast.AST, target_analyser) -> GeneratorData:
2✔
95
        result = self.visit(node)
2✔
96
        if hasattr(target_analyser, '_update_logs'):
2✔
97
            target_analyser._update_logs(self)
2✔
98
        return result
2✔
99

100
    def visit(self, node: ast.AST) -> GeneratorData:
2✔
101
        result = super().visit(node)
2✔
102
        if not isinstance(result, GeneratorData):
2✔
103
            result = self.build_data(node)
2✔
104
        return result
2✔
105

106
    def visit_to_map(self, node: ast.AST, generate: bool = False) -> GeneratorData:
2✔
107
        address: int = VMCodeMapping.instance().bytecode_size
2✔
108
        if isinstance(node, ast.Expr):
2✔
109
            value = self.visit_Expr(node, generate)
2✔
110
        elif generate:
2✔
111
            value = self.visit_to_generate(node)
2✔
112
        else:
113
            value = self.visit(node)
2✔
114

115
        if not isinstance(node, (ast.For, ast.While, ast.If)):
2✔
116
            # control flow nodes must map each of their instructions
117
            self.include_instruction(node, address)
2✔
118
        return value
2✔
119

120
    def visit_to_generate(self, node) -> GeneratorData:
2✔
121
        """
122
        Visitor to generate the nodes that the primary visitor is used to retrieve value
123

124
        :param node: an ast node
125
        """
126
        if isinstance(node, ast.AST):
2✔
127
            result = self.visit(node)
2✔
128

129
            if not result.already_generated and result.symbol_id is not None:
2✔
130
                if self.is_exception_name(result.symbol_id):
2✔
131
                    self.generator.convert_new_exception()
2✔
132
                else:
133
                    is_internal = hasattr(node, 'is_internal_call') and node.is_internal_call
2✔
134
                    class_type = result.type if isinstance(node, ast.Attribute) else None
2✔
135

136
                    if (self.is_implemented_class_type(result.type)
2✔
137
                            and len(result.symbol_id.split(constants.ATTRIBUTE_NAME_SEPARATOR)) > 1):
138
                        # if the symbol id has the attribute separator and the top item on the stack is a user class,
139
                        # then this value is an attribute from that class
140
                        # change the id for correct generation
141
                        result.symbol_id = result.symbol_id.split(constants.ATTRIBUTE_NAME_SEPARATOR)[-1]
2✔
142

143
                    elif isinstance(result.index, Package):
2✔
144
                        class_type = None
2✔
145

146
                    self.generator.convert_load_symbol(result.symbol_id, is_internal=is_internal, class_type=class_type)
2✔
147

148
                result.already_generated = True
2✔
149

150
            return result
2✔
151
        else:
152
            index = self.generator.convert_literal(node)
2✔
153
            return self.build_data(node, index=index)
2✔
154

155
    def is_exception_name(self, exc_id: str) -> bool:
2✔
156
        global_symbols = globals()
2✔
157
        if exc_id in global_symbols or exc_id in global_symbols['__builtins__']:
2✔
158
            symbol = (global_symbols[exc_id]
2✔
159
                      if exc_id in global_symbols
160
                      else global_symbols['__builtins__'][exc_id])
161
            if isclass(symbol) and issubclass(symbol, BaseException):
2✔
162
                return True
2✔
163
        return False
2✔
164

165
    def _remove_inserted_opcodes_since(self, last_address: int, last_stack_size: Optional[int] = None):
2✔
166
        self.generator._remove_inserted_opcodes_since(last_address, last_stack_size)
2✔
167

168
    def _get_unique_name(self, name_id: str, node: ast.AST) -> str:
2✔
169
        return '{0}{2}{1}'.format(node.__hash__(), name_id, constants.VARIABLE_NAME_SEPARATOR)
2✔
170

171
    def set_filename(self, filename: str):
2✔
172
        if isinstance(filename, str) and os.path.isfile(filename):
2✔
173
            if constants.PATH_SEPARATOR != os.path.sep:
2✔
174
                self.filename = filename.replace(constants.PATH_SEPARATOR, os.path.sep)
×
175
            else:
176
                self.filename = filename
2✔
177

178
    def visit_Module(self, module: ast.Module) -> GeneratorData:
2✔
179
        """
180
        Visitor of the module node
181

182
        Fills module symbol table
183

184
        :param module:
185
        """
186
        global_stmts = [node for node in module.body if not isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef))]
2✔
187
        function_stmts = module.body[len(global_stmts):]
2✔
188
        mandatory_global_stmts = []
2✔
189
        for stmt in global_stmts:
2✔
190
            if isinstance(stmt, ast.ClassDef):
2✔
191
                class_symbol = self.get_symbol(stmt.name)
2✔
192
                if isinstance(class_symbol, UserClass) and len(class_symbol.class_variables) > 0:
2✔
193
                    mandatory_global_stmts.append(stmt)
2✔
194
            elif not isinstance(stmt, (ast.Import, ast.ImportFrom)):
2✔
195
                mandatory_global_stmts.append(stmt)
2✔
196

197
        for stmt in function_stmts:
2✔
198
            self.visit(stmt)
2✔
199

200
        if self.generator.initialize_static_fields():
2✔
201
            last_symbols = self.symbols  # save to revert in the end and not compromise consequent visits
2✔
202
            class_non_static_stmts = []
2✔
203

204
            for node in global_stmts.copy():
2✔
205
                if isinstance(node, ast.ClassDef):
2✔
206
                    class_origin_module = None
2✔
207
                    if hasattr(node, 'origin'):
2✔
208
                        class_origin_module = node.origin
2✔
209
                        if (node.origin is not self._root_module
2✔
210
                                and hasattr(node.origin, 'symbols')):
211
                            # symbols unique to imports are not included in the symbols
212
                            self.symbols = node.origin.symbols
2✔
213

214
                    class_variables = []
2✔
215
                    class_functions = []
2✔
216
                    for stmt in node.body:
2✔
217
                        if isinstance(stmt, (ast.Assign, ast.AugAssign, ast.AnnAssign)):
2✔
218
                            class_variables.append(stmt)
2✔
219
                        else:
220
                            class_functions.append(stmt)
2✔
221

222
                    if len(class_functions) > 0:
2✔
223
                        if class_origin_module is not None and not hasattr(node, 'origin'):
2✔
224
                            node.origin = class_origin_module
×
225

226
                        cls_fun = node
2✔
227
                        if len(class_variables) > 0:
2✔
228
                            cls_var = node
2✔
229
                            cls_fun = self.clone(node)
2✔
230
                            cls_fun.body = class_functions
2✔
231
                            cls_var.body = class_variables
2✔
232
                        else:
233
                            global_stmts.remove(cls_fun)
2✔
234

235
                        class_non_static_stmts.append(cls_fun)
2✔
236
                    self.symbols = last_symbols  # don't use inner scopes to evaluate the other globals
2✔
237

238
            # to generate the 'initialize' method for Neo
239
            self._log_info(f"Compiling '{constants.INITIALIZE_METHOD_ID}' function")
2✔
240
            self._is_generating_initialize = True
2✔
241
            for stmt in global_stmts:
2✔
242
                cur_tree = self._tree
2✔
243
                cur_filename = self.filename
2✔
244
                if hasattr(stmt, 'origin'):
2✔
245
                    if hasattr(stmt.origin, 'filename'):
2✔
246
                        self.set_filename(stmt.origin.filename)
2✔
247
                    self._tree = stmt.origin
2✔
248

249
                self.visit(stmt)
2✔
250
                self.filename = cur_filename
2✔
251
                self._tree = cur_tree
2✔
252

253
            self._is_generating_initialize = False
2✔
254
            self.generator.end_initialize()
2✔
255

256
            # generate any symbol inside classes that's not variables AFTER generating 'initialize' method
257
            for stmt in class_non_static_stmts:
2✔
258
                cur_tree = self._tree
2✔
259
                if hasattr(stmt, 'origin'):
2✔
260
                    self._tree = stmt.origin
2✔
261
                self.visit(stmt)
2✔
262
                self._tree = cur_tree
2✔
263

264
            self.symbols = last_symbols  # revert in the end to not compromise consequent visits
2✔
265
            self.generator.additional_symbols = None
2✔
266

267
        elif len(function_stmts) > 0 or len(mandatory_global_stmts) > 0:
2✔
268
            # to organize syntax tree nodes from other modules
269
            for stmt in global_stmts:
2✔
270
                if not hasattr(stmt, 'origin'):
2✔
271
                    stmt.origin = module
2✔
272

273
            module.symbols = self._symbols
2✔
274
            self.global_stmts.extend(global_stmts)
2✔
275
        else:
276
            # to generate objects when there are no static variables to generate 'initialize'
277
            for stmt in global_stmts:
2✔
278
                self.visit(stmt)
2✔
279

280
        return self.build_data(module)
2✔
281

282
    def visit_ClassDef(self, node: ast.ClassDef) -> GeneratorData:
2✔
283
        if node.name in self.symbols:
2✔
284
            class_symbol = self.symbols[node.name]
2✔
285
            if isinstance(class_symbol, UserClass):
2✔
286
                self.current_class = class_symbol
2✔
287

288
            if self._is_generating_initialize:
2✔
289
                address = self.generator.bytecode_size
2✔
290
                self.generator.convert_new_empty_array(len(class_symbol.class_variables), class_symbol)
2✔
291
                self.generator.convert_store_variable(node.name, address)
2✔
292
            else:
293
                init_method = class_symbol.constructor_method()
2✔
294
                if isinstance(init_method, Method) and init_method.start_address is None:
2✔
295
                    self.generator.generate_implicit_init_user_class(init_method)
2✔
296

297
        for stmt in node.body:
2✔
298
            self.visit(stmt)
2✔
299

300
        self.current_class = None
2✔
301
        return self.build_data(node)
2✔
302

303
    def visit_FunctionDef(self, function: ast.FunctionDef) -> GeneratorData:
2✔
304
        """
305
        Visitor of the function definition node
306

307
        Generates the Neo VM code for the function
308

309
        :param function: the python ast function definition node
310
        """
311
        method = self._symbols[function.name]
2✔
312

313
        if isinstance(method, Property):
2✔
314
            method = method.getter
2✔
315

316
        if isinstance(method, Method):
2✔
317
            self.current_method = method
2✔
318
            if isinstance(self.current_class, ClassType):
2✔
319
                function_name = self.current_class.identifier + constants.ATTRIBUTE_NAME_SEPARATOR + function.name
2✔
320
            else:
321
                function_name = function.name
2✔
322

323
            self._log_info(f"Compiling '{function_name}' function")
2✔
324

325
            if method.is_public or method.is_called:
2✔
326
                if not isinstance(self.current_class, ClassType) or not self.current_class.is_interface:
2✔
327
                    self.generator.convert_begin_method(method)
2✔
328

329
                    for stmt in function.body:
2✔
330
                        self.visit_to_map(stmt)
2✔
331

332
                    self.generator.convert_end_method(function.name)
2✔
333

334
            self.current_method = None
2✔
335

336
        return self.build_data(function, symbol=method, symbol_id=function.name)
2✔
337

338
    def visit_Return(self, ret: ast.Return) -> GeneratorData:
2✔
339
        """
340
        Visitor of a function return node
341

342
        :param ret: the python ast return node
343
        """
344
        if self.generator.stack_size > 0:
2✔
345
            self.generator.clear_stack(True)
2✔
346

347
        if self.current_method.return_type is not Type.none:
2✔
348
            result = self.visit_to_generate(ret.value)
2✔
349
            if result.type is Type.none and not self.generator.is_none_inserted():
2✔
350
                self.generator.convert_literal(None)
2✔
351
        elif ret.value is not None:
2✔
352
            self.visit_to_generate(ret.value)
2✔
353
            if self.generator.stack_size > 0:
2✔
354
                self.generator.remove_stack_top_item()
2✔
355

356
        self.generator.insert_return()
2✔
357

358
        return self.build_data(ret)
2✔
359

360
    def store_variable(self, *var_ids: VariableGenerationData, value: ast.AST):
2✔
361
        # if the value is None, it is a variable declaration
362
        if value is not None:
2✔
363
            if len(var_ids) == 1:
2✔
364
                # it's a simple assignment
365
                var_id, index, address = var_ids[0]
2✔
366
                if index is None:
2✔
367
                    # if index is None, then it is a variable assignment
368
                    result_data = self.visit_to_generate(value)
2✔
369

370
                    if result_data.type is Type.none and not self.generator.is_none_inserted():
2✔
371
                        self.generator.convert_literal(None)
2✔
372
                    self.generator.convert_store_variable(var_id, address, self.current_class if self.current_method is None else None)
2✔
373
                else:
374
                    # if not, it is an array assignment
375
                    self.generator.convert_load_symbol(var_id)
2✔
376
                    self.visit_to_generate(index)
2✔
377

378
                    aux_index = VMCodeMapping.instance().bytecode_size
2✔
379
                    value_data = self.visit_to_generate(value)
2✔
380
                    value_address = value_data.index if value_data.index is not None else aux_index
2✔
381

382
                    self.generator.convert_set_item(value_address)
2✔
383

384
            elif len(var_ids) > 0:
2✔
385
                # it's a chained assignment
386
                self.visit_to_generate(value)
2✔
387
                for pos, (var_id, index, address) in enumerate(reversed(var_ids)):
2✔
388
                    if index is None:
2✔
389
                        # if index is None, then it is a variable assignment
390
                        if pos < len(var_ids) - 1:
2✔
391
                            self.generator.duplicate_stack_top_item()
2✔
392
                        self.generator.convert_store_variable(var_id, address)
2✔
393
                    else:
394
                        # if not, it is an array assignment
395
                        if pos < len(var_ids) - 1:
2✔
396
                            self.generator.convert_load_symbol(var_id)
2✔
397
                            self.visit_to_generate(index)
2✔
398
                            fix_index = VMCodeMapping.instance().bytecode_size
2✔
399
                            self.generator.duplicate_stack_item(3)
2✔
400
                        else:
401
                            self.visit_to_generate(index)
2✔
402
                            fix_index = VMCodeMapping.instance().bytecode_size
2✔
403
                            self.generator.convert_load_symbol(var_id)
2✔
404
                            self.generator.swap_reverse_stack_items(3)
2✔
405
                        self.generator.convert_set_item(fix_index)
2✔
406

407
    def visit_AnnAssign(self, ann_assign: ast.AnnAssign) -> GeneratorData:
2✔
408
        """
409
        Visitor of an annotated assignment node
410

411
        :param ann_assign: the python ast variable assignment node
412
        """
413
        var_value_address = self.generator.bytecode_size
2✔
414
        var_data = self.visit(ann_assign.target)
2✔
415
        var_id = var_data.symbol_id
2✔
416
        # filter to find the imported variables
417
        if (var_id not in self.generator.symbol_table
2✔
418
                and hasattr(ann_assign, 'origin')
419
                and isinstance(ann_assign.origin, ast.AST)):
420
            var_id = self._get_unique_name(var_id, ann_assign.origin)
×
421
        self.store_variable(VariableGenerationData(var_id, None, var_value_address), value=ann_assign.value)
2✔
422
        return self.build_data(ann_assign)
2✔
423

424
    def visit_Assign(self, assign: ast.Assign) -> GeneratorData:
2✔
425
        """
426
        Visitor of an assignment node
427

428
        :param assign: the python ast variable assignment node
429
        """
430
        vars_ids: List[VariableGenerationData] = []
2✔
431
        for target in assign.targets:
2✔
432
            var_value_address = self.generator.bytecode_size
2✔
433
            target_data: GeneratorData = self.visit(target)
2✔
434

435
            var_id = target_data.symbol_id
2✔
436
            var_index = target_data.index
2✔
437

438
            # filter to find the imported variables
439
            if (var_id not in self.generator.symbol_table
2✔
440
                    and hasattr(assign, 'origin')
441
                    and isinstance(assign.origin, ast.AST)):
442
                var_id = self._get_unique_name(var_id, assign.origin)
×
443

444
            vars_ids.append(VariableGenerationData(var_id, var_index, var_value_address))
2✔
445

446
        self.store_variable(*vars_ids, value=assign.value)
2✔
447
        return self.build_data(assign)
2✔
448

449
    def visit_AugAssign(self, aug_assign: ast.AugAssign) -> GeneratorData:
2✔
450
        """
451
        Visitor of an augmented assignment node
452

453
        :param aug_assign: the python ast augmented assignment node
454
        """
455
        start_address = self.generator.bytecode_size
2✔
456
        var_data = self.visit(aug_assign.target)
2✔
457
        var_id = var_data.symbol_id
2✔
458
        # filter to find the imported variables
459
        if (var_id not in self.generator.symbol_table
2✔
460
                and hasattr(aug_assign, 'origin')
461
                and isinstance(aug_assign.origin, ast.AST)):
462
            var_id = self._get_unique_name(var_id, aug_assign.origin)
×
463

464
        if isinstance(var_data.type, UserClass):
2✔
465
            self.generator.duplicate_stack_top_item()
2✔
466
        elif hasattr(var_data.origin_object_type, 'identifier') and var_data.already_generated:
2✔
467
            VMCodeMapping.instance().remove_opcodes(start_address)
×
468
            self.generator.convert_load_symbol(var_data.origin_object_type.identifier)
×
469

470
        self.generator.convert_load_symbol(var_id, class_type=var_data.origin_object_type)
2✔
471
        value_address = self.generator.bytecode_size
2✔
472
        self.visit_to_generate(aug_assign.value)
2✔
473
        self.generator.convert_operation(aug_assign.op)
2✔
474
        self.generator.convert_store_variable(var_id, value_address, user_class=var_data.origin_object_type)
2✔
475
        return self.build_data(aug_assign)
2✔
476

477
    def visit_Subscript(self, subscript: ast.Subscript) -> GeneratorData:
2✔
478
        """
479
        Visitor of a subscript node
480

481
        :param subscript: the python ast subscript node
482
        """
483
        if isinstance(subscript.slice, ast.Slice):
2✔
484
            return self.visit_Subscript_Slice(subscript)
2✔
485

486
        return self.visit_Subscript_Index(subscript)
2✔
487

488
    def visit_Subscript_Index(self, subscript: ast.Subscript) -> GeneratorData:
2✔
489
        """
490
        Visitor of a subscript node with index
491

492
        :param subscript: the python ast subscript node
493
        """
494
        index = None
2✔
495
        symbol_id = None
2✔
496
        if isinstance(subscript.ctx, ast.Load):
2✔
497
            # get item
498
            value_data = self.visit_to_generate(subscript.value)
2✔
499
            slice = subscript.slice.value if isinstance(subscript.slice, ast.Index) else subscript.slice
2✔
500
            self.visit_to_generate(slice)
2✔
501

502
            index_is_constant_number = isinstance(slice, ast.Num) and isinstance(slice.n, int)
2✔
503
            self.generator.convert_get_item(index_is_positive=(index_is_constant_number and slice.n >= 0),
2✔
504
                                            test_is_negative_index=not (index_is_constant_number and slice.n < 0))
505

506
            value_type = value_data.type
2✔
507
        else:
508
            # set item
509
            var_data = self.visit(subscript.value)
2✔
510

511
            index = subscript.slice.value if isinstance(subscript.slice, ast.Index) else subscript.slice
2✔
512
            symbol_id = var_data.symbol_id
2✔
513
            value_type = var_data.type
2✔
514

515
        result_type = value_type.item_type if isinstance(value_type, SequenceType) else value_type
2✔
516
        return self.build_data(subscript, result_type=result_type,
2✔
517
                               symbol_id=symbol_id, index=index)
518

519
    def visit_Subscript_Slice(self, subscript: ast.Subscript) -> GeneratorData:
2✔
520
        """
521
        Visitor of a subscript node with slice
522

523
        :param subscript: the python ast subscript node
524
        """
525
        lower_omitted = subscript.slice.lower is None
2✔
526
        upper_omitted = subscript.slice.upper is None
2✔
527
        step_omitted = subscript.slice.step is None
2✔
528

529
        self.visit_to_generate(subscript.value)
2✔
530

531
        step_negative = True if not step_omitted and subscript.slice.step.n < 0 else False
2✔
532

533
        if step_negative:
2✔
534
            # reverse the ByteString or the list
535
            self.generator.convert_array_negative_stride()
2✔
536

537
        addresses = []
2✔
538

539
        for index_number, omitted in [(subscript.slice.lower, lower_omitted), (subscript.slice.upper, upper_omitted)]:
2✔
540
            if not omitted:
2✔
541
                addresses.append(VMCodeMapping.instance().bytecode_size)
2✔
542
                self.visit_to_generate(index_number)
2✔
543

544
                index_is_constant_number = isinstance(index_number, ast.Num) and isinstance(index_number.n, int)
2✔
545
                if index_is_constant_number and index_number.n < 0:
2✔
546
                    self.generator.fix_negative_index(test_is_negative=False)
2✔
547
                elif not index_is_constant_number:
2✔
548
                    self.generator.fix_negative_index()
2✔
549

550
                if step_negative:
2✔
551
                    self.generator.duplicate_stack_item(len(addresses) + 1)  # duplicates the subscript
2✔
552
                    self.generator.fix_index_negative_stride()
2✔
553

554
                self.generator.fix_index_out_of_range(has_another_index_in_stack=len(addresses) == 2)
2✔
555

556
        # if both are explicit
557
        if not lower_omitted and not upper_omitted:
2✔
558
            self.generator.convert_get_sub_sequence()
2✔
559

560
        # only one of them is omitted
561
        elif lower_omitted != upper_omitted:
2✔
562
            # start position is omitted
563
            if lower_omitted:
2✔
564
                self.generator.convert_get_sequence_beginning()
2✔
565
            # end position is omitted
566
            else:
567
                self.generator.convert_get_sequence_ending()
2✔
568
        else:
569
            self.generator.convert_copy()
2✔
570

571
        if not step_omitted:
2✔
572
            self.visit_to_generate(subscript.slice.step)
2✔
573
            self.generator.convert_get_stride()
2✔
574

575
        return self.build_data(subscript)
2✔
576

577
    def _convert_unary_operation(self, operand, op):
2✔
578
        self.visit_to_generate(operand)
2✔
579
        self.generator.convert_operation(op)
2✔
580

581
    def _convert_binary_operation(self, left, right, op):
2✔
582
        self.visit_to_generate(left)
2✔
583
        self.visit_to_generate(right)
2✔
584
        self.generator.convert_operation(op)
2✔
585

586
    def visit_BinOp(self, bin_op: ast.BinOp) -> GeneratorData:
2✔
587
        """
588
        Visitor of a binary operation node
589

590
        :param bin_op: the python ast binary operation node
591
        """
592
        if isinstance(bin_op.op, BinaryOperation):
2✔
593
            self._convert_binary_operation(bin_op.left, bin_op.right, bin_op.op)
2✔
594

595
        return self.build_data(bin_op)
2✔
596

597
    def visit_UnaryOp(self, un_op: ast.UnaryOp) -> GeneratorData:
2✔
598
        """
599
        Visitor of a binary operation node
600

601
        :param un_op: the python ast binary operation node
602
        """
603
        if isinstance(un_op.op, UnaryOperation):
2✔
604
            self._convert_unary_operation(un_op.operand, un_op.op)
2✔
605

606
        return self.build_data(un_op, result_type=un_op.op.result)
2✔
607

608
    def visit_Compare(self, compare: ast.Compare) -> GeneratorData:
2✔
609
        """
610
        Visitor of a compare operation node
611

612
        :param compare: the python ast compare operation node
613
        """
614
        operation_symbol: IOperation = BinaryOp.And
2✔
615
        converted: bool = False
2✔
616
        left = compare.left
2✔
617
        for index, op in enumerate(compare.ops):
2✔
618
            right = compare.comparators[index]
2✔
619
            if isinstance(op, IOperation):
2✔
620
                if isinstance(op, BinaryOperation):
2✔
621
                    self._convert_binary_operation(left, right, op)
2✔
622
                else:
623
                    operand = left
2✔
624
                    if isinstance(operand, ast.NameConstant) and operand.value is None:
2✔
625
                        operand = right
×
626
                    self._convert_unary_operation(operand, op)
2✔
627
                # if it's more than two comparators, must include AND between the operations
628
                if not converted:
2✔
629
                    converted = True
2✔
630
                    operation_symbol = op
2✔
631
                else:
632
                    operation_symbol = BinaryOp.And
2✔
633
                    self.generator.convert_operation(BinaryOp.And)
2✔
634
            left = right
2✔
635

636
        return self.build_data(compare, symbol=operation_symbol, result_type=operation_symbol.result)
2✔
637

638
    def visit_BoolOp(self, bool_op: ast.BoolOp) -> GeneratorData:
2✔
639
        """
640
        Visitor of a compare operation node
641

642
        :param bool_op: the python ast boolean operation node
643
        """
644
        if isinstance(bool_op.op, BinaryOperation):
2✔
645
            left = bool_op.values[0]
2✔
646
            self.visit_to_generate(left)
2✔
647
            for index, right in enumerate(bool_op.values[1:]):
2✔
648
                self.visit_to_generate(right)
2✔
649
                self.generator.convert_operation(bool_op.op)
2✔
650

651
        return self.build_data(bool_op)
2✔
652

653
    def visit_While(self, while_node: ast.While) -> GeneratorData:
2✔
654
        """
655
        Visitor of a while statement node
656

657
        :param while_node: the python ast while statement node
658
        """
659
        start_addr: int = self.generator.convert_begin_while()
2✔
660
        for stmt in while_node.body:
2✔
661
            self.visit_to_map(stmt, generate=True)
2✔
662

663
        test_address: int = VMCodeMapping.instance().bytecode_size
2✔
664
        test_data = self.visit_to_map(while_node.test, generate=True)
2✔
665
        self.generator.convert_end_while(start_addr, test_address)
2✔
666

667
        else_begin_address: int = self.generator.last_code_start_address
2✔
668
        for stmt in while_node.orelse:
2✔
669
            self.visit_to_map(stmt, generate=True)
2✔
670

671
        self.generator.convert_end_loop_else(start_addr, else_begin_address, len(while_node.orelse) > 0)
2✔
672
        return self.build_data(while_node, index=start_addr)
2✔
673

674
    def visit_Match(self, match_node: ast.Match) -> GeneratorData:
2✔
675
        case_addresses = []
2✔
676

677
        for index, case in enumerate(match_node.cases):
2✔
678
            # if it's the wildcard to accept all values
679
            if hasattr(case.pattern, 'pattern') and case.pattern.pattern is None:
2✔
680
                for stmt in case.body:
2✔
681
                    self.visit_to_map(stmt, generate=True)
2✔
682
                self.generator.convert_end_if(case_addresses[-1])
2✔
683
            else:
684
                subject = self.visit_to_map(match_node.subject, generate=True)
2✔
685
                if isinstance(case.pattern, ast.MatchSingleton):
2✔
686
                    self.generator.convert_literal(case.pattern.value)
2✔
687
                    pattern_type = self.get_type(case.pattern.value)
2✔
688
                else:
689
                    pattern = self.visit_to_generate(case.pattern.value)
2✔
690
                    pattern_type = pattern.type
2✔
691

692
                self.generator.duplicate_stack_item(2)
2✔
693
                pattern_type.generate_is_instance_type_check(self.generator)
2✔
694

695
                self.generator.swap_reverse_stack_items(3)
2✔
696
                self.generator.convert_operation(BinaryOp.NumEq)
2✔
697
                self.generator.convert_operation(BinaryOp.And)
2✔
698

699
                case_addresses.append(self.generator.convert_begin_if())
2✔
700
                for stmt in case.body:
2✔
701
                    self.visit_to_map(stmt, generate=True)
2✔
702

703
                ends_with_if = len(case.body) > 0 and isinstance(case.body[-1], ast.If)
2✔
704

705
                if index < len(match_node.cases) - 1:
2✔
706
                    case_addresses[index] = self.generator.convert_begin_else(case_addresses[index], ends_with_if)
2✔
707

708
        for case_addr in reversed(range(len(case_addresses))):
2✔
709
            self.generator.convert_end_if(case_addresses[case_addr])
2✔
710

711
        return self.build_data(match_node)
2✔
712

713
    def visit_For(self, for_node: ast.For) -> GeneratorData:
2✔
714
        """
715
        Visitor of for statement node
716

717
        :param for_node: the python ast for node
718
        """
719
        self.visit_to_generate(for_node.iter)
2✔
720
        start_address = self.generator.convert_begin_for()
2✔
721

722
        if isinstance(for_node.target, tuple):
2✔
UNCOV
723
            for target in for_node.target:
×
UNCOV
724
                var_data = self.visit_to_map(target)
×
UNCOV
725
                self.generator.convert_store_variable(var_data.symbol_id)
×
726
        else:
727
            var_data = self.visit(for_node.target)
2✔
728
            self.generator.convert_store_variable(var_data.symbol_id)
2✔
729

730
        for stmt in for_node.body:
2✔
731
            self.visit_to_map(stmt, generate=True)
2✔
732

733
        # TODO: remove when optimizing for generation #864e6me36
734
        if self.current_method is not None:
2✔
735
            self.current_method.remove_instruction(for_node.lineno, for_node.col_offset)
2✔
736

737
        condition_address = self.generator.convert_end_for(start_address)
2✔
738
        self.include_instruction(for_node, condition_address)
2✔
739
        else_begin = self.generator.last_code_start_address
2✔
740

741
        for stmt in for_node.orelse:
2✔
742
            self.visit_to_map(stmt, generate=True)
2✔
743

744
        self.generator.convert_end_loop_else(start_address,
2✔
745
                                             else_begin,
746
                                             has_else=len(for_node.orelse) > 0,
747
                                             is_for=True)
748
        return self.build_data(for_node)
2✔
749

750
    def visit_If(self, if_node: ast.If) -> GeneratorData:
2✔
751
        """
752
        Visitor of if statement node
753

754
        :param if_node: the python ast if statement node
755
        """
756
        test = self.visit_to_map(if_node.test, generate=True)
2✔
757

758
        if not Type.bool.is_type_of(test.type) and test.type is not None:
2✔
759
            self.generator.convert_builtin_method_call(Builtin.Bool)
2✔
760

761
        start_addr: int = self.generator.convert_begin_if()
2✔
762
        for stmt in if_node.body:
2✔
763
            self.visit_to_map(stmt, generate=True)
2✔
764

765
        ends_with_if = len(if_node.body) > 0 and isinstance(if_node.body[-1], ast.If)
2✔
766

767
        if len(if_node.orelse) > 0:
2✔
768
            start_addr = self.generator.convert_begin_else(start_addr, ends_with_if)
2✔
769
            for stmt in if_node.orelse:
2✔
770
                self.visit_to_map(stmt, generate=True)
2✔
771

772
        self.generator.convert_end_if(start_addr)
2✔
773
        return self.build_data(if_node)
2✔
774

775
    def visit_Expr(self, expr: ast.Expr, generate: bool = False) -> GeneratorData:
2✔
776
        """
777
        Visitor of an expression node
778

779
        :param expr: the python ast expression node
780
        :param generate: if it should convert the value
781
        """
782
        last_stack = self.generator.stack_size
2✔
783
        if generate:
2✔
784
            value = self.visit_to_generate(expr.value)
2✔
785
        else:
786
            value = self.visit(expr.value)
2✔
787

788
        new_stack = self.generator.stack_size
2✔
789
        for x in range(last_stack, new_stack):
2✔
790
            self.generator.remove_stack_top_item()
2✔
791

792
        return value
2✔
793

794
    def visit_IfExp(self, if_node: ast.IfExp) -> GeneratorData:
2✔
795
        """
796
        Visitor of if expression node
797

798
        :param if_node: the python ast if statement node
799
        """
800
        self.visit_to_map(if_node.test, generate=True)
2✔
801

802
        start_addr: int = self.generator.convert_begin_if()
2✔
803
        body_data = self.visit_to_map(if_node.body, generate=True)
2✔
804

805
        start_addr = self.generator.convert_begin_else(start_addr)
2✔
806
        else_data = self.visit_to_map(if_node.orelse, generate=True)
2✔
807

808
        self.generator.convert_end_if(start_addr)
2✔
809
        return self.build_data(if_node, result_type=Type.union.build([body_data.type, else_data.type]))
2✔
810

811
    def visit_Assert(self, assert_node: ast.Assert) -> GeneratorData:
2✔
812
        """
813
        Visitor of the assert node
814

815
        :param assert_node: the python ast assert node
816
        """
817
        self.visit_to_generate(assert_node.test)
2✔
818

819
        if assert_node.msg is not None:
2✔
820
            self.visit_to_generate(assert_node.msg)
2✔
821

822
        self.generator.convert_assert(has_message=assert_node.msg is not None)
2✔
823
        return self.build_data(assert_node)
2✔
824

825
    def visit_Call(self, call: ast.Call) -> GeneratorData:
2✔
826
        """
827
        Visitor of a function call node
828

829
        :param call: the python ast function call node
830
        :returns: The called function return type
831
        """
832
        # the parameters are included into the stack in the reversed order
833
        last_address = VMCodeMapping.instance().bytecode_size
2✔
834
        last_stack = self.generator.stack_size
2✔
835

836
        func_data = self.visit(call.func)
2✔
837
        function_id = func_data.symbol_id
2✔
838
        # if the symbol is not a method, check if it is a class method
839
        if (isinstance(func_data.type, ClassType) and func_data.symbol_id in func_data.type.symbols
2✔
840
                and isinstance(func_data.type.symbols[func_data.symbol_id], IExpression)):
841
            symbol = func_data.type.symbols[func_data.symbol_id]
2✔
842
        else:
843
            symbol = func_data.symbol
2✔
844

845
        if not isinstance(symbol, Method):
2✔
846
            is_internal = hasattr(call, 'is_internal_call') and call.is_internal_call
2✔
847
            _, symbol = self.generator.get_symbol(function_id, is_internal=is_internal)
2✔
848

849
        if self.is_implemented_class_type(symbol):
2✔
850
            self.generator.convert_init_user_class(symbol)
2✔
851
            symbol = symbol.constructor_method()
2✔
852
        args_addresses: List[int] = []
2✔
853

854
        has_cls_or_self_argument = isinstance(symbol, Method) and symbol.has_cls_or_self
2✔
855
        if not has_cls_or_self_argument:
2✔
856
            self._remove_inserted_opcodes_since(last_address, last_stack)
2✔
857

858
        if isinstance(symbol, Method):
2✔
859
            # self or cls is already generated
860
            call_args = (call.args[1:]
2✔
861
                         if has_cls_or_self_argument and len(call.args) == len(symbol.args)
862
                         else call.args)
863
            args_to_generate = [arg for index, arg in enumerate(call_args) if index in symbol.args_to_be_generated()]
2✔
864
            keywords_dict = {keyword.arg: keyword.value for keyword in call.keywords}
2✔
865
            keywords_with_index = {index: keywords_dict[arg_name]
2✔
866
                                   for index, arg_name in enumerate(symbol.args)
867
                                   if arg_name in keywords_dict}
868

869
            for index in keywords_with_index:
2✔
870
                if index < len(args_to_generate):
2✔
871
                    # override default values
872
                    args_to_generate[index] = keywords_with_index[index]
2✔
873
                else:
874
                    # put keywords
875
                    args_to_generate.append(keywords_with_index[index])
2✔
876

877
        else:
878
            args_to_generate = call.args
2✔
879

880
        if isinstance(symbol, IBuiltinMethod):
2✔
881
            reordered_args = []
2✔
882
            for index in symbol.generation_order:
2✔
883
                if 0 <= index < len(args_to_generate):
2✔
884
                    reordered_args.append(args_to_generate[index])
2✔
885

886
            args = reordered_args
2✔
887
        else:
888
            args = reversed(args_to_generate)
2✔
889

890
        args_begin_address = self.generator.last_code_start_address
2✔
891
        for arg in args:
2✔
892
            args_addresses.append(
2✔
893
                VMCodeMapping.instance().bytecode_size
894
            )
895
            self.visit_to_generate(arg)
2✔
896

897
        class_type = None
2✔
898
        if has_cls_or_self_argument:
2✔
899
            num_args = len(args_addresses)
2✔
900
            if self.generator.stack_size > num_args:
2✔
901
                value = self.generator._stack_pop(-num_args - 1)
2✔
902
                self.generator._stack_append(value)
2✔
903
                class_type = value if isinstance(value, UserClass) else None
2✔
904
            end_address = VMCodeMapping.instance().move_to_end(last_address, args_begin_address)
2✔
905
            if not symbol.is_init:
2✔
906
                args_addresses.append(end_address)
2✔
907

908
        if self.is_exception_name(function_id):
2✔
909
            self.generator.convert_new_exception(len(call.args))
2✔
910
        elif isinstance(symbol, type(Builtin.Super)) and len(args_to_generate) == 0:
2✔
911
            self_or_cls_id = list(self.current_method.args)[0]
2✔
912
            self.generator.convert_load_symbol(self_or_cls_id)
2✔
913
        elif isinstance(symbol, IBuiltinMethod):
2✔
914
            self.generator.convert_builtin_method_call(symbol, args_addresses)
2✔
915
        else:
916
            self.generator.convert_load_symbol(function_id, args_addresses,
2✔
917
                                               class_type=class_type)
918

919
        return self.build_data(call, symbol=symbol, symbol_id=function_id,
2✔
920
                               result_type=symbol.type if isinstance(symbol, IExpression) else symbol,
921
                               already_generated=True)
922

923
    def visit_Raise(self, raise_node: ast.Raise) -> GeneratorData:
2✔
924
        """
925
        Visitor of the raise node
926

927
        :param raise_node: the python ast raise node
928
        """
929
        self.visit_to_map(raise_node.exc, generate=True)
2✔
930
        self.generator.convert_raise_exception()
2✔
931
        return self.build_data(raise_node)
2✔
932

933
    def visit_Try(self, try_node: ast.Try) -> GeneratorData:
2✔
934
        """
935
        Visitor of the try node
936

937
        :param try_node: the python ast try node
938
        """
939
        try_address: int = self.generator.convert_begin_try()
2✔
940
        try_end: Optional[int] = None
2✔
941
        for stmt in try_node.body:
2✔
942
            self.visit_to_map(stmt, generate=True)
2✔
943

944
        if len(try_node.handlers) == 1:
2✔
945
            handler = try_node.handlers[0]
2✔
946
            try_end = self.generator.convert_try_except(handler.name)
2✔
947
            for stmt in handler.body:
2✔
948
                self.visit_to_map(stmt, generate=True)
2✔
949

950
        else_address = None
2✔
951
        if len(try_node.orelse) > 0:
2✔
952
            else_start_address = self.generator.convert_begin_else(try_end)
2✔
953
            else_address = self.generator.bytecode_size
2✔
954
            for stmt in try_node.orelse:
2✔
955
                self.visit_to_map(stmt, generate=True)
2✔
956
            self.generator.convert_end_if(else_start_address)
2✔
957

958
        except_end = self.generator.convert_end_try(try_address, try_end, else_address)
2✔
959
        for stmt in try_node.finalbody:
2✔
960
            self.visit_to_map(stmt, generate=True)
2✔
961
        self.generator.convert_end_try_finally(except_end, try_address, len(try_node.finalbody) > 0)
2✔
962

963
        return self.build_data(try_node)
2✔
964

965
    def visit_Name(self, name_node: ast.Name) -> GeneratorData:
2✔
966
        """
967
        Visitor of a name node
968

969
        :param name_node: the python ast name identifier node
970
        :return: the identifier of the name
971
        """
972
        name = name_node.id
2✔
973
        if name not in self._symbols:
2✔
974
            hashed_name = self._get_unique_name(name, self._tree)
2✔
975
            if hashed_name not in self._symbols and hasattr(name_node, 'origin'):
2✔
976
                hashed_name = self._get_unique_name(name, name_node.origin)
2✔
977

978
            if hashed_name in self._symbols:
2✔
979
                name = hashed_name
2✔
980

981
        else:
982
            if name not in self.generator.symbol_table:
2✔
983
                symbol = self._symbols[name]
2✔
984
                for symbol_id, internal_symbol in self.generator.symbol_table.items():
2✔
985
                    if internal_symbol == symbol:
2✔
UNCOV
986
                        name = symbol_id
×
UNCOV
987
                        break
×
988

989
        return self.build_data(name_node, name)
2✔
990

991
    def visit_Starred(self, starred: ast.Starred) -> GeneratorData:
2✔
992
        value_data = self.visit_to_generate(starred.value)
2✔
993
        self.generator.convert_starred_variable()
2✔
994

995
        starred_data = value_data.copy(starred)
2✔
996
        starred_data.already_generated = True
2✔
997
        return starred_data
2✔
998

999
    def visit_Attribute(self, attribute: ast.Attribute) -> GeneratorData:
2✔
1000
        """
1001
        Visitor of a attribute node
1002

1003
        :param attribute: the python ast attribute node
1004
        :return: the identifier of the attribute
1005
        """
1006
        last_address = VMCodeMapping.instance().bytecode_size
2✔
1007
        last_stack = self.generator.stack_size
2✔
1008

1009
        _attr, attr = self.generator.get_symbol(attribute.attr)
2✔
1010
        value = attribute.value
2✔
1011
        value_symbol = None
2✔
1012
        value_type = None
2✔
1013
        value_data = self.visit(value)
2✔
1014
        need_to_visit_again = True
2✔
1015

1016
        if value_data.symbol_id is not None and not value_data.already_generated:
2✔
1017
            value_id = value_data.symbol_id
2✔
1018
            if value_data.symbol is not None:
2✔
1019
                value_symbol = value_data.symbol
2✔
1020
            else:
1021
                _, value_symbol = self.generator.get_symbol(value_id)
2✔
1022
            value_type = value_symbol.type if hasattr(value_symbol, 'type') else value_symbol
2✔
1023

1024
            if hasattr(value_type, 'symbols') and attribute.attr in value_type.symbols:
2✔
1025
                attr = value_type.symbols[attribute.attr]
2✔
1026
            elif isinstance(value_type, Package) and attribute.attr in value_type.inner_packages:
2✔
1027
                attr = value_type.inner_packages[attribute.attr]
2✔
1028

1029
            if isinstance(value_symbol, UserClass):
2✔
1030
                if isinstance(attr, Method) and attr.has_cls_or_self:
2✔
1031
                    self.generator.convert_load_symbol(value_id)
2✔
1032
                    need_to_visit_again = False
2✔
1033

1034
                if isinstance(attr, Variable):
2✔
1035
                    cur_bytesize = self.generator.bytecode_size
2✔
1036
                    self.visit_to_generate(attribute.value)
2✔
1037
                    self.generator.convert_load_symbol(attribute.attr, class_type=value_symbol)
2✔
1038

1039
                    symbol_id = _attr if isinstance(_attr, str) else None
2✔
1040
                    return self.build_data(attribute,
2✔
1041
                                           already_generated=self.generator.bytecode_size > cur_bytesize,
1042
                                           symbol=attr, symbol_id=symbol_id,
1043
                                           origin_object_type=value_symbol)
1044
        else:
1045
            if isinstance(value, ast.Attribute) and value_data.already_generated:
2✔
1046
                need_to_visit_again = False
2✔
1047
            else:
1048
                need_to_visit_again = value_data.already_generated
2✔
1049
                self._remove_inserted_opcodes_since(last_address, last_stack)
2✔
1050

1051
        # the verification above only verify variables, this one will should work with literals and constants
1052
        if isinstance(value, ast.Constant) and len(attr.args) > 0 and isinstance(attr, IBuiltinMethod) and attr.has_self_argument:
2✔
UNCOV
1053
            attr = attr.build(value_data.type)
×
1054

1055
        if attr is not Type.none and not hasattr(attribute, 'generate_value'):
2✔
1056
            value_symbol_id = (value_symbol.identifier
2✔
1057
                               if self.is_implemented_class_type(value_symbol)
1058
                               else value_data.symbol_id)
1059
            attribute_id = f'{value_symbol_id}{constants.ATTRIBUTE_NAME_SEPARATOR}{attribute.attr}'
2✔
1060
            index = value_type if isinstance(value_type, Package) else None
2✔
1061
            return self.build_data(attribute, symbol_id=attribute_id, symbol=attr, index=index)
2✔
1062

1063
        if isinstance(value, ast.Attribute) and need_to_visit_again:
2✔
1064
            value_data = self.visit(value)
2✔
1065
        elif hasattr(attribute, 'generate_value') and attribute.generate_value:
2✔
1066
            current_bytecode_size = self.generator.bytecode_size
2✔
1067
            if need_to_visit_again:
2✔
1068
                value_data = self.visit_to_generate(attribute.value)
2✔
1069

1070
            result = value_data.type
2✔
1071
            generation_result = value_data.symbol
2✔
1072
            if result is None and value_data.symbol_id is not None:
2✔
1073
                _, result = self.generator.get_symbol(value_data.symbol_id)
2✔
1074
                if isinstance(result, IExpression):
2✔
UNCOV
1075
                    generation_result = result
×
1076
                    result = result.type
×
1077
            elif isinstance(attribute.value, ast.Attribute) and isinstance(value_data.index, int):
2✔
1078
                result = self.get_type(generation_result)
2✔
1079

1080
            if self.is_implemented_class_type(result):
2✔
1081
                class_attr_id = f'{result.identifier}.{attribute.attr}'
2✔
1082
                symbol_id = class_attr_id
2✔
1083
                symbol = None
2✔
1084
                result_type = None
2✔
1085
                symbol_index = None
2✔
1086
                is_load_context_variable_from_class = (isinstance(attribute.ctx, ast.Load) and
2✔
1087
                                                       isinstance(attr, Variable) and
1088
                                                       isinstance(result, UserClass))
1089

1090
                if self.generator.bytecode_size > current_bytecode_size and isinstance(result, UserClass) and not is_load_context_variable_from_class:
2✔
1091
                    # it was generated already, don't convert again
1092
                    generated = False
2✔
1093
                    symbol_id = attribute.attr if isinstance(generation_result, Variable) else class_attr_id
2✔
1094
                    result_type = result
2✔
1095
                else:
1096
                    current_bytecode_size = self.generator.bytecode_size
2✔
1097
                    index = self.generator.convert_class_symbol(result,
2✔
1098
                                                                attribute.attr,
1099
                                                                isinstance(attribute.ctx, ast.Load))
1100
                    generated = self.generator.bytecode_size > current_bytecode_size
2✔
1101
                    symbol = result
2✔
1102
                    if not isinstance(result, UserClass):
2✔
1103
                        if isinstance(index, int):
2✔
1104
                            symbol_index = index
2✔
1105
                        else:
1106
                            symbol_id = index
2✔
1107

1108
                return self.build_data(attribute,
2✔
1109
                                       symbol_id=symbol_id, symbol=symbol,
1110
                                       result_type=result_type, index=symbol_index,
1111
                                       origin_object_type=value_type,
1112
                                       already_generated=generated)
1113

1114
        if value_data is not None and value_symbol is None:
2✔
1115
            value_symbol = value_data.symbol_id
×
1116

1117
        if value_data is not None and value_data.symbol_id is not None:
2✔
1118
            value_id = f'{value_data.symbol_id}{constants.ATTRIBUTE_NAME_SEPARATOR}{attribute.attr}'
2✔
1119
        else:
UNCOV
1120
            value_id = attribute.attr
×
1121

1122
        return self.build_data(attribute, symbol_id=value_id, symbol=value_symbol)
2✔
1123

1124
    def visit_Continue(self, continue_node: ast.Continue) -> GeneratorData:
2✔
1125
        """
1126
        :param continue_node: the python ast continue statement node
1127
        """
1128
        self.generator.convert_loop_continue()
2✔
1129
        return self.build_data(continue_node)
2✔
1130

1131
    def visit_Break(self, break_node: ast.Break) -> GeneratorData:
2✔
1132
        """
1133
        :param break_node: the python ast break statement node
1134
        """
1135
        self.generator.convert_loop_break()
2✔
1136
        return self.build_data(break_node)
2✔
1137

1138
    def visit_Constant(self, constant: ast.Constant) -> GeneratorData:
2✔
1139
        """
1140
        Visitor of constant values node
1141

1142
        :param constant: the python ast constant value node
1143
        """
1144
        index = self.generator.convert_literal(constant.value)
2✔
1145
        result_type = self.get_type(constant.value)
2✔
1146
        return self.build_data(constant, result_type=result_type, index=index, already_generated=True)
2✔
1147

1148
    def visit_NameConstant(self, constant: ast.NameConstant) -> GeneratorData:
2✔
1149
        """
1150
        Visitor of constant names node
1151

1152
        :param constant: the python ast name constant node
1153
        """
UNCOV
1154
        index = self.generator.convert_literal(constant.value)
×
UNCOV
1155
        result_type = self.get_type(constant.value)
×
UNCOV
1156
        return self.build_data(constant, result_type=result_type, index=index, already_generated=True)
×
1157

1158
    def visit_Num(self, num: ast.Num) -> GeneratorData:
2✔
1159
        """
1160
        Visitor of literal number node
1161

1162
        :param num: the python ast number node
1163
        """
UNCOV
1164
        index = self.generator.convert_literal(num.n)
×
UNCOV
1165
        result_type = self.get_type(num.n)
×
UNCOV
1166
        return self.build_data(num, result_type=result_type, index=index, already_generated=True)
×
1167

1168
    def visit_Str(self, string: ast.Str) -> GeneratorData:
2✔
1169
        """
1170
        Visitor of literal string node
1171

1172
        :param string: the python ast string node
1173
        """
UNCOV
1174
        index = self.generator.convert_literal(string.s)
×
UNCOV
1175
        result_type = self.get_type(string.s)
×
UNCOV
1176
        return self.build_data(string, result_type=result_type, index=index, already_generated=True)
×
1177

1178
    def visit_Bytes(self, bts: ast.Bytes) -> GeneratorData:
2✔
1179
        """
1180
        Visitor of literal bytes node
1181

1182
        :param bts: the python ast bytes node
1183
        """
UNCOV
1184
        index = self.generator.convert_literal(bts.s)
×
UNCOV
1185
        result_type = self.get_type(bts.s)
×
UNCOV
1186
        return self.build_data(bts, result_type=result_type, index=index, already_generated=True)
×
1187

1188
    def visit_Tuple(self, tup_node: ast.Tuple) -> GeneratorData:
2✔
1189
        """
1190
        Visitor of literal tuple node
1191

1192
        :param tup_node: the python ast tuple node
1193
        """
1194
        result_type = Type.tuple
2✔
1195
        self._create_array(tup_node.elts, result_type)
2✔
1196
        return self.build_data(tup_node, result_type=result_type, already_generated=True)
2✔
1197

1198
    def visit_List(self, list_node: ast.List) -> GeneratorData:
2✔
1199
        """
1200
        Visitor of literal list node
1201

1202
        :param list_node: the python ast list node
1203
        """
1204
        result_type = Type.list
2✔
1205
        self._create_array(list_node.elts, result_type)
2✔
1206
        return self.build_data(list_node, result_type=result_type, already_generated=True)
2✔
1207

1208
    def visit_Dict(self, dict_node: ast.Dict) -> GeneratorData:
2✔
1209
        """
1210
        Visitor of literal dict node
1211

1212
        :param dict_node: the python ast dict node
1213
        """
1214
        result_type = Type.dict
2✔
1215
        length = min(len(dict_node.keys), len(dict_node.values))
2✔
1216
        self.generator.convert_new_map(result_type)
2✔
1217
        for key_value in range(length):
2✔
1218
            self.generator.duplicate_stack_top_item()
2✔
1219
            self.visit_to_generate(dict_node.keys[key_value])
2✔
1220
            value_data = self.visit_to_generate(dict_node.values[key_value])
2✔
1221
            self.generator.convert_set_item(value_data.index)
2✔
1222

1223
        return self.build_data(dict_node, result_type=result_type, already_generated=True)
2✔
1224

1225
    def visit_JoinedStr(self, fstring: ast.JoinedStr) -> GeneratorData:
2✔
1226
        """
1227
        Visitor of an f-string node
1228

1229
        :param string: the python ast string node
1230
        """
1231
        result_type = Type.str
2✔
1232
        index = self.generator.bytecode_size
2✔
1233
        for index, value in enumerate(fstring.values):
2✔
1234
            self.visit_to_generate(value)
2✔
1235
            if index != 0:
2✔
1236
                self.generator.convert_operation(BinaryOp.Concat)
2✔
1237
                if index < len(fstring.values) - 1:
2✔
NEW
1238
                    self._remove_inserted_opcodes_since(self.generator.last_code_start_address)
×
1239

1240
        return self.build_data(fstring, result_type=result_type, index=index, already_generated=True)
2✔
1241

1242
    def visit_FormattedValue(self, formatted_value: ast.FormattedValue) -> GeneratorData:
2✔
1243
        """
1244
        Visitor of a formatted_value node
1245

1246
        :param formatted_value: the python ast string node
1247
        """
1248
        generated_value = self.visit_to_generate(formatted_value.value)
2✔
1249
        if generated_value.type == Type.str:
2✔
1250
            return generated_value
2✔
1251

1252
        else:
1253
            match generated_value.type:
2✔
1254
                case Type.bool:
2✔
1255
                    self.generator.convert_builtin_method_call(Builtin.StrBool)
2✔
1256
                case Type.int:
2✔
1257
                    self.generator.convert_builtin_method_call(Builtin.StrInt)
2✔
1258
                case Type.bytes:
2✔
1259
                    self.generator.convert_builtin_method_call(Builtin.StrBytes)
2✔
1260
                case x if Type.sequence.is_type_of(x):
2✔
1261
                    self.generator.convert_builtin_method_call(Builtin.StrSequence)
2✔
1262
                case x if isinstance(x, UserClass):
2✔
1263
                    self.generator.convert_builtin_method_call(Builtin.StrClass.build(x))
2✔
1264

1265
            return self.build_data(formatted_value, result_type=Type.str, already_generated=True)
2✔
1266

1267
    def visit_Pass(self, pass_node: ast.Pass) -> GeneratorData:
2✔
1268
        """
1269
        Visitor of pass node
1270

1271
        :param pass_node: the python ast dict node
1272
        """
1273
        # only generates if the scope is a function
1274
        result_type = None
2✔
1275
        generated = False
2✔
1276

1277
        if isinstance(self.current_method, Method):
2✔
1278
            self.generator.insert_nop()
2✔
1279
            generated = True
2✔
1280

1281
        return self.build_data(pass_node, result_type=result_type, already_generated=generated)
2✔
1282

1283
    def _create_array(self, values: List[ast.AST], array_type: IType):
2✔
1284
        """
1285
        Creates a new array from a literal sequence
1286

1287
        :param values: list of values of the new array items
1288
        """
1289
        length = len(values)
2✔
1290
        if length > 0:
2✔
1291
            for value in reversed(values):
2✔
1292
                self.visit_to_generate(value)
2✔
1293
        self.generator.convert_new_array(length, array_type)
2✔
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