Skip to content

Commit f508fa3

Browse files
authored
Remove InhomogeneousTupleVariable (pyccel#1838)
Remove the `InhomogeneousTupleVariable` class. Fixes pyccel#1583 There is now enough information in the types to make this class unnecessary. Removing it allows inhomogeneous tuples to be used in classes (as it was previously not possible to combine `DottedVariable` and `InhomogeneousTupleVariable`). **Commit Summary** - Use the `symbolic_alias` category in a `Scope` to map an indexed element of an inhomogeneous tuple to the equivalent variable. - Create `_build_X` functions for functions which accept inhomogeneous tuple arguments (created `SemanticParser._build_PythonTupleFunction` and `SemanticParser._build_NumpyArray`). This allows the variables to be collected from the scope during the building process - Allow a `PyccelUnarySub` to be used as an index (to facilitate indexing with literals) - Add a `is_literal_integer` function to determine if an expression is a literal integer - Recognise inhomogeneous tuples via their class type - Remove `InhomogeneousTupleVariable` - Ensure `IndexedElement` can handle inhomogeneous tuple objects. - Don't declare inhomogeneous tuples (their elements are now already present in the scope) - Clarify use of dtype/class type in `FCodePrinter._print_Declare` - Correct type annotation printing in Python (quotes are no longer present to be removed) - Raise a warning if trying to wrap tuples - Clean up tuple handling in `_visit_Assign`. Fixes pyccel#1392 - Extract printed inhomogenous tuple variables into `PythonTuple` at the semantic stage - Increase coverage - Add a test for inhomogeneous tuples in classes
1 parent b739d46 commit f508fa3

File tree

20 files changed

+595
-378
lines changed

20 files changed

+595
-378
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ All notable changes to this project will be documented in this file.
3131
- #1936 : Add missing C output for inline decorator example in documentation
3232
- #1937 : Optimise `pyccel.ast.basic.PyccelAstNode.substitute` method.
3333
- #1544 : Add support for `typing.TypeAlias`.
34+
- #1583 : Allow inhomogeneous tuples in classes.
3435
- \[INTERNALS\] Added `container_rank` property to `ast.datatypes.PyccelType` objects.
3536
- \[DEVELOPER\] Added an improved traceback to the developer-mode errors for errors in function calls.
3637

developer_docs/tuples.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
# Tuples
2+
3+
In Pyccel tuples require special handling. There are 2 main use cases. The first use case is tuples as a small collection of objects of the same type. This case is handled by homogeneous tuples (for more details see the [user docs](../docs/containers.md)). This case is relatively easy to handle as it works similarly to all other containers. The second case is much more tricky. In this case a tuple is an object which allows objects of different types to be grouped together. In this case Pyccel must treat the objects symbolically.
4+
5+
In order to handle the translation of inhomogeneous tuples Pyccel creates variables for each of the elements of the inhomogeneous tuples. The names of these elements are chosen to keep the code as close to the original as possible. Therefore for a tuple stored in a variable `var` the element `var[0]` is usually translated to a variable called `var_0`. This handling of tuples is mostly made possible via the `Scope`.
6+
Whenever a variable representing a tuple element is created it must be associated with the `IndexedElement` via the `Scope`. This is done by calling the function `Scope.insert_symbolic_alias` (e.g. `insertion_scope.insert_symbolic_alias(IndexedElement(lhs, i), v)`).
7+
Once the symbolic alias has been created the function `Scope.collect_tuple_element` allows the variable to be retrieved from the `IndexedElement`.
8+
The `IndexedElement` representation of the inhomogeneous tuple element is a purely symbolic object and should not appear in generated code. Care must therefore be taken to ensure that `Scope.collect_tuple_element` is used whenever elements are extracted from a tuple.

docs/containers.md

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
# Container types in Pyccel
2+
3+
Pyccel provides support for some container types with certain limits. The types that are currently supported are:
4+
- NumPy arrays
5+
- Tuples
6+
7+
## NumPy arrays
8+
9+
NumPy arrays are provided as part of the NumPy support. There is dedicated documentation which explains the limitations and implementation details. See [N-dimensional array](./ndarrays.md) for more details.
10+
11+
## Tuples
12+
13+
In Pyccel tuples are divided into two types: homogeneous and inhomogeneous. Homogeneous tuples are objects where all elements of the container have the same type while inhomogeneous tuples can contain objects of different types. These two types are handled differently and therefore have very different restrictions.
14+
15+
Currently Pyccel cannot wrap tuples so they can be used in functions but cannot yet be exposed to Python.
16+
17+
### Homogeneous tuples
18+
19+
Homogeneous tuples are handled as though they were arrays. This means that they have the same restrictions and advantages as NumPy arrays. In particular they can be indexed at an arbitrary point.
20+
21+
Elements of a homogeneous tuple should have the same type, the same number of dimensions, and (if relevant) the same NumPy ordering. If any of these constraints is not respected then you may unexpectedly find yourself using the more inflexible inhomogeneous tuples. Further tuples containing pointers to other objects cannot always be stored in a homogeneous tuple.
22+
23+
### Inhomogeneous tuples
24+
25+
Inhomogeneous tuples are handled symbolically. This means that an inhomogeneous tuple is treated as a collection of translatable objects. Each of these objects is then handled individually. In particular this means that tuples can only be indexed by compile-time constants.
26+
27+
For example the following code:
28+
```python
29+
def f():
30+
a = (1, True, 3.0)
31+
print(a)
32+
b = a[0]+2
33+
return a[2]
34+
```
35+
is translated to the following C code:
36+
```c
37+
double f(void)
38+
{
39+
int64_t a_0;
40+
bool a_1;
41+
double a_2;
42+
int64_t b;
43+
a_0 = INT64_C(1);
44+
a_1 = 1;
45+
a_2 = 3.0;
46+
printf("%s%"PRId64"%s%s%s%.15lf%s\n", "(", a_0, ", ", a_1 ? "True" : "False", ", ", a_2, ")");
47+
b = a_0 + INT64_C(2);
48+
return a_2;
49+
}
50+
```
51+
and the following Fortran code:
52+
```fortran
53+
function f() result(Out_0001)
54+
55+
implicit none
56+
57+
real(f64) :: Out_0001
58+
integer(i64) :: a_0
59+
logical(b1) :: a_1
60+
real(f64) :: a_2
61+
integer(i64) :: b
62+
63+
a_0 = 1_i64
64+
a_1 = .True._b1
65+
a_2 = 3.0_f64
66+
write(stdout, '(A, I0, A, A, A, F0.15, A)', advance="no") '(' , a_0 &
67+
, ', ' , merge("True ", "False", a_1) , ', ' , a_2 , ')'
68+
write(stdout, '()', advance="yes")
69+
b = a_0 + 2_i64
70+
Out_0001 = a_2
71+
return
72+
73+
end function f
74+
```
75+
76+
But the following code will raise an error:
77+
```python
78+
def f():
79+
a = (1, True, 3.0)
80+
i = 2
81+
print(a[i])
82+
```
83+
```
84+
ERROR at annotation (semantic) stage
85+
pyccel:
86+
|fatal [semantic]: foo.py [4,10]| Inhomogeneous tuples must be indexed with constant integers for the type inference to work (a)
87+
```

docs/type_annotations.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ In general string type hints must be used to provide Pyccel with information abo
4141

4242
## Tuples
4343

44-
Currently tuples are supported locally in Pyccel but cannot be passed as arguments or returned. The implementation of the type annotations (as a first step to adding the missing support) is in progress. Currently homogeneous tuple type annotations are supported for local variables. Internally we handle homogeneous tuples as thought they were NumPy arrays. When creating multiple dimensional tuples it is therefore important to ensure that all objects have compatible sizes otherwise they will be handled as inhomogeneous tuples.
44+
Currently tuples are supported locally in Pyccel but cannot be passed as arguments or returned. The implementation of the type annotations (as a first step to adding the missing support) is in progress. Currently homogeneous tuple type annotations are supported for local variables. See [Container types in Pyccel](./containers.md#tuples) for more information about tuple handling. When creating multiple dimensional tuples it is therefore important to ensure that all objects have compatible sizes otherwise they will be handled as inhomogeneous tuples.
4545

4646
To declare a homogeneous tuple the syntax is as follows:
4747
```python

pyccel/ast/builtins.py

Lines changed: 4 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
from .literals import LiteralString
2828
from .operators import PyccelAdd, PyccelAnd, PyccelMul, PyccelIsNot
2929
from .operators import PyccelMinus, PyccelUnarySub, PyccelNot
30-
from .variable import IndexedElement, Variable, InhomogeneousTupleVariable
30+
from .variable import IndexedElement, Variable
3131

3232
pyccel_stage = PyccelStage()
3333

@@ -606,24 +606,12 @@ class PythonTupleFunction(TypedAstNode):
606606
different to the `(,)` syntax as it only takes one argument
607607
and unpacks any variables.
608608
609-
Parameters
610-
----------
611-
arg : TypedAstNode
612-
The argument passed to the function call.
609+
This class should not be used to create an instance, it is
610+
simply a place-holder to indicate the class to the semantic parser.
613611
"""
614612
__slots__ = ()
615613
_attribute_nodes = ()
616614

617-
def __new__(cls, arg):
618-
if isinstance(arg, PythonTuple):
619-
return arg
620-
elif isinstance(arg, (PythonList, InhomogeneousTupleVariable)):
621-
return PythonTuple(*arg)
622-
elif isinstance(arg.shape[0], LiteralInteger):
623-
return PythonTuple(*[arg[i] for i in range(arg.shape[0])])
624-
else:
625-
raise TypeError(f"Can't unpack {arg} into a tuple")
626-
627615
#==============================================================================
628616
class PythonLen(PyccelFunction):
629617
"""
@@ -1377,7 +1365,7 @@ class PythonType(PyccelFunction):
13771365
__slots__ = ('_type','_obj')
13781366
_attribute_nodes = ('_obj',)
13791367
_class_type = SymbolicType()
1380-
_shape = ()
1368+
_shape = None
13811369

13821370
def __init__(self, obj):
13831371
if not isinstance (obj, TypedAstNode):

pyccel/ast/numpyext.py

Lines changed: 2 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -697,30 +697,8 @@ class NumpyArray(NumpyNewArray):
697697

698698
def __init__(self, arg, dtype=None, order='K', ndmin=None):
699699

700-
if not isinstance(arg, (PythonTuple, PythonList, Variable, IndexedElement)):
701-
raise TypeError(f'Unknown type of {type(arg)}')
702-
703-
is_homogeneous_tuple = isinstance(arg.class_type, HomogeneousTupleType)
704-
# Inhomogeneous tuples can contain homogeneous data if it is inhomogeneous due to pointers
705-
if isinstance(arg.class_type, InhomogeneousTupleType):
706-
is_homogeneous_tuple = isinstance(arg.dtype, FixedSizeNumericType) and len(set(a.rank for a in arg))
707-
if not isinstance(arg, PythonTuple):
708-
arg = PythonTuple(*arg)
709-
710-
# TODO: treat inhomogenous lists and tuples when they have mixed ordering
711-
if not (is_homogeneous_tuple or isinstance(arg.class_type, HomogeneousContainerType)):
712-
raise TypeError('we only accept homogeneous arguments')
713-
714-
if not isinstance(order, (LiteralString, str)):
715-
raise TypeError("The order must be specified explicitly with a string.")
716-
elif isinstance(order, LiteralString):
717-
order = order.python_value
718-
719-
if ndmin is not None:
720-
if not isinstance(ndmin, (LiteralInteger, int)):
721-
raise TypeError("The minimum number of dimensions must be specified explicitly with an integer.")
722-
elif isinstance(ndmin, LiteralInteger):
723-
ndmin = ndmin.python_value
700+
assert isinstance(arg, (PythonTuple, PythonList, Variable, IndexedElement))
701+
assert isinstance(order, str)
724702

725703
init_dtype = dtype
726704

pyccel/ast/operators.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,9 @@ class PyccelUnarySub(PyccelUnary):
358358
def __repr__(self):
359359
return f'-{repr(self.args[0])}'
360360

361+
def __index__(self):
362+
return -int(self.args[0])
363+
361364
#==============================================================================
362365

363366
class PyccelNot(PyccelUnaryOperator):

pyccel/ast/utilities.py

Lines changed: 27 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from .builtins import (builtin_functions_dict,
2020
PythonRange, PythonList, PythonTuple, PythonSet)
2121
from .cmathext import cmath_mod
22-
from .datatypes import HomogeneousTupleType, PythonNativeInt
22+
from .datatypes import HomogeneousTupleType, InhomogeneousTupleType, PythonNativeInt
2323
from .internals import PyccelFunction, Slice
2424
from .itertoolsext import itertools_mod
2525
from .literals import LiteralInteger, LiteralEllipsis, Nil
@@ -29,9 +29,10 @@
2929
from .numpyext import (NumpyEmpty, NumpyArray, numpy_mod,
3030
NumpyTranspose, NumpyLinspace)
3131
from .operators import PyccelAdd, PyccelMul, PyccelIs, PyccelArithmeticOperator
32+
from .operators import PyccelUnarySub
3233
from .scipyext import scipy_mod
3334
from .typingext import typing_mod
34-
from .variable import (Variable, IndexedElement, InhomogeneousTupleVariable )
35+
from .variable import Variable, IndexedElement
3536

3637
from .c_concepts import ObjectAddress
3738

@@ -732,8 +733,8 @@ def expand_inhomog_tuple_assignments(block, language_has_vectors = False):
732733
block.substitute(allocs_to_unravel, new_allocs)
733734

734735
assigns = [a for a in block.get_attribute_nodes(Assign) \
735-
if isinstance(a.lhs, InhomogeneousTupleVariable) \
736-
and isinstance(a.rhs, (PythonTuple, InhomogeneousTupleVariable))]
736+
if isinstance(a.lhs.class_type, InhomogeneousTupleType) \
737+
and isinstance(a.rhs, (PythonTuple, Variable))]
737738
if len(assigns) != 0:
738739
new_assigns = [[Assign(l,r) for l,r in zip(a.lhs, a.rhs)] for a in assigns]
739740
block.substitute(assigns, new_assigns)
@@ -796,3 +797,25 @@ def expand_to_loops(block, new_index, scope, language_has_vectors = False):
796797
body = [bi for b in body for bi in b]
797798

798799
return body
800+
801+
#==============================================================================
802+
def is_literal_integer(expr):
803+
"""
804+
Determine whether the expression is a literal integer.
805+
806+
Determine whether the expression is a literal integer. A literal integer
807+
can be described by a LiteralInteger, a PyccelUnarySub(LiteralInteger) or
808+
a Constant.
809+
810+
Parameters
811+
----------
812+
expr : object
813+
Any Python object which should be analysed to determine whether it is an integer.
814+
815+
Returns
816+
-------
817+
bool
818+
True if the object represents a literal integer, false otherwise.
819+
"""
820+
return isinstance(expr, (int, LiteralInteger)) or \
821+
isinstance(expr, PyccelUnarySub) and isinstance(expr.args[0], (int, LiteralInteger))

0 commit comments

Comments
 (0)