Skip to content

Commit

Permalink
Remove pyccel.ast.utilities.builtin_function (#1841)
Browse files Browse the repository at this point in the history
The function `pyccel.ast.utilities.builtin_function` does the same thing
as the code here:

https://github.com/pyccel/pyccel/blob/78d56dec8303d728bc9cf43c55464fc42786645f/pyccel/parser/semantic.py#L1064-L1085

however it was not wrapped in a try/except and its implementation leaves
no room for specialised `_visit` functions if the class needs more
context information. This PR removes the function and uses the existing
implementation to retrieve the same behaviour. In order to be able to
wrap all builtin classes in a `PyccelFunctionDef` the inheritance is
changed so `PythonRange`, `PythonMap`, `PythonEnumerate` and
`PythonType` all inherit from `PyccelFunction`. As these objects do not
represent objects in memory in the low level code the class type
`SymbolicType()` is used.

`PyccelInternalFunction` is renamed to `PyccelFunction`

---------

Co-authored-by: Yaman Güçlü <yaman.guclu@gmail.com>
  • Loading branch information
EmilyBourne and yguclu committed Apr 19, 2024
1 parent 2728d40 commit 9464954
Show file tree
Hide file tree
Showing 19 changed files with 137 additions and 135 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Expand Up @@ -58,6 +58,9 @@ All notable changes to this project will be documented in this file.
- \[INTERNALS\] Ensure `SemanticParser.infer_type` returns all documented information.
- \[INTERNALS\] Enforce correct value for `pyccel_staging` property of `PyccelAstNode`.
- \[INTERNALS\] Allow visiting objects containing both syntactic and semantic elements in `SemanticParser`.
- \[INTERNALS\] Rename `pyccel.ast.internals.PyccelInternalFunction` to `pyccel.ast.internals.PyccelFunction`.
- \[INTERNALS\] All internal classes which can be generated from `FunctionCall`s must inherit from `PyccelFunction`.
- \[INTERNALS\] `PyccelFunction` objects which do not represent objects in memory have the type `SymbolicType`.

### Deprecated

Expand All @@ -69,6 +72,7 @@ All notable changes to this project will be documented in this file.
- \[INTERNALS\] Remove `ast.basic.TypedAstNode._dtype`. The datatype can still be accessed as it is contained within the class type.
- \[INTERNALS\] Removed unused and undocumented function `get_function_from_ast`.
- \[INTERNALS\] Remove unused parameters `expr`, `status` and `like` from `pyccel.ast.core.Assign`.
- \[INTERNALS\] Remove `pyccel.ast.utilities.builtin_functions`.

## \[1.11.2\] - 2024-03-05

Expand Down
8 changes: 4 additions & 4 deletions developer_docs/ast_nodes.md
Expand Up @@ -4,7 +4,7 @@ While translating from Python to the target language, Pyccel needs to store all

All objects in the Abstract Syntax Tree inherit from the class `pyccel.ast.basic.PyccelAstNode`. This class serves 2 roles. Firstly it provides a super class from which all our AST nodes can inherit which makes them easy to identify. Secondly it provides functionalities common to all AST nodes. For example it provides the `ast` property which allows the original code parsed by Python's `ast` module to be stored in the class. This object is important in order to report neat errors for code that cannot be handled by Pyccel. It also contains functions involving the relations between the nodes. These are explained in the section [Constructing a tree](#Constructing-a-tree).

The inheritance tree for a Python AST node is often more complicated than directly inheriting from `PyccelAstNode`. In particular there are two classes which you will see in the inheritance throughout the code. These classes are `TypedAstNode` and `PyccelInternalFunction`. These classes are explained in more detail below.
The inheritance tree for a Python AST node is often more complicated than directly inheriting from `PyccelAstNode`. In particular there are two classes which you will see in the inheritance throughout the code. These classes are `TypedAstNode` and `PyccelFunction`. These classes are explained in more detail below.

## Typed AST Node

Expand Down Expand Up @@ -56,11 +56,11 @@ The static type is the class type that would be assigned to an object created us

## Pyccel Internal Function

The class `pyccel.ast.internals.PyccelInternalFunction` is a super class. This class should never be used directly but provides functionalities which are common to certain AST objects. These AST nodes are those which describe functions which are supported by Pyccel. For example it is used for functions from the `math` library, the `cmath` library, the `numpy` library, etc. `PyccelInternalFunction` inherits from `TypedAstNode`. The type information for the sub-class describes the type of the result of the function.
The class `pyccel.ast.internals.PyccelFunction` is a super class. This class should never be used directly but provides functionalities which are common to certain AST objects. These AST nodes are those which describe functions which are supported by Pyccel. For example it is used for functions from the `math` library, the `cmath` library, the `numpy` library, etc. `PyccelFunction` inherits from `TypedAstNode`. The type information for the sub-class describes the type of the result of the function.

Instances of the subclasses of `PyccelInternalFunction` are created from a `FunctionCall` object resulting from the syntactic stage. The `PyccelInternalFunction` objects are initialised by passing the contents of the `FunctionCallArgument`s used in the `FunctionCall` to the constructor. The actual arguments may be either positional or keyword arguments, but the constructor of `PyccelInternalFunction` only accepts positional arguments through the variadic `*args`. It is therefore important that any subclasses of `PyccelInternalFunction` provide their own `__init__` with the correct function signature. I.e. a constructor whose positional and keyword arguments match the names of the positional and keyword arguments of the Python function being described.
Instances of the subclasses of `PyccelFunction` are created from a `FunctionCall` object resulting from the syntactic stage. The `PyccelFunction` objects are initialised by passing the contents of the `FunctionCallArgument`s used in the `FunctionCall` to the constructor. The actual arguments may be either positional or keyword arguments, but the constructor of `PyccelFunction` only accepts positional arguments through the variadic `*args`. It is therefore important that any subclasses of `PyccelFunction` provide their own `__init__` with the correct function signature. I.e. a constructor whose positional and keyword arguments match the names of the positional and keyword arguments of the Python function being described.

The constructor of `PyccelInternalFunction` takes a tuple of arguments passed to the function. The arguments can be passed through in the constructor of the sub-class to let `PyccelInternalFunction` take care of saving the arguments, or the arguments can be saved with more useful names inside the class. However care must be taken to ensure that the argument is not saved inside the class twice. Additionally if the arguments are saved inside the sub-class then the `_attribute_nodes` static class attribute must be correctly set to ensure the tree is correctly constructed (see below). As `PyccelInternalFunction` implements `_attribute_nodes` these classes are some of the only ones which will not raise an error during the pull request tests if this information is missing.
The constructor of `PyccelFunction` takes a tuple of arguments passed to the function. The arguments can be passed through in the constructor of the sub-class to let `PyccelFunction` take care of saving the arguments, or the arguments can be saved with more useful names inside the class. However care must be taken to ensure that the argument is not saved inside the class twice. Additionally if the arguments are saved inside the sub-class then the `_attribute_nodes` static class attribute must be correctly set to ensure the tree is correctly constructed (see below). As `PyccelFunction` implements `_attribute_nodes` these classes are some of the only ones which will not raise an error during the pull request tests if this information is missing.

## Constructing a tree

Expand Down
2 changes: 1 addition & 1 deletion developer_docs/how_to_solve_an_issue.md
Expand Up @@ -7,7 +7,7 @@ This file summarises basic approaches which should allow you to attempt some of
To add a new function:

- Determine the functions in C/Fortran that are equivalent to the function you want to support (ideally aim to support as many arguments as seems feasible, check the Python standard for the latest description of the function)
- Add a class to represent the function. The class should go in the appropriate file in the [ast](../pyccel/ast) folder. This function will probably inherit from [PyccelInternalFunction](../pyccel/ast/internals.py)
- Add a class to represent the function. The class should go in the appropriate file in the [ast](../pyccel/ast) folder. This function will probably inherit from [PyccelFunction](../pyccel/ast/internals.py)
- Ensure the function is recognised in the semantic stage by adding it to the appropriate dictionary (see the function `builtin_function` and the dictionary `builtin_import_registery` in [ast/utilities.py](../pyccel/ast/utilities.py)
- Add the print functions for the 3 languages
- Add tests in the folder `tests/epyccel`
Expand Down
6 changes: 3 additions & 3 deletions developer_docs/overview.md
Expand Up @@ -53,20 +53,20 @@ In the syntactic, semantic, and code generation stages a similar strategy is use
#### Example
Suppose we want to generate the code for an object of the class `NumpyTanh`, first we collect the inheritance tree of `NumpyTanh`. This gives us:
```python
('NumpyTanh', 'NumpyUfuncUnary', 'NumpyUfuncBase', 'PyccelInternalFunction', 'TypedAstNode', 'PyccelAstNode')
('NumpyTanh', 'NumpyUfuncUnary', 'NumpyUfuncBase', 'PyccelFunction', 'TypedAstNode', 'PyccelAstNode')
```
Therefore the print functions which are acceptable for visiting this object are:

- `_print_NumpyTanh`
- `_print_NumpyUfuncUnary`
- `_print_NumpyUfuncBase`
- `_print_PyccelInternalFunction`
- `_print_PyccelFunction`
- `_print_TypedAstNode`
- `_print_PyccelAstNode`

We run through these possible functions choosing the one which is the most specialised. If none of these methods exist, then an error is raised.

In the case of `NumpyTanh` the function which will be selected is `_print_NumpyUfuncBase` when translating to C or Fortran, and `_print_PyccelInternalFunction` when translating to Python.
In the case of `NumpyTanh` the function which will be selected is `_print_NumpyUfuncBase` when translating to C or Fortran, and `_print_PyccelFunction` when translating to Python.

### AST

Expand Down
4 changes: 2 additions & 2 deletions pyccel/ast/builtin_methods/list_methods.py
Expand Up @@ -11,7 +11,7 @@
"""

from pyccel.ast.datatypes import VoidType
from pyccel.ast.internals import PyccelInternalFunction
from pyccel.ast.internals import PyccelFunction

__all__ = ('ListAppend',
'ListClear',
Expand All @@ -25,7 +25,7 @@
)

#==============================================================================
class ListMethod(PyccelInternalFunction):
class ListMethod(PyccelFunction):
"""
Abstract class for list method calls.
Expand Down
4 changes: 2 additions & 2 deletions pyccel/ast/builtin_methods/set_methods.py
Expand Up @@ -10,7 +10,7 @@
This module contains objects which describe these methods within Pyccel's AST.
"""
from pyccel.ast.datatypes import VoidType
from pyccel.ast.internals import PyccelInternalFunction
from pyccel.ast.internals import PyccelFunction
from pyccel.ast.basic import TypedAstNode

__all__ = (
Expand All @@ -23,7 +23,7 @@
'SetRemove'
)

class SetMethod(PyccelInternalFunction):
class SetMethod(PyccelFunction):
"""
Abstract class for set method calls.
Expand Down
67 changes: 43 additions & 24 deletions pyccel/ast/builtins.py
Expand Up @@ -19,8 +19,8 @@
from .datatypes import GenericType, PythonNativeComplex, PrimitiveComplexType
from .datatypes import HomogeneousTupleType, InhomogeneousTupleType
from .datatypes import HomogeneousListType, HomogeneousContainerType
from .datatypes import FixedSizeNumericType, HomogeneousSetType
from .internals import PyccelInternalFunction, Slice, PyccelArrayShapeElement
from .datatypes import FixedSizeNumericType, HomogeneousSetType, SymbolicType
from .internals import PyccelFunction, Slice, PyccelArrayShapeElement
from .literals import LiteralInteger, LiteralFloat, LiteralComplex, Nil
from .literals import Literal, LiteralImaginaryUnit, convert_to_literal
from .literals import LiteralString
Expand Down Expand Up @@ -59,7 +59,7 @@
)

#==============================================================================
class PythonComplexProperty(PyccelInternalFunction):
class PythonComplexProperty(PyccelFunction):
"""
Represents a call to the .real or .imag property.
Expand Down Expand Up @@ -144,7 +144,7 @@ def __str__(self):
return f'Imag({self.internal_var})'

#==============================================================================
class PythonConjugate(PyccelInternalFunction):
class PythonConjugate(PyccelFunction):
"""
Represents a call to the .conjugate() function.
Expand Down Expand Up @@ -187,7 +187,7 @@ def __str__(self):
return f'Conjugate({self.internal_var})'

#==============================================================================
class PythonBool(PyccelInternalFunction):
class PythonBool(PyccelFunction):
"""
Represents a call to Python's native `bool()` function.
Expand Down Expand Up @@ -226,7 +226,7 @@ def __str__(self):
return f'Bool({self.arg})'

#==============================================================================
class PythonComplex(PyccelInternalFunction):
class PythonComplex(PyccelFunction):
"""
Represents a call to Python's native `complex()` function.
Expand Down Expand Up @@ -338,7 +338,7 @@ def __str__(self):
return f"complex({self.real}, {self.imag})"

#==============================================================================
class PythonEnumerate(PyccelAstNode):
class PythonEnumerate(PyccelFunction):
"""
Represents a call to Python's native `enumerate()` function.
Expand All @@ -355,6 +355,8 @@ class PythonEnumerate(PyccelAstNode):
__slots__ = ('_element','_start')
_attribute_nodes = ('_element','_start')
name = 'enumerate'
_class_type = SymbolicType()
_shape = ()

def __init__(self, arg, start = None):
if pyccel_stage != "syntactic" and \
Expand Down Expand Up @@ -390,7 +392,7 @@ def length(self):
return PythonLen(self.element)

#==============================================================================
class PythonFloat(PyccelInternalFunction):
class PythonFloat(PyccelFunction):
"""
Represents a call to Python's native `float()` function.
Expand Down Expand Up @@ -431,7 +433,7 @@ def __str__(self):
return f'float({self.arg})'

#==============================================================================
class PythonInt(PyccelInternalFunction):
class PythonInt(PyccelFunction):
"""
Represents a call to Python's native `int()` function.
Expand Down Expand Up @@ -613,7 +615,7 @@ def __new__(cls, arg):
raise TypeError(f"Can't unpack {arg} into a tuple")

#==============================================================================
class PythonLen(PyccelInternalFunction):
class PythonLen(PyccelFunction):
"""
Represents a `len` expression in the code.
Expand Down Expand Up @@ -768,12 +770,25 @@ def is_homogeneous(self):
return True

#==============================================================================
class PythonMap(PyccelAstNode):
""" Represents the map stmt
class PythonMap(PyccelFunction):
"""
Class representing a call to Python's builtin map function.
Class representing a call to Python's builtin map function.
Parameters
----------
func : FunctionDef
The function to be applied to the elements.
func_args : TypedAstNode
The arguments to which the function will be applied.
"""
__slots__ = ('_func','_func_args')
_attribute_nodes = ('_func','_func_args')
name = 'map'
_class_type = SymbolicType()
_shape = ()

def __init__(self, func, func_args):
self._func = func
Expand Down Expand Up @@ -842,7 +857,7 @@ def file(self):
return self._file

#==============================================================================
class PythonRange(PyccelAstNode):
class PythonRange(PyccelFunction):
"""
Class representing a range.
Expand All @@ -862,6 +877,8 @@ class PythonRange(PyccelAstNode):
__slots__ = ('_start','_stop','_step')
_attribute_nodes = ('_start', '_stop', '_step')
name = 'range'
_class_type = SymbolicType()
_shape = ()

def __init__(self, *args):
# Define default values
Expand Down Expand Up @@ -917,7 +934,7 @@ def __getitem__(self, index):


#==============================================================================
class PythonZip(PyccelInternalFunction):
class PythonZip(PyccelFunction):
"""
Represents a call to Python `zip` for code generation.
Expand Down Expand Up @@ -957,7 +974,7 @@ def __getitem__(self, index):
return [a[index] for a in self.args]

#==============================================================================
class PythonAbs(PyccelInternalFunction):
class PythonAbs(PyccelFunction):
"""
Represents a call to Python `abs` for code generation.
Expand Down Expand Up @@ -985,7 +1002,7 @@ def arg(self):
return self._args[0]

#==============================================================================
class PythonSum(PyccelInternalFunction):
class PythonSum(PyccelFunction):
"""
Represents a call to Python `sum` for code generation.
Expand Down Expand Up @@ -1019,7 +1036,7 @@ def arg(self):
return self._args[0]

#==============================================================================
class PythonMax(PyccelInternalFunction):
class PythonMax(PyccelFunction):
"""
Represents a call to Python's built-in `max` function.
Expand All @@ -1045,7 +1062,7 @@ def __init__(self, *x):

if not x.is_homogeneous:
types = ', '.join(str(xi.dtype) for xi in x)
raise PyccelError("Cannot determine final dtype of 'max' call with arguments of different "
raise TypeError("Cannot determine final dtype of 'max' call with arguments of different "
f"types ({types}). Please cast arguments to the desired dtype")
if isinstance(x.class_type, HomogeneousContainerType):
self._class_type = x.class_type.element_type
Expand All @@ -1055,11 +1072,11 @@ def __init__(self, *x):


#==============================================================================
class PythonMin(PyccelInternalFunction):
class PythonMin(PyccelFunction):
"""
Represents a call to Python's built-in `max` function.
Represents a call to Python's built-in `min` function.
Represents a call to Python's built-in `max` function.
Represents a call to Python's built-in `min` function.
Parameters
----------
Expand All @@ -1081,7 +1098,7 @@ def __init__(self, *x):

if not x.is_homogeneous:
types = ', '.join(str(xi.dtype) for xi in x)
raise PyccelError("Cannot determine final dtype of 'min' call with arguments of different "
raise TypeError("Cannot determine final dtype of 'min' call with arguments of different "
f"types ({types}). Please cast arguments to the desired dtype")
if isinstance(x.class_type, HomogeneousContainerType):
self._class_type = x.class_type.element_type
Expand Down Expand Up @@ -1135,7 +1152,7 @@ def __str__(self):
return f"{self.variables} -> {self.expr}"

#==============================================================================
class PythonType(PyccelAstNode):
class PythonType(PyccelFunction):
"""
Represents a call to the Python builtin `type` function.
Expand All @@ -1153,10 +1170,12 @@ class PythonType(PyccelAstNode):
"""
__slots__ = ('_type','_obj')
_attribute_nodes = ('_obj',)
_class_type = SymbolicType()
_shape = ()

def __init__(self, obj):
if not isinstance (obj, TypedAstNode):
raise PyccelError(f"Python's type function is not implemented for {type(obj)} object")
raise TypeError(f"Python's type function is not implemented for {type(obj)} object")
self._type = obj.class_type
self._obj = obj

Expand Down
8 changes: 4 additions & 4 deletions pyccel/ast/cmathext.py
Expand Up @@ -11,7 +11,7 @@
from pyccel.ast.core import PyccelFunctionDef, Module
from pyccel.ast.datatypes import PythonNativeBool, PythonNativeFloat, PythonNativeComplex
from pyccel.ast.datatypes import PrimitiveComplexType, HomogeneousTupleType
from pyccel.ast.internals import PyccelInternalFunction
from pyccel.ast.internals import PyccelFunction
from pyccel.ast.literals import LiteralInteger
from pyccel.ast.operators import PyccelAnd, PyccelOr
from pyccel.ast.variable import Constant
Expand Down Expand Up @@ -403,7 +403,7 @@ def __new__(cls, z):
# Dictionary to map math functions to classes above
#==============================================================================

class CmathPhase(PyccelInternalFunction):
class CmathPhase(PyccelFunction):
"""
Class representing a call to the `cmath.phase` function.
Expand All @@ -420,7 +420,7 @@ class CmathPhase(PyccelInternalFunction):
def __init__(self, z):
super().__init__(z)

class CmathPolar(PyccelInternalFunction):
class CmathPolar(PyccelFunction):
"""
Class representing a call to the `cmath.polar` function.
Expand All @@ -439,7 +439,7 @@ class CmathPolar(PyccelInternalFunction):
def __init__(self, z):
super().__init__(z)

class CmathRect(PyccelInternalFunction):
class CmathRect(PyccelFunction):
"""
Class representing a call to the `cmath.rect` function.
Expand Down

0 comments on commit 9464954

Please sign in to comment.