Skip to content

Commit 4967933

Browse files
authored
Add support for type aliases (pyccel#1906)
Add support for non-parametrised type aliases as described at https://docs.python.org/3/library/typing.html#type-aliases. Fixes pyccel#1544 **Commit Summary** - Add a `TypeAlias` datatype - Add support for `typing.TypeAlias` - Ensure errors are raised when symbolic objects are redefined - Avoid `class_type.name` (only available for types of user-defined classes) - Remove dead code "handling lambdify" - Stop calling `insert_symbolic_function` (unused, see pyccel#330 ) - Add tests
1 parent d82bb62 commit 4967933

File tree

14 files changed

+247
-47
lines changed

14 files changed

+247
-47
lines changed

.pylintrc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,8 @@ disable=invalid-name,
102102
duplicate-code,
103103
cyclic-import, # Output not consistent between pylint calls. This should be reactivated if cyclic dependencies can be fixed.
104104
unused-wildcard-import, # Raised by everything not used in pyccel.errors.messages when using 'from pyccel.errors.messages import *'
105-
isinstance-second-argument-not-valid-type # prevents doing isinstance(a, acceptable_iterable_types)
105+
isinstance-second-argument-not-valid-type, # prevents doing isinstance(a, acceptable_iterable_types)
106+
syntax-error # Ignore syntax errors (usually from testing future Python versions). If the file doesn't run this will be obvious in the tests
106107

107108

108109
# Enable the message, report, category or checker with the given id(s). You can

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ All notable changes to this project will be documented in this file.
3030
- #1944 : Add the appropriate C language equivalent for declaring a Python `dict` container using the STC library.
3131
- #1936 : Add missing C output for inline decorator example in documentation
3232
- #1937 : Optimise `pyccel.ast.basic.PyccelAstNode.substitute` method.
33+
- #1544 : Add support for `typing.TypeAlias`.
3334
- \[INTERNALS\] Added `container_rank` property to `ast.datatypes.PyccelType` objects.
3435
- \[DEVELOPER\] Added an improved traceback to the developer-mode errors for errors in function calls.
3536

docs/type_annotations.md

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,3 +93,25 @@ def f(a : 'T', b : 'T'):
9393
```
9494

9595
For more details, see the documentation for [templates](./templates.md).
96+
97+
## Type Aliases
98+
99+
Python also provides type alias objects as described in the Python docs (<https://docs.python.org/3/library/typing.html#type-aliases>). For the moment type parameter lists are not supported. Both the new Python 3.12 syntax and the old syntax are supported. Type aliases cannot be redefined. This allows the user to more easily change between different types. The type name will not appear in the underlying code.
100+
101+
E.g.
102+
```python
103+
from typing import TypeAlias
104+
105+
MyType : TypeAlias = float
106+
107+
def set_i(x : 'MyType[:]', i : 'int', val : MyType):
108+
x[i] = val
109+
```
110+
111+
or:
112+
```python
113+
type MyType = float
114+
115+
def set_i(x : 'MyType[:]', i : 'int', val : MyType):
116+
x[i] = val
117+
```

pyccel/ast/class_defs.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
from .core import ClassDef, PyccelFunctionDef
1717
from .datatypes import (PythonNativeBool, PythonNativeInt, PythonNativeFloat,
1818
PythonNativeComplex, StringType, TupleType, CustomDataType,
19-
HomogeneousListType, HomogeneousSetType, DictType)
19+
HomogeneousListType, HomogeneousSetType, DictType, SymbolicType)
2020
from .numpyext import (NumpyShape, NumpySum, NumpyAmin, NumpyAmax,
2121
NumpyImag, NumpyReal, NumpyTranspose,
2222
NumpyConjugate, NumpySize, NumpyResultType, NumpyArray)
@@ -251,7 +251,7 @@ def get_cls_base(class_type):
251251
NotImplementedError
252252
Raised if the base class cannot be found.
253253
"""
254-
if isinstance(class_type, CustomDataType):
254+
if isinstance(class_type, (CustomDataType, SymbolicType)):
255255
return None
256256
elif class_type in literal_classes:
257257
return literal_classes[class_type]

pyccel/ast/datatypes.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
'GenericType',
3939
'SymbolicType',
4040
'CharType',
41+
'TypeAlias',
4142
# ------------ Container types ------------
4243
'TupleType',
4344
'HomogeneousContainerType',
@@ -472,6 +473,21 @@ class CharType(FixedSizeType):
472473
_name = 'char'
473474
_primitive_type = PrimitiveCharacterType
474475

476+
#==============================================================================
477+
class TypeAlias(SymbolicType):
478+
"""
479+
Class representing the type of a symbolic object describing a type descriptor.
480+
481+
Class representing the type of a symbolic object describing a type descriptor.
482+
This type is equivalent to Python's built-in typing.TypeAlias.
483+
484+
See Also
485+
--------
486+
typing.TypeAlias : <https://docs.python.org/3/library/typing.html#typing.TypeAlias>.
487+
"""
488+
__slots__ = ()
489+
_name = 'TypeAlias'
490+
475491
#==============================================================================
476492

477493
class ContainerType(PyccelType):

pyccel/ast/typingext.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,11 @@
99

1010
from .basic import TypedAstNode
1111
from .core import Module, PyccelFunctionDef
12+
from .datatypes import TypeAlias
1213

1314
__all__ = (
1415
'TypingFinal',
16+
'TypingTypeAlias',
1517
'typing_mod'
1618
)
1719

@@ -45,10 +47,24 @@ def arg(self):
4547
"""
4648
return self._arg
4749

50+
#==============================================================================
51+
class TypingTypeAlias(TypedAstNode):
52+
"""
53+
Class representing a call to the typing.TypeAlias construct.
54+
55+
Class representing a call to the typing.TypeAlias construct. This object
56+
is only used for type annotations. It is useful for creating a PyccelFunctionDef
57+
but instances should not be created.
58+
"""
59+
__slots__ = ()
60+
_attribute_nodes = ()
61+
_static_type = TypeAlias()
62+
4863
#==============================================================================
4964

5065
typing_funcs = {
5166
'Final': PyccelFunctionDef('Final', TypingFinal),
67+
'TypeAlias': PyccelFunctionDef('TypeAlias', TypingTypeAlias),
5268
}
5369

5470
typing_mod = Module('typing',

pyccel/parser/semantic.py

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@
6565
from pyccel.ast.datatypes import PythonNativeBool, PythonNativeInt, PythonNativeFloat
6666
from pyccel.ast.datatypes import DataTypeFactory, PrimitiveFloatingPointType
6767
from pyccel.ast.datatypes import InhomogeneousTupleType, HomogeneousTupleType, HomogeneousSetType, HomogeneousListType
68-
from pyccel.ast.datatypes import PrimitiveComplexType, FixedSizeNumericType, DictType
68+
from pyccel.ast.datatypes import PrimitiveComplexType, FixedSizeNumericType, DictType, TypeAlias
6969

7070
from pyccel.ast.functionalexpr import FunctionalSum, FunctionalMax, FunctionalMin, GeneratorComprehension, FunctionalFor
7171

@@ -368,7 +368,7 @@ def check_for_variable(self, name):
368368
class_def = prefix.cls_base
369369
except AttributeError:
370370
class_def = get_cls_base(prefix.class_type) or \
371-
self.scope.find(prefix.class_type.name, 'classes')
371+
self.scope.find(str(prefix.class_type), 'classes')
372372

373373
attr_name = name.name[-1]
374374
class_scope = class_def.scope
@@ -689,6 +689,9 @@ def _infer_type(self, expr):
689689
dict
690690
Dictionary containing all the type information which was inferred.
691691
"""
692+
if not isinstance(expr, TypedAstNode):
693+
return {'class_type' : SymbolicType()}
694+
692695
d_var = {
693696
'class_type' : expr.class_type,
694697
'shape' : expr.shape,
@@ -1410,6 +1413,10 @@ def _assign_lhs_variable(self, lhs, d_var, rhs, new_expressions, is_augassign,ar
14101413
else:
14111414
var = None
14121415
else:
1416+
symbolic_var = self.scope.find(lhs, 'symbolic_alias')
1417+
if symbolic_var:
1418+
errors.report(f"{lhs} variable represents a symbolic concept. Its value cannot be changed.",
1419+
severity='fatal')
14131420
var = self.scope.find(lhs)
14141421

14151422
# Variable not yet declared (hence array not yet allocated)
@@ -1936,6 +1943,8 @@ def _get_indexed_type(self, base, args, expr):
19361943
for t in annotation.type_list:
19371944
t.is_const = True
19381945
return annotation
1946+
elif isinstance(base, UnionTypeAnnotation):
1947+
return UnionTypeAnnotation(*[self._get_indexed_type(t, args, expr) for t in base.type_list])
19391948

19401949
if all(isinstance(a, Slice) for a in args):
19411950
rank = len(args)
@@ -2472,7 +2481,7 @@ def _visit_Slice(self, expr):
24722481
def _visit_IndexedElement(self, expr):
24732482
var = self._visit(expr.base)
24742483

2475-
if isinstance(var, (PyccelFunctionDef, VariableTypeAnnotation)):
2484+
if isinstance(var, (PyccelFunctionDef, VariableTypeAnnotation, UnionTypeAnnotation)):
24762485
return self._get_indexed_type(var, expr.indices, expr)
24772486

24782487
# TODO check consistency of indices with shape/rank
@@ -2584,7 +2593,7 @@ def _visit_AnnotatedPyccelSymbol(self, expr):
25842593
possible_args.append(address)
25852594
elif isinstance(t, VariableTypeAnnotation):
25862595
class_type = t.class_type
2587-
cls_base = get_cls_base(class_type) or self.scope.find(class_type.name, 'classes')
2596+
cls_base = self.scope.find(str(class_type), 'classes') or get_cls_base(class_type)
25882597
v = var_class(class_type, name, cls_base = cls_base,
25892598
shape = None,
25902599
is_const = t.is_const, is_optional = False,
@@ -2628,6 +2637,8 @@ def _visit_SyntacticTypeAnnotation(self, expr):
26282637
raise errors.report(PYCCEL_RESTRICTION_TODO + ' Could not deduce type information',
26292638
severity='fatal', symbol=expr)
26302639

2640+
def _visit_VariableTypeAnnotation(self, expr):
2641+
return expr
26312642

26322643
def _visit_DottedName(self, expr):
26332644

@@ -2698,7 +2709,7 @@ def _visit_DottedName(self, expr):
26982709
class_type = d_var['class_type']
26992710
cls_base = get_cls_base(class_type)
27002711
if cls_base is None:
2701-
cls_base = self.scope.find(class_type.name, 'classes')
2712+
cls_base = self.scope.find(str(class_type), 'classes')
27022713

27032714
# look for a class method
27042715
if isinstance(rhs, FunctionCall):
@@ -2980,6 +2991,35 @@ def _visit_Assign(self, expr):
29802991
rhs = expr.rhs
29812992
lhs = expr.lhs
29822993

2994+
if isinstance(lhs, AnnotatedPyccelSymbol):
2995+
semantic_lhs = self._visit(lhs)
2996+
if len(semantic_lhs) != 1:
2997+
errors.report("Cannot declare variable with multiple types",
2998+
symbol=expr, severity='error')
2999+
semantic_lhs_var = semantic_lhs[0]
3000+
if isinstance(semantic_lhs_var, DottedVariable):
3001+
cls_def = semantic_lhs_var.lhs.cls_base
3002+
insert_scope = cls_def.scope
3003+
cls_def.add_new_attribute(semantic_lhs_var)
3004+
else:
3005+
insert_scope = self.scope
3006+
3007+
lhs = lhs.name
3008+
if semantic_lhs_var.class_type is TypeAlias():
3009+
if not isinstance(rhs, SyntacticTypeAnnotation):
3010+
pyccel_stage.set_stage('syntactic')
3011+
rhs = SyntacticTypeAnnotation(rhs)
3012+
pyccel_stage.set_stage('semantic')
3013+
type_annot = self._visit(rhs)
3014+
self.scope.insert_symbolic_alias(lhs, type_annot)
3015+
return EmptyNode()
3016+
3017+
try:
3018+
insert_scope.insert_variable(semantic_lhs_var)
3019+
except RuntimeError as e:
3020+
errors.report(e, symbol=expr, severity='error')
3021+
3022+
29833023
# Steps before visiting
29843024
if isinstance(rhs, GeneratorComprehension):
29853025
rhs.substitute(rhs.lhs, lhs)
@@ -3024,10 +3064,12 @@ def _visit_Assign(self, expr):
30243064
d_m_args = {arg.value.name:arg.value for arg in macro.master_arguments
30253065
if isinstance(arg.value, Variable)}
30263066

3027-
if not sympy_iterable(lhs):
3028-
lhs = [lhs]
3067+
lhs_iter = lhs
3068+
3069+
if not sympy_iterable(lhs_iter):
3070+
lhs_iter = [lhs]
30293071
results_shapes = macro.get_results_shapes(args)
3030-
for m_result, shape, result in zip(macro.results, results_shapes, lhs):
3072+
for m_result, shape, result in zip(macro.results, results_shapes, lhs_iter):
30313073
if m_result in d_m_args and not result in args_names:
30323074
d_result = self._infer_type(d_m_args[m_result])
30333075
d_result['shape'] = shape
@@ -3071,14 +3113,6 @@ def _visit_Assign(self, expr):
30713113
return rhs
30723114
if isinstance(rhs, ConstructorCall):
30733115
return rhs
3074-
elif isinstance(rhs, FunctionDef):
3075-
3076-
# case of lambdify
3077-
3078-
rhs = rhs.rename(expr.lhs.name)
3079-
for i in rhs.body:
3080-
i.set_current_ast(python_ast)
3081-
return rhs
30823116

30833117
elif isinstance(rhs, CodeBlock) and len(rhs.body)>1 and isinstance(rhs.body[1], FunctionalFor):
30843118
return rhs
@@ -3148,25 +3182,6 @@ def _visit_Assign(self, expr):
31483182
# case of rhs is a target variable the lhs must be a pointer
31493183
d['memory_handling'] = 'alias'
31503184

3151-
lhs = expr.lhs
3152-
if isinstance(lhs, AnnotatedPyccelSymbol):
3153-
semantic_lhs = self._visit(lhs)
3154-
if len(semantic_lhs) != 1:
3155-
errors.report("Cannot declare variable with multiple types",
3156-
symbol=expr, severity='error')
3157-
semantic_lhs_var = semantic_lhs[0]
3158-
if isinstance(semantic_lhs_var, DottedVariable):
3159-
cls_def = semantic_lhs_var.lhs.cls_base
3160-
insert_scope = cls_def.scope
3161-
cls_def.add_new_attribute(semantic_lhs_var)
3162-
else:
3163-
insert_scope = self.scope
3164-
try:
3165-
insert_scope.insert_variable(semantic_lhs_var)
3166-
except RuntimeError as e:
3167-
errors.report(e, symbol=expr, severity='error')
3168-
lhs = lhs.name
3169-
31703185
if isinstance(lhs, (PyccelSymbol, DottedName)):
31713186
if isinstance(d_var, list):
31723187
if len(d_var) == 1:
@@ -3321,12 +3336,9 @@ def _visit_Assign(self, expr):
33213336
# it is then treated as a def node
33223337

33233338
F = self.scope.find(l, 'symbolic_functions')
3324-
if F is None:
3325-
self.insert_symbolic_function(new_expr)
3326-
else:
3327-
errors.report(PYCCEL_RESTRICTION_TODO,
3328-
bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
3329-
severity='fatal')
3339+
errors.report(PYCCEL_RESTRICTION_TODO,
3340+
bounding_box=(self.current_ast_node.lineno, self.current_ast_node.col_offset),
3341+
severity='fatal')
33303342

33313343
new_expressions.append(new_expr)
33323344

pyccel/parser/syntactic.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,8 @@
4242
from pyccel.ast.core import CodeBlock
4343
from pyccel.ast.core import IndexedElement
4444

45+
from pyccel.ast.datatypes import TypeAlias
46+
4547
from pyccel.ast.bitwise_operators import PyccelRShift, PyccelLShift, PyccelBitXor, PyccelBitOr, PyccelBitAnd, PyccelInvert
4648
from pyccel.ast.operators import PyccelPow, PyccelAdd, PyccelMul, PyccelDiv, PyccelMod, PyccelFloorDiv
4749
from pyccel.ast.operators import PyccelEq, PyccelNe, PyccelLt, PyccelLe, PyccelGt, PyccelGe
@@ -62,7 +64,7 @@
6264

6365
from pyccel.ast.internals import Slice, PyccelSymbol, PyccelFunction
6466

65-
from pyccel.ast.type_annotations import SyntacticTypeAnnotation, UnionTypeAnnotation
67+
from pyccel.ast.type_annotations import SyntacticTypeAnnotation, UnionTypeAnnotation, VariableTypeAnnotation
6668

6769
from pyccel.parser.base import BasicParser
6870
from pyccel.parser.extend_tree import extend_tree
@@ -1353,6 +1355,17 @@ def _visit_Yield(self, stmt):
13531355
def _visit_Starred(self, stmt):
13541356
return StarredArguments(self._visit(stmt.value))
13551357

1358+
def _visit_TypeAlias(self, stmt):
1359+
if stmt.type_params:
1360+
errors.report("Type parameters are not yet supported on a type alias expression.\n"+PYCCEL_RESTRICTION_TODO,
1361+
severity='error', symbol=stmt)
1362+
self._in_lhs_assign = True
1363+
name = self._visit(stmt.name)
1364+
self._in_lhs_assign = False
1365+
rhs = self._treat_type_annotation(stmt.value, self._visit(stmt.value))
1366+
type_annotation = UnionTypeAnnotation(VariableTypeAnnotation(TypeAlias(), is_const = True))
1367+
return Assign(AnnotatedPyccelSymbol(name, annotation=type_annotation), rhs)
1368+
13561369
#==============================================================================
13571370

13581371

tests/epyccel/modules/Module_10.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# pylint: disable=missing-function-docstring, missing-module-docstring
2+
3+
type MyType = float
4+
5+
def set_i(x : 'MyType[:]', i : 'int', val : MyType):
6+
x[i] = val
7+
8+
def swap(x : 'MyType[:]', i : 'int', j : 'int'):
9+
temp = x[i]
10+
x[i] = x[j]
11+
x[j] = temp
12+
13+
def inplace_max(x : 'MyType[:]'):
14+
n = x.shape[0]
15+
# bubble sort
16+
for j in range(n):
17+
i = n-1-j
18+
while i < n-1:
19+
if x[i] > x[i+1]:
20+
swap(x, i, i+1)
21+
else:
22+
i+=1
23+
return x[-1]
24+
25+
def f(x : 'MyType[:]', y : 'MyType[:]'):
26+
n = x.shape[0]
27+
for i in range(n):
28+
set_i(x, i, float(i))
29+
for i in range(n//3):
30+
set_i(x[::3], i, -1.0)
31+
b = inplace_max(y[:])
32+
return b

0 commit comments

Comments
 (0)