Skip to content

Commit 5c170e7

Browse files
authored
Remove the FunctionDef, Imports and Classes from CodeBlocks (pyccel#1812)
Remove instances of `FunctionDef`, `Import` and `ClassDef` from `CodeBlock`s. They were previously collected here in the syntactic stage. Fixes pyccel#1768 . Use this change to remove the argument `annotate` from `_visit_FunctionDef`. In order to correctly group the imports, move the recognition of a `if __name__ == '__main__'` statement to the syntactic stage. This allows the code to be simplified. An error is now raised if multiple blocks like this are found and the program is then handled in `_visit_Module` in the semantic stage.
1 parent 1a9104d commit 5c170e7

File tree

7 files changed

+215
-202
lines changed

7 files changed

+215
-202
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,12 @@ All notable changes to this project will be documented in this file.
4040
- \[INTERNALS\] Use cached `__and__` method to determine result type of bitwise comparison operations.
4141
- \[INTERNALS\] Removed unused `fcode`, `ccode`, `cwrappercode`, `luacode`, and `pycode` functions from printers.
4242
- \[INTERNALS\] Removed unused arguments from methods in `pyccel.codegen.codegen.Codegen`.
43+
- \[INTERNALS\] Stop storing `FunctionDef`, `ClassDef`, and `Import` objects inside `CodeBlock`s.
4344

4445
### Deprecated
4546

4647
- #1786 : Remove support for `real` and `integer` as type annotations.
48+
- #1812 : Stop allowing multiple main blocks inside a module.
4749
- \[INTERNALS\] Remove property `ast.basic.TypedAstNode.precision`.
4850
- \[INTERNALS\] Remove class `ast.datatypes.DataType` (replaced by `ast.datatypes.PrimitiveType` and `ast.datatypes.PyccelType`).
4951
- \[INTERNALS\] Remove unused properties `prefix` and `alias` from `CustomDataType`.

pyccel/ast/basic.py

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -407,15 +407,26 @@ def get_all_user_nodes(self):
407407
return self._user_nodes
408408

409409
def get_direct_user_nodes(self, condition):
410-
""" For an object with multiple user nodes
411-
Get the objects which satisfy a given
412-
condition
410+
"""
411+
Get the direct user nodes which satisfy the condition.
412+
413+
This function returns all the direct user nodes which satisfy the
414+
provided condition. A "direct" user node is a node which uses the
415+
instance directly (e.g. a `FunctionCall` uses a `FunctionDef` directly
416+
while a `FunctionDef` uses a `Variable` indirectly via a `FunctionDefArgument`
417+
or a `CodeBlock`). Most objects only have 1 direct user node so
418+
this function only makes sense for an object with multiple user nodes.
419+
E.g. a `Variable`, or a `FunctionDef`.
413420
414421
Parameters
415422
----------
416423
condition : lambda
417-
The condition which the user nodes
418-
must satisfy to be returned
424+
The condition which the user nodes must satisfy to be returned.
425+
426+
Returns
427+
-------
428+
list
429+
The user nodes which satisfy the condition.
419430
"""
420431
return [p for p in self._user_nodes if condition(p)]
421432

pyccel/ast/core.py

Lines changed: 38 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1064,8 +1064,8 @@ def __init__(
10641064
raise TypeError('Only a Interface instance is allowed.')
10651065

10661066
NoneType = type(None)
1067-
if not isinstance(init_func, (NoneType, FunctionDef)):
1068-
raise TypeError('init_func must be a FunctionDef')
1067+
assert (pyccel_stage == 'syntactic' and isinstance(init_func, CodeBlock)) or \
1068+
isinstance(init_func, (NoneType, FunctionDef))
10691069

10701070
if not isinstance(free_func, (NoneType, FunctionDef)):
10711071
raise TypeError('free_func must be a FunctionDef')
@@ -1092,21 +1092,22 @@ def __init__(
10921092
self._classes = classes
10931093
self._imports = imports
10941094

1095-
self._internal_dictionary = {v.name:v for v in variables}
1096-
self._internal_dictionary.update({f.name:f for f in funcs})
1097-
self._internal_dictionary.update({i.name:i for i in interfaces})
1098-
self._internal_dictionary.update({c.name:c for c in classes})
1099-
import_mods = {i.source: [t.object for t in i.target if isinstance(t.object, Module)] for i in imports}
1100-
self._internal_dictionary.update({v:t[0] for v,t in import_mods.items() if t})
1101-
1102-
if init_func:
1103-
init_if = init_func.body.body[0]
1104-
# The init function should always contain an If block unless it is part of a wrapper
1105-
if isinstance(init_if, If):
1106-
init_cond = init_if.blocks[0].condition
1107-
init_var = init_cond.args[0]
1108-
self._variables.append(init_var)
1109-
self._variable_inits.append(LiteralFalse())
1095+
if pyccel_stage != "syntactic":
1096+
self._internal_dictionary = {v.name:v for v in variables}
1097+
self._internal_dictionary.update({f.name:f for f in funcs})
1098+
self._internal_dictionary.update({i.name:i for i in interfaces})
1099+
self._internal_dictionary.update({c.name:c for c in classes})
1100+
import_mods = {i.source: [t.object for t in i.target if isinstance(t.object, Module)] for i in imports}
1101+
self._internal_dictionary.update({v:t[0] for v,t in import_mods.items() if t})
1102+
1103+
if init_func:
1104+
init_if = init_func.body.body[0]
1105+
# The init function should always contain an If block unless it is part of a wrapper
1106+
if isinstance(init_if, If):
1107+
init_cond = init_if.blocks[0].condition
1108+
init_var = init_cond.args[0]
1109+
self._variables.append(init_var)
1110+
self._variable_inits.append(LiteralFalse())
11101111

11111112
super().__init__(scope)
11121113

@@ -1278,20 +1279,29 @@ def module(self):
12781279
return self._module
12791280

12801281
class Program(ScopedAstNode):
1282+
"""
1283+
Represents a Program in the code.
12811284
1282-
"""Represents a Program in the code. A block consists of the following inputs
1285+
A class representing a program in the code. A program is a set of statements
1286+
that are executed when the module is run directly. In Python these statements
1287+
are located in an `if __name__ == '__main__':` block.
12831288
12841289
Parameters
12851290
----------
1286-
variables: list
1287-
list of the variables that appear in the block.
1291+
name : str
1292+
The name used to identify the program (this is used for printing in Fortran).
12881293
1289-
body: list
1290-
a list of statements
1294+
variables : Iterable[Variable]
1295+
An iterable object containing the variables that appear in the program.
1296+
1297+
body : CodeBlock
1298+
An CodeBlock containing the statements in the body of the program.
12911299
1292-
imports: list, tuple
1293-
list of needed imports
1300+
imports : Iterable[Import]
1301+
An iterable object containing the imports used by the program.
12941302
1303+
scope : Scope
1304+
The scope of the program.
12951305
"""
12961306
__slots__ = ('_name', '_variables', '_body', '_imports')
12971307
_attribute_nodes = ('_variables', '_body', '_imports')
@@ -1315,18 +1325,16 @@ def __init__(
13151325
if not isinstance(i, Variable):
13161326
raise TypeError('Only a Variable instance is allowed.')
13171327

1318-
if not iterable(body):
1319-
raise TypeError('body must be an iterable')
1320-
body = CodeBlock(body)
1328+
assert isinstance(body, CodeBlock)
13211329

13221330
if not iterable(imports):
13231331
raise TypeError('imports must be an iterable')
13241332

1325-
imports = set(imports) # for unicity
1326-
imports = tuple(imports)
1333+
imports = {i : None for i in imports} # for unicity and ordering
1334+
imports = tuple(imports.keys())
13271335

13281336
self._name = name
1329-
self._variables = variables
1337+
self._variables = tuple(variables)
13301338
self._body = body
13311339
self._imports = imports
13321340
super().__init__(scope)
@@ -4455,24 +4463,6 @@ def args_var(self):
44554463

44564464
# ...
44574465

4458-
class InProgram(TypedAstNode):
4459-
"""
4460-
Class representing the 'in program' test.
4461-
4462-
Class representing the test indicating whether the code should
4463-
only be executed when the file is executed as a program. In
4464-
other words, a class representing the boolean:
4465-
`__name__ == '__main__'`
4466-
"""
4467-
_rank = 0
4468-
_shape = None
4469-
_order = None
4470-
_class_type = PythonNativeBool()
4471-
_attribute_nodes = ()
4472-
__slots__ = ()
4473-
4474-
# ...
4475-
44764466
class Decorator(PyccelAstNode):
44774467
""" Class representing a function decorator.
44784468
For now this is just designed to handle the pyccel decorators

0 commit comments

Comments
 (0)