From 94649547dd691b0cc865c7b6feffcfdd24832072 Mon Sep 17 00:00:00 2001 From: EmilyBourne Date: Fri, 19 Apr 2024 15:30:33 +0200 Subject: [PATCH] Remove `pyccel.ast.utilities.builtin_function` (#1841) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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ü --- CHANGELOG.md | 4 ++ developer_docs/ast_nodes.md | 8 +-- developer_docs/how_to_solve_an_issue.md | 2 +- developer_docs/overview.md | 6 +- pyccel/ast/builtin_methods/list_methods.py | 4 +- pyccel/ast/builtin_methods/set_methods.py | 4 +- pyccel/ast/builtins.py | 67 ++++++++++++++-------- pyccel/ast/cmathext.py | 8 +-- pyccel/ast/core.py | 10 ++-- pyccel/ast/cwrapper.py | 12 ++-- pyccel/ast/internals.py | 8 +-- pyccel/ast/itertoolsext.py | 11 +++- pyccel/ast/mathext.py | 4 +- pyccel/ast/numpyext.py | 44 +++++++------- pyccel/ast/sysext.py | 4 +- pyccel/ast/utilities.py | 42 +++----------- pyccel/codegen/printing/pycode.py | 4 +- pyccel/parser/semantic.py | 26 ++++----- pyccel/parser/syntactic.py | 4 +- 19 files changed, 137 insertions(+), 135 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c0649da3c..25de230a48 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 @@ -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 diff --git a/developer_docs/ast_nodes.md b/developer_docs/ast_nodes.md index 785a006868..b8e05f239d 100644 --- a/developer_docs/ast_nodes.md +++ b/developer_docs/ast_nodes.md @@ -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 @@ -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 diff --git a/developer_docs/how_to_solve_an_issue.md b/developer_docs/how_to_solve_an_issue.md index 8c6771cec1..00190e20e6 100644 --- a/developer_docs/how_to_solve_an_issue.md +++ b/developer_docs/how_to_solve_an_issue.md @@ -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` diff --git a/developer_docs/overview.md b/developer_docs/overview.md index 6ef68b1211..595e42130f 100644 --- a/developer_docs/overview.md +++ b/developer_docs/overview.md @@ -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 diff --git a/pyccel/ast/builtin_methods/list_methods.py b/pyccel/ast/builtin_methods/list_methods.py index 462e27cb0f..5dc9fef977 100644 --- a/pyccel/ast/builtin_methods/list_methods.py +++ b/pyccel/ast/builtin_methods/list_methods.py @@ -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', @@ -25,7 +25,7 @@ ) #============================================================================== -class ListMethod(PyccelInternalFunction): +class ListMethod(PyccelFunction): """ Abstract class for list method calls. diff --git a/pyccel/ast/builtin_methods/set_methods.py b/pyccel/ast/builtin_methods/set_methods.py index 7505901e5b..74f0010634 100644 --- a/pyccel/ast/builtin_methods/set_methods.py +++ b/pyccel/ast/builtin_methods/set_methods.py @@ -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__ = ( @@ -23,7 +23,7 @@ 'SetRemove' ) -class SetMethod(PyccelInternalFunction): +class SetMethod(PyccelFunction): """ Abstract class for set method calls. diff --git a/pyccel/ast/builtins.py b/pyccel/ast/builtins.py index ffe706b48c..ba98c537b2 100644 --- a/pyccel/ast/builtins.py +++ b/pyccel/ast/builtins.py @@ -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 @@ -59,7 +59,7 @@ ) #============================================================================== -class PythonComplexProperty(PyccelInternalFunction): +class PythonComplexProperty(PyccelFunction): """ Represents a call to the .real or .imag property. @@ -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. @@ -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. @@ -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. @@ -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. @@ -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 \ @@ -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. @@ -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. @@ -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. @@ -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 @@ -842,7 +857,7 @@ def file(self): return self._file #============================================================================== -class PythonRange(PyccelAstNode): +class PythonRange(PyccelFunction): """ Class representing a range. @@ -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 @@ -917,7 +934,7 @@ def __getitem__(self, index): #============================================================================== -class PythonZip(PyccelInternalFunction): +class PythonZip(PyccelFunction): """ Represents a call to Python `zip` for code generation. @@ -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. @@ -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. @@ -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. @@ -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 @@ -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 ---------- @@ -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 @@ -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. @@ -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 diff --git a/pyccel/ast/cmathext.py b/pyccel/ast/cmathext.py index 5f821dfeea..f6f5a6f800 100644 --- a/pyccel/ast/cmathext.py +++ b/pyccel/ast/cmathext.py @@ -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 @@ -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. @@ -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. @@ -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. diff --git a/pyccel/ast/core.py b/pyccel/ast/core.py index a5e959bec0..cbbc611518 100644 --- a/pyccel/ast/core.py +++ b/pyccel/ast/core.py @@ -21,7 +21,7 @@ PythonNativeBool, InhomogeneousTupleType, VoidType) -from .internals import PyccelSymbol, PyccelInternalFunction, apply_pickle +from .internals import PyccelSymbol, PyccelFunction, apply_pickle from .literals import Nil, LiteralFalse, LiteralInteger from .literals import NilArgument, LiteralTrue @@ -2866,19 +2866,19 @@ def __getnewargs__(self): class PyccelFunctionDef(FunctionDef): """ - Class used for storing `PyccelInternalFunction` objects in a FunctionDef. + Class used for storing `PyccelFunction` objects in a FunctionDef. Class inheriting from `FunctionDef` which can store a pointer to a class type defined by pyccel for treating internal functions. This is useful for importing builtin functions and for defining - classes which have `PyccelInternalFunction`s as attributes or methods. + classes which have `PyccelFunction`s as attributes or methods. Parameters ---------- name : str The name of the function. - func_class : type inheriting from PyccelInternalFunction / TypedAstNode + func_class : type inheriting from PyccelFunction / TypedAstNode The class which should be instantiated upon a FunctionCall to this FunctionDef object. @@ -2894,7 +2894,7 @@ class PyccelFunctionDef(FunctionDef): __slots__ = ('_argument_description',) def __init__(self, name, func_class, *, decorators = {}, argument_description = {}): assert isinstance(func_class, type) and \ - issubclass(func_class, (PyccelInternalFunction, TypedAstNode)) + issubclass(func_class, (PyccelFunction, TypedAstNode)) assert isinstance(argument_description, dict) arguments = () results = () diff --git a/pyccel/ast/cwrapper.py b/pyccel/ast/cwrapper.py index 6104719597..78592a11e2 100644 --- a/pyccel/ast/cwrapper.py +++ b/pyccel/ast/cwrapper.py @@ -28,7 +28,7 @@ from .c_concepts import ObjectAddress, CNativeInt -from .internals import PyccelInternalFunction +from .internals import PyccelFunction from .literals import LiteralString, LiteralInteger @@ -251,7 +251,7 @@ def arg_names(self): return self._arg_names #------------------------------------------------------------------- -class PyBuildValueNode(PyccelInternalFunction): +class PyBuildValueNode(PyccelFunction): """ Represents a call to the function PyBuildValueNode. @@ -289,7 +289,7 @@ def args(self): return self._result_args #------------------------------------------------------------------- -class PyModule_AddObject(PyccelInternalFunction): +class PyModule_AddObject(PyccelFunction): """ Represents a call to the PyModule_AddObject function. @@ -341,7 +341,7 @@ def variable(self): return self._var #------------------------------------------------------------------- -class PyModule_Create(PyccelInternalFunction): +class PyModule_Create(PyccelFunction): """ Represents a call to the PyModule_Create function. @@ -374,7 +374,7 @@ def module_def_name(self): return self._module_def_name #------------------------------------------------------------------- -class PyCapsule_New(PyccelInternalFunction): +class PyCapsule_New(PyccelFunction): """ Represents a call to the function PyCapsule_New. @@ -425,7 +425,7 @@ def API_var(self): return self._API_var #------------------------------------------------------------------- -class PyCapsule_Import(PyccelInternalFunction): +class PyCapsule_Import(PyccelFunction): """ Represents a call to the function PyCapsule_Import. diff --git a/pyccel/ast/internals.py b/pyccel/ast/internals.py index f502d9abfe..0e4eed8a8e 100644 --- a/pyccel/ast/internals.py +++ b/pyccel/ast/internals.py @@ -20,13 +20,13 @@ 'PrecomputedCode', 'PyccelArrayShapeElement', 'PyccelArraySize', - 'PyccelInternalFunction', + 'PyccelFunction', 'PyccelSymbol', 'Slice', ) -class PyccelInternalFunction(TypedAstNode): +class PyccelFunction(TypedAstNode): """ Abstract class for function calls translated to Pyccel objects. @@ -67,7 +67,7 @@ def is_elemental(self): return False -class PyccelArraySize(PyccelInternalFunction): +class PyccelArraySize(PyccelFunction): """ Gets the total number of elements in an array. @@ -108,7 +108,7 @@ def __eq__(self, other): return False -class PyccelArrayShapeElement(PyccelInternalFunction): +class PyccelArrayShapeElement(PyccelFunction): """ Gets the number of elements in a given dimension of an array. diff --git a/pyccel/ast/itertoolsext.py b/pyccel/ast/itertoolsext.py index 24c6914890..1a4a8b2a32 100644 --- a/pyccel/ast/itertoolsext.py +++ b/pyccel/ast/itertoolsext.py @@ -7,18 +7,23 @@ """ from .builtins import PythonLen, PythonRange from .core import PyccelFunctionDef, Module -from .internals import PyccelInternalFunction +from .internals import PyccelFunction __all__ = ( 'Product', 'itertools_mod', ) -class Product(PyccelInternalFunction): +class Product(PyccelFunction): """ Represents a call to itertools.product for code generation. - arg : list, tuple + Represents a call to itertools.product for code generation. + + Parameters + ---------- + *args : PyccelAstType + The arguments passed to the product function. """ __slots__ = ('_elements',) _attribute_nodes = ('_elements',) diff --git a/pyccel/ast/mathext.py b/pyccel/ast/mathext.py index c48c249da9..f2c713f463 100644 --- a/pyccel/ast/mathext.py +++ b/pyccel/ast/mathext.py @@ -9,7 +9,7 @@ from pyccel.ast.core import PyccelFunctionDef, Module from pyccel.ast.datatypes import PythonNativeInt, PythonNativeBool, PythonNativeFloat -from pyccel.ast.internals import PyccelInternalFunction +from pyccel.ast.internals import PyccelFunction from pyccel.ast.variable import Constant __all__ = ( @@ -75,7 +75,7 @@ #============================================================================== # Base classes #============================================================================== -class MathFunctionBase(PyccelInternalFunction): +class MathFunctionBase(PyccelFunction): """ Abstract base class for the Math Functions. diff --git a/pyccel/ast/numpyext.py b/pyccel/ast/numpyext.py index b1a889ba90..a455608886 100644 --- a/pyccel/ast/numpyext.py +++ b/pyccel/ast/numpyext.py @@ -25,7 +25,7 @@ from .datatypes import HomogeneousTupleType, FixedSizeNumericType, GenericType, HomogeneousContainerType from .datatypes import InhomogeneousTupleType, ContainerType -from .internals import PyccelInternalFunction, Slice +from .internals import PyccelFunction, Slice from .internals import PyccelArraySize, PyccelArrayShapeElement from .literals import LiteralInteger, LiteralString, convert_to_literal @@ -521,7 +521,7 @@ class NumpyComplex128(NumpyComplex): #======================================================================================= -class NumpyResultType(PyccelInternalFunction): +class NumpyResultType(PyccelFunction): """ Class representing a call to the `numpy.result_type` function. @@ -604,7 +604,7 @@ def process_dtype(dtype): raise TypeError(f'Unknown type of {dtype}.') #============================================================================== -class NumpyNewArray(PyccelInternalFunction): +class NumpyNewArray(PyccelFunction): """ Superclass for nodes representing NumPy array allocation functions. @@ -614,7 +614,7 @@ class NumpyNewArray(PyccelInternalFunction): Parameters ---------- *args : tuple of TypedAstNode - The arguments of the superclass PyccelInternalFunction. + The arguments of the superclass PyccelFunction. class_type : NumpyNDArrayType The type of the new array. init_dtype : PythonType, PyccelFunctionDef, LiteralString, str @@ -835,7 +835,7 @@ def __getitem__(self, index): return PyccelAdd(self.start, step, simplify=True) #============================================================================== -class NumpySum(PyccelInternalFunction): +class NumpySum(PyccelFunction): """ Represents a call to numpy.sum for code generation. @@ -866,7 +866,7 @@ def arg(self): return self._args[0] #============================================================================== -class NumpyProduct(PyccelInternalFunction): +class NumpyProduct(PyccelFunction): """ Represents a call to numpy.prod for code generation. @@ -902,7 +902,7 @@ def arg(self): #============================================================================== -class NumpyMatmul(PyccelInternalFunction): +class NumpyMatmul(PyccelFunction): """ Represents a call to numpy.matmul for code generation. @@ -964,7 +964,7 @@ def b(self): return self._args[1] #============================================================================== -class NumpyShape(PyccelInternalFunction): +class NumpyShape(PyccelFunction): """ Represents a call to numpy.shape for code generation. @@ -1129,7 +1129,7 @@ def is_elemental(self): return True #============================================================================== -class NumpyWhere(PyccelInternalFunction): +class NumpyWhere(PyccelFunction): """ Represents a call to `numpy.where`. @@ -1200,7 +1200,7 @@ def is_elemental(self): return True #============================================================================== -class NumpyRand(PyccelInternalFunction): +class NumpyRand(PyccelFunction): """ Represents a call to numpy.random.random or numpy.random.rand for code generation. @@ -1225,7 +1225,7 @@ def __init__(self, *args): self._class_type = NumpyNDArrayType(NumpyFloat64Type(), rank, order) #============================================================================== -class NumpyRandint(PyccelInternalFunction): +class NumpyRandint(PyccelFunction): """ Class representing a call to NumPy's randint function. @@ -1439,7 +1439,7 @@ def fill_value(self): return convert_to_literal(1, self.dtype) #============================================================================== -class NumpyFullLike(PyccelInternalFunction): +class NumpyFullLike(PyccelFunction): """ Represents a call to numpy.full_like for code generation. @@ -1486,7 +1486,7 @@ def __new__(cls, a, fill_value, dtype=None, order='K', subok=True, shape=None): return NumpyFull(shape, fill_value, dtype, order) #============================================================================== -class NumpyEmptyLike(PyccelInternalFunction): +class NumpyEmptyLike(PyccelFunction): """ Represents a call to numpy.empty_like for code generation. @@ -1532,7 +1532,7 @@ def __new__(cls, a, dtype=None, order='K', subok=True, shape=None): return NumpyEmpty(shape, dtype, order) #============================================================================== -class NumpyOnesLike(PyccelInternalFunction): +class NumpyOnesLike(PyccelFunction): """ Represents a call to numpy.ones_like for code generation. @@ -1577,7 +1577,7 @@ def __new__(cls, a, dtype=None, order='K', subok=True, shape=None): return NumpyOnes(shape, dtype, order) #============================================================================== -class NumpyZerosLike(PyccelInternalFunction): +class NumpyZerosLike(PyccelFunction): """ Represents a call to numpy.zeros_like for code generation. @@ -1623,7 +1623,7 @@ def __new__(cls, a, dtype=None, order='K', subok=True, shape=None): return NumpyZeros(shape, dtype, order) #============================================================================== -class NumpyNorm(PyccelInternalFunction): +class NumpyNorm(PyccelFunction): """ Represents call to `numpy.norm`. @@ -1684,7 +1684,7 @@ def axis(self): # Numpy universal functions # https://numpy.org/doc/stable/reference/ufuncs.html#available-ufuncs #============================================================================== -class NumpyUfuncBase(PyccelInternalFunction): +class NumpyUfuncBase(PyccelFunction): """ Base class for Numpy's universal functions. @@ -2162,7 +2162,7 @@ def _get_dtype(self, x1, x2): arg_dtype = arg_class_type return process_dtype(arg_dtype) -class NumpyAmin(PyccelInternalFunction): +class NumpyAmin(PyccelFunction): """ Represents a call to numpy.min for code generation. @@ -2190,7 +2190,7 @@ def arg(self): """ return self._args[0] -class NumpyAmax(PyccelInternalFunction): +class NumpyAmax(PyccelFunction): """ Represents a call to numpy.max for code generation. @@ -2397,7 +2397,7 @@ def dim(self): """ return self._dim -class NumpyNonZero(PyccelInternalFunction): +class NumpyNonZero(PyccelFunction): """ Class representing a call to the function `numpy.nonzero`. @@ -2443,7 +2443,7 @@ def elements(self): def __iter__(self): return self._elements.__iter__() -class NumpyCountNonZero(PyccelInternalFunction): +class NumpyCountNonZero(PyccelFunction): """ Class representing a call to the NumPy function `count_nonzero`. @@ -2519,7 +2519,7 @@ def keep_dims(self): return self._keep_dims -class NumpySize(PyccelInternalFunction): +class NumpySize(PyccelFunction): """ Represent a call to numpy.size in the user code. diff --git a/pyccel/ast/sysext.py b/pyccel/ast/sysext.py index 73a017a725..e655de65bb 100644 --- a/pyccel/ast/sysext.py +++ b/pyccel/ast/sysext.py @@ -6,7 +6,7 @@ """ Module containing objects from the sys module understood by pyccel """ from .core import PyccelFunctionDef, Module -from .internals import PyccelInternalFunction +from .internals import PyccelFunction from .datatypes import VoidType from .internals import LiteralInteger @@ -17,7 +17,7 @@ 'sys_mod', ) -class SysExit(PyccelInternalFunction): +class SysExit(PyccelFunction): """ Represents a call to sys.exit. diff --git a/pyccel/ast/utilities.py b/pyccel/ast/utilities.py index dd7f917245..ddb379c919 100644 --- a/pyccel/ast/utilities.py +++ b/pyccel/ast/utilities.py @@ -20,7 +20,7 @@ PythonRange, PythonList, PythonTuple) from .cmathext import cmath_mod from .datatypes import HomogeneousTupleType, PythonNativeInt -from .internals import PyccelInternalFunction, Slice +from .internals import PyccelFunction, Slice from .itertoolsext import itertools_mod from .literals import LiteralInteger, LiteralEllipsis, Nil from .mathext import math_mod @@ -45,35 +45,9 @@ 'split_positional_keyword_arguments', ) -#============================================================================== -def builtin_function(expr, args=None): - """Returns a builtin-function call applied to given arguments.""" - - if isinstance(expr, FunctionCall): - name = str(expr.funcdef) - elif isinstance(expr, str): - name = expr - else: - raise TypeError('expr must be of type str or FunctionCall') - - dic = builtin_functions_dict - - # Unpack FunctionCallArguments - args = [a.value for a in args] - - if name in dic.keys() : - try: - return dic[name](*args) - except PyccelError as e: - errors.report(e, - symbol=expr, - severity='fatal') - - return None - #============================================================================== decorators_mod = Module('decorators',(), - funcs = [PyccelFunctionDef(d, PyccelInternalFunction) for d in pyccel_decorators.__all__]) + funcs = [PyccelFunctionDef(d, PyccelFunction) for d in pyccel_decorators.__all__]) pyccel_mod = Module('pyccel',(),(), imports = [Import('decorators', decorators_mod)]) @@ -437,7 +411,7 @@ def collect_loops(block, indices, new_index, language_has_vectors = False, resul current_level = 0 array_creator_types = (Allocate, PythonList, PythonTuple, Concatenate, Duplicate) is_function_call = lambda f: ((isinstance(f, FunctionCall) and not f.funcdef.is_elemental) - or (isinstance(f, PyccelInternalFunction) and not f.is_elemental and not hasattr(f, '__getitem__') + or (isinstance(f, PyccelFunction) and not f.is_elemental and not hasattr(f, '__getitem__') and not isinstance(f, (NumpyTranspose)))) for line in block: @@ -462,14 +436,14 @@ def collect_loops(block, indices, new_index, language_has_vectors = False, resul ObjectAddress, NumpyTranspose, FunctionCall, - PyccelInternalFunction, + PyccelFunction, PyccelIs)) # Find all elemental function calls. Normally function call arguments are not indexed # However elemental functions are an exception elemental_func_calls = [f for f in notable_nodes if (isinstance(f, FunctionCall) \ and f.funcdef.is_elemental)] - elemental_func_calls += [f for f in notable_nodes if (isinstance(f, PyccelInternalFunction) \ + elemental_func_calls += [f for f in notable_nodes if (isinstance(f, PyccelFunction) \ and f.is_elemental)] # Collect all objects into which indices may be inserted @@ -481,7 +455,7 @@ def collect_loops(block, indices, new_index, language_has_vectors = False, resul transposed_vars = [v for v in notable_nodes if isinstance(v, NumpyTranspose)] \ + [v for f in elemental_func_calls \ for v in f.get_attribute_nodes(NumpyTranspose)] - indexed_funcs = [v for v in notable_nodes if isinstance(v, PyccelInternalFunction) and hasattr(v, '__getitem__')] + indexed_funcs = [v for v in notable_nodes if isinstance(v, PyccelFunction) and hasattr(v, '__getitem__')] is_checks = [n for n in notable_nodes if isinstance(n, PyccelIs)] @@ -497,7 +471,7 @@ def collect_loops(block, indices, new_index, language_has_vectors = False, resul # Find function calls in this line funcs = [f for f in notable_nodes+transposed_vars if (isinstance(f, FunctionCall) \ and not f.funcdef.is_elemental)] - internal_funcs = [f for f in notable_nodes+transposed_vars if (isinstance(f, PyccelInternalFunction) \ + internal_funcs = [f for f in notable_nodes+transposed_vars if (isinstance(f, PyccelFunction) \ and not f.is_elemental and not hasattr(f, '__getitem__')) \ and not isinstance(f, NumpyTranspose)] @@ -560,7 +534,7 @@ def collect_loops(block, indices, new_index, language_has_vectors = False, resul # Replace variable expressions with Indexed versions line.substitute(variables, new_vars, - excluded_nodes = (FunctionCall, PyccelInternalFunction)) + excluded_nodes = (FunctionCall, PyccelFunction)) line.substitute(transposed_vars + indexed_funcs, handled_funcs, excluded_nodes = (FunctionCall)) _ = [f.substitute(variables, new_vars) for f in elemental_func_calls] diff --git a/pyccel/codegen/printing/pycode.py b/pyccel/codegen/printing/pycode.py index 79729b398e..6f01ec99b1 100644 --- a/pyccel/codegen/printing/pycode.py +++ b/pyccel/codegen/printing/pycode.py @@ -165,7 +165,7 @@ def _get_numpy_name(self, expr): Parameters ---------- - expr : PyccelInternalFunction + expr : PyccelFunction A Pyccel node describing a NumPy function. Returns @@ -769,7 +769,7 @@ def _print_NumpyArange(self, expr): dtype] if a != '') return f"{name}({args})" - def _print_PyccelInternalFunction(self, expr): + def _print_PyccelFunction(self, expr): name = self._aliases.get(type(expr),expr.name) args = ', '.join(self._print(a) for a in expr.args) return "{}({})".format(name, args) diff --git a/pyccel/parser/semantic.py b/pyccel/parser/semantic.py index 708e7d9743..3c463a7cd4 100644 --- a/pyccel/parser/semantic.py +++ b/pyccel/parser/semantic.py @@ -71,7 +71,7 @@ from pyccel.ast.headers import FunctionHeader, MethodHeader, Header from pyccel.ast.headers import MacroFunction, MacroVariable -from pyccel.ast.internals import PyccelInternalFunction, Slice, PyccelSymbol, PyccelArrayShapeElement +from pyccel.ast.internals import PyccelFunction, Slice, PyccelSymbol, PyccelArrayShapeElement from pyccel.ast.itertoolsext import Product from pyccel.ast.literals import LiteralTrue, LiteralFalse @@ -104,7 +104,6 @@ from pyccel.ast.typingext import TypingFinal -from pyccel.ast.utilities import builtin_function as pyccel_builtin_function from pyccel.ast.utilities import builtin_import as pyccel_builtin_import from pyccel.ast.utilities import builtin_import_registry as pyccel_builtin_import_registry from pyccel.ast.utilities import split_positional_keyword_arguments @@ -1022,7 +1021,7 @@ def _handle_function(self, expr, func, args, is_method = False): """ Create the node representing the function call. - Create a FunctionCall or an instance of a PyccelInternalFunction + Create a FunctionCall or an instance of a PyccelFunction from the function information and arguments. Parameters @@ -1030,7 +1029,7 @@ def _handle_function(self, expr, func, args, is_method = False): expr : TypedAstNode The expression where this call is found (used for error output). - func : FunctionDef instance, Interface instance or PyccelInternalFunction type + func : FunctionDef instance, Interface instance or PyccelFunction type The function being called. args : iterable @@ -1041,7 +1040,7 @@ def _handle_function(self, expr, func, args, is_method = False): Returns ------- - FunctionCall/PyccelInternalFunction + FunctionCall/PyccelFunction The semantic representation of the call. """ if isinstance(func, PyccelFunctionDef): @@ -2228,7 +2227,7 @@ def generate_and_assign_temp_var(): assign = self._visit(syntactic_assign) self._additional_exprs[-1].append(assign) return FunctionCallArgument(self._visit(tmp_var)) - if isinstance(value, (PyccelArithmeticOperator, PyccelInternalFunction)) and value.rank: + if isinstance(value, (PyccelArithmeticOperator, PyccelFunction)) and value.rank: a = generate_and_assign_temp_var() elif isinstance(value, FunctionCall) and isinstance(value.class_type, CustomDataType): if not value.funcdef.results[0].var.is_alias: @@ -2928,7 +2927,12 @@ def _visit_FunctionCall(self, expr): except RuntimeError: pass - func = self.scope.find(name, 'functions') + func = self.scope.find(name, 'functions') + + if func is None: + name = str(expr.funcdef) + if name in builtin_functions_dict: + func = PyccelFunctionDef(name, builtin_functions_dict[name]) # Check for specialised method if isinstance(func, PyccelFunctionDef): @@ -2969,12 +2973,8 @@ def _visit_FunctionCall(self, expr): if name == 'lambdify': args = self.scope.find(str(expr.args[0]), 'symbolic_functions') - F = pyccel_builtin_function(expr, args) - - if func is None and F is not None: - return F - elif self.scope.find(name, 'cls_constructs'): + if self.scope.find(name, 'cls_constructs'): # TODO improve the test # we must not invoke the scope like this @@ -3193,7 +3193,7 @@ def _visit_Assign(self, expr): d_var = self._infer_type(rhs) if d_var['memory_handling'] == 'alias' and not isinstance(lhs, IndexedElement): rhs = rhs.internal_var - elif isinstance(rhs, PyccelInternalFunction) and isinstance(rhs.dtype, VoidType): + elif isinstance(rhs, PyccelFunction) and isinstance(rhs.dtype, VoidType): if expr.lhs.is_temp: return rhs else: diff --git a/pyccel/parser/syntactic.py b/pyccel/parser/syntactic.py index e37e287a4a..5572bcb13a 100644 --- a/pyccel/parser/syntactic.py +++ b/pyccel/parser/syntactic.py @@ -60,7 +60,7 @@ from pyccel.ast.functionalexpr import FunctionalSum, FunctionalMax, FunctionalMin, GeneratorComprehension, FunctionalFor from pyccel.ast.variable import DottedName, AnnotatedPyccelSymbol -from pyccel.ast.internals import Slice, PyccelSymbol, PyccelInternalFunction +from pyccel.ast.internals import Slice, PyccelSymbol, PyccelFunction from pyccel.ast.type_annotations import SyntacticTypeAnnotation, UnionTypeAnnotation @@ -960,7 +960,7 @@ def _visit_FunctionDef(self, stmt): body = CodeBlock(body) returns = [i.expr for i in body.get_attribute_nodes(Return, - excluded_nodes = (Assign, FunctionCall, PyccelInternalFunction, FunctionDef))] + excluded_nodes = (Assign, FunctionCall, PyccelFunction, FunctionDef))] assert all(len(i) == len(returns[0]) for i in returns) if is_inline and len(returns)>1: errors.report("Inline functions cannot have multiple return statements",