Skip to content

Commit 6c5b537

Browse files
said-hadjoutEmilyBourneyguclu
authored
Annotate functions when they are called (pyccel#1728)
Solve pyccel#1720: - Annotate `FunctionDef` when it is called, or at the end of the `CodeBlock` if it is never called. - Only annotate `InlinedFunctionDef` if it is called. - Never print `InlinedFunctionDef` except in the Python printer. - Increase error level to "fatal" when incompatible arguments are passed to a function marked as `@inline`. - Add support for `Ellipsis` as the only index for an array. --------- Co-authored-by: EmilyBourne <[email protected]> Co-authored-by: Yaman Güçlü <[email protected]>
1 parent 9183af9 commit 6c5b537

27 files changed

+944
-313
lines changed

CHANGELOG.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,21 @@ All notable changes to this project will be documented in this file.
44
## \[UNRELEASED\]
55

66
### Added
7-
8-
- #1694 : Add Python support for list method `extend()`.
7+
- #1720 : Add support for `Ellipsis` as the only index for an array.
8+
- #1694 : Add Python support for list method `extend()`.
99
- #1739 : Add Python support for set method `clear()`.
1010
- #1739 : Add abstract class `SetMethod` to handle calls to various set methods.
1111
- #1740 : Add Python support for set method `copy()`.
1212

1313
### Fixed
1414

15+
- #1720 : Fix Undefined Variable error when the function definition is after the variable declaration.
16+
1517
### Changed
18+
- #1720 : functions with the `@inline` decorator are no longer exposed to Python in the shared library.
19+
- #1720 : Error raised when incompatible arguments are passed to an `inlined` function is now fatal.
20+
- \[INTERNALS\] `FunctionDef` is annotated when it is called, or at the end of the `CodeBlock` if it is never called.
21+
- \[INTERNALS\] `InlinedFunctionDef` is only annotated if it is called.
1622

1723
### Deprecated
1824

docs/decorators.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -364,6 +364,7 @@ end module boo
364364
## Inline
365365

366366
The `@inline` decorator indicates that the body of a function should be printed directly when it is called rather than passing through an additional function call. This can be useful for code optimisation.
367+
Any functions with the `@inline` decorator will not be exposed to the user in the shared library.
367368

368369
### Basic Example
369370

@@ -598,6 +599,14 @@ The generated C code:
598599
```c
599600
```
600601

602+
### Import Error when imported from the shared library
603+
Using the previous example, if we import the function `get_val`, we get this error:
604+
```
605+
Traceback (most recent call last):
606+
File "<string>", line 1, in <module>
607+
ImportError: cannot import name 'get_val' from 'boo' (/home/__init__.py)
608+
```
609+
601610
## Getting Help
602611

603612
If you face problems with Pyccel, please take the following steps:

pyccel/ast/basic.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
AST nodes requiring type descriptors.
1111
"""
1212
import ast
13+
from types import GeneratorType
1314

1415
from pyccel.utilities.stage import PyccelStage
1516

@@ -35,7 +36,7 @@ def iterable(x):
3536
bool
3637
True if object is iterable for a PyccelAstNode.
3738
"""
38-
return isinstance(x, (list, tuple, dict_keys, dict_values, set))
39+
return isinstance(x, (list, tuple, dict_keys, dict_values, set, GeneratorType))
3940

4041
pyccel_stage = PyccelStage()
4142

pyccel/ast/core.py

Lines changed: 167 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2277,6 +2277,14 @@ class FunctionDef(ScopedAstNode):
22772277
is_external : bool
22782278
True for a function which cannot be explicitly imported or renamed.
22792279
2280+
is_imported : bool, default : False
2281+
True for a function that is imported.
2282+
2283+
is_semantic : bool, optional
2284+
True for a function that is annotated.
2285+
It is used to indicate if the function has been visited in the semantic stage or not.
2286+
It is only used by the clone method, where we might clone a syntactic function in the semantic stage.
2287+
22802288
functions : list, tuple
22812289
A list of functions defined within this function.
22822290
@@ -2331,7 +2339,8 @@ class FunctionDef(ScopedAstNode):
23312339
'_decorators','_headers','_is_recursive','_is_pure',
23322340
'_is_elemental','_is_private','_is_header',
23332341
'_functions','_interfaces','_docstring', '_is_external',
2334-
'_result_pointer_map')
2342+
'_result_pointer_map','_is_imported', '_is_semantic')
2343+
23352344
_attribute_nodes = ('_arguments','_results','_body',
23362345
'_global_vars','_imports','_functions','_interfaces')
23372346

@@ -2353,6 +2362,8 @@ def __init__(
23532362
is_private=False,
23542363
is_header=False,
23552364
is_external=False,
2365+
is_imported=False,
2366+
is_semantic=None,
23562367
functions=(),
23572368
interfaces=(),
23582369
result_pointer_map={},
@@ -2448,11 +2459,13 @@ def __init__(
24482459
self._is_private = is_private
24492460
self._is_header = is_header
24502461
self._is_external = is_external
2462+
self._is_imported = is_imported
24512463
self._functions = functions
24522464
self._interfaces = interfaces
24532465
self._result_pointer_map = result_pointer_map
24542466
self._docstring = docstring
24552467
super().__init__(scope)
2468+
self._is_semantic = self.pyccel_staging != 'syntactic' if is_semantic is None else is_semantic
24562469

24572470
@property
24582471
def name(self):
@@ -2596,6 +2609,15 @@ def is_external(self):
25962609
from a f77 module """
25972610
return self._is_external
25982611

2612+
@property
2613+
def is_imported(self):
2614+
"""
2615+
Indicates if the function was imported from another file.
2616+
2617+
Indicates if the function was imported from another file.
2618+
"""
2619+
return self._is_imported
2620+
25992621
@property
26002622
def is_inline(self):
26012623
""" True if the function should be printed inline """
@@ -2610,6 +2632,16 @@ def is_static(self):
26102632
"""
26112633
return self._is_static
26122634

2635+
@property
2636+
def is_semantic(self):
2637+
"""
2638+
Indicates if the function was created with semantic information.
2639+
2640+
Indicates if the function has been annotated with type descriptors
2641+
in the semantic stage.
2642+
"""
2643+
return self._is_semantic
2644+
26132645
@property
26142646
def functions(self):
26152647
""" List of functions within this function """
@@ -2661,7 +2693,6 @@ def clone(self, newname, **new_kwargs):
26612693
new_func.rename(newname)
26622694
return new_func
26632695

2664-
26652696
def rename(self, newname):
26662697
"""
26672698
Rename the FunctionDef name
@@ -2700,6 +2731,8 @@ def __getnewargs__(self):
27002731
'is_header':self._is_header,
27012732
'functions':self._functions,
27022733
'is_external':self._is_external,
2734+
'is_imported':self._is_imported,
2735+
'is_semantic':self._is_semantic,
27032736
'interfaces':self._interfaces,
27042737
'docstring':self._docstring,
27052738
'scope':self._scope}
@@ -2756,19 +2789,27 @@ class InlineFunctionDef(FunctionDef):
27562789
"""
27572790
Represents a function definition for an inline function.
27582791
2792+
Represents a function definition for an inline function.
2793+
27592794
Parameters
27602795
----------
2761-
See FunctionDef
2762-
2763-
namespace_imports : Scope
2764-
The objects in the scope which are available due to imports
2796+
*args : list
2797+
The FunctionDef class arguments.
2798+
namespace_imports : dict
2799+
The imports available in the function Scope.
2800+
global_funcs : iterable, optional
2801+
The global functions used in the function.
2802+
**kwargs : dict
2803+
The FunctionDef class keyword arguments.
27652804
"""
27662805
__slots__ = ('_namespace_imports','_orig_args','_new_args','_new_local_vars', '_if_block_replacements',
27672806
'_global_funcs')
27682807

27692808
def __init__(self, *args, namespace_imports = None, global_funcs = None, **kwargs):
2809+
if namespace_imports is not None:
2810+
assert isinstance(namespace_imports, dict)
27702811
self._namespace_imports = namespace_imports
2771-
self._global_funcs = global_funcs
2812+
self._global_funcs = tuple(global_funcs) if global_funcs is not None else None
27722813
super().__init__(*args, **kwargs)
27732814
self._orig_args = tuple(a.var for a in self.arguments)
27742815
self._new_args = None
@@ -2861,6 +2902,15 @@ def global_funcs(self):
28612902
""" List of global functions used in the function """
28622903
return self._global_funcs
28632904

2905+
def __getnewargs__(self):
2906+
"""
2907+
This method returns the positional and keyword arguments
2908+
used to create an instance of this class.
2909+
"""
2910+
args, kwargs = super().__getnewargs__()
2911+
kwargs.update({'namespace_imports':self._namespace_imports, 'global_funcs':self._global_funcs})
2912+
return args, kwargs
2913+
28642914
class PyccelFunctionDef(FunctionDef):
28652915
"""
28662916
Class used for storing `PyccelInternalFunction` objects in a FunctionDef.
@@ -2930,29 +2980,40 @@ class Interface(PyccelAstNode):
29302980
is_argument : bool
29312981
True if the interface is used for a function argument.
29322982
2983+
is_imported : bool
2984+
True if the interface is imported from another file.
2985+
2986+
syntactic_node : FunctionDef, default: None
2987+
The syntactic node that is not annotated.
2988+
29332989
Examples
29342990
--------
29352991
>>> from pyccel.ast.core import Interface, FunctionDef
29362992
>>> f = FunctionDef('F', [], [], [])
29372993
>>> Interface('I', [f])
29382994
"""
2939-
__slots__ = ('_name','_functions','_is_argument')
2995+
__slots__ = ('_name','_functions','_is_argument', '_is_imported', '_syntactic_node')
29402996
_attribute_nodes = ('_functions',)
29412997

29422998
def __init__(
29432999
self,
29443000
name,
29453001
functions,
29463002
is_argument = False,
3003+
is_imported=False,
3004+
syntactic_node=None,
29473005
):
29483006

29493007
if not isinstance(name, str):
29503008
raise TypeError('Expecting an str')
2951-
if not isinstance(functions, list):
2952-
raise TypeError('Expecting a list')
3009+
3010+
assert iterable(functions)
3011+
29533012
self._name = name
2954-
self._functions = functions
3013+
self._functions = tuple(functions)
29553014
self._is_argument = is_argument
3015+
self._is_imported = is_imported
3016+
self._syntactic_node = syntactic_node
29563017
super().__init__()
29573018

29583019
@property
@@ -2970,6 +3031,24 @@ def is_argument(self):
29703031
"""True if the interface is used for a function argument."""
29713032
return self._is_argument
29723033

3034+
@property
3035+
def is_imported(self):
3036+
"""
3037+
Indicates if the function was imported from another file.
3038+
3039+
Indicates if the function was imported from another file.
3040+
"""
3041+
return self._is_imported
3042+
3043+
@property
3044+
def syntactic_node(self):
3045+
"""
3046+
The syntactic node that is not annotated.
3047+
3048+
The syntactic node that is not annotated.
3049+
"""
3050+
return self._syntactic_node
3051+
29733052
@property
29743053
def docstring(self):
29753054
"""
@@ -2979,6 +3058,83 @@ def docstring(self):
29793058
"""
29803059
return self._functions[0].docstring
29813060

3061+
@property
3062+
def is_semantic(self):
3063+
"""
3064+
Flag to check if the node is annotated.
3065+
3066+
Flag to check if the node has been annotated with type descriptors
3067+
in the semantic stage.
3068+
"""
3069+
return self._functions[0].is_semantic
3070+
3071+
@property
3072+
def is_inline(self):
3073+
"""
3074+
Flag to check if the node is inlined.
3075+
3076+
Flag to check if the node is inlined.
3077+
"""
3078+
return self._functions[0].is_inline
3079+
3080+
def rename(self, newname):
3081+
"""
3082+
Rename the Interface name to a newname.
3083+
3084+
Rename the Interface name to a newname.
3085+
3086+
Parameters
3087+
----------
3088+
newname : str
3089+
New name for the Interface.
3090+
"""
3091+
3092+
self._name = newname
3093+
3094+
def clone(self, newname, **new_kwargs):
3095+
"""
3096+
Create an almost identical Interface with name `newname`.
3097+
3098+
Create an almost identical Interface with name `newname`.
3099+
Additional parameters can be passed to alter the resulting
3100+
FunctionDef.
3101+
3102+
Parameters
3103+
----------
3104+
newname : str
3105+
New name for the Interface.
3106+
3107+
**new_kwargs : dict
3108+
Any new keyword arguments to be passed to the new Interface.
3109+
3110+
Returns
3111+
-------
3112+
Interface
3113+
The clone of the interface.
3114+
"""
3115+
3116+
args, kwargs = self.__getnewargs__()
3117+
kwargs.update(new_kwargs)
3118+
cls = type(self)
3119+
new_func = cls(*args, **kwargs)
3120+
new_func.rename(newname)
3121+
return new_func
3122+
3123+
def __getnewargs__(self):
3124+
"""
3125+
This method returns the positional and keyword arguments
3126+
used to create an instance of this class.
3127+
"""
3128+
args = (
3129+
self._name,
3130+
self._functions)
3131+
3132+
kwargs = {
3133+
'is_argument': self._is_argument,
3134+
'is_imported':self._is_imported,
3135+
'syntactic_node':self._syntactic_node}
3136+
return args, kwargs
3137+
29823138
def point(self, args, use_final_precision = False):
29833139
"""Returns the actual function that will be called, depending on the passed arguments."""
29843140
fs_args = [[j for j in i.arguments] for i in

0 commit comments

Comments
 (0)