From b02eca1414d0985db69b503db912f4dd3a714f6e Mon Sep 17 00:00:00 2001 From: Shaikh Ubaid Date: Thu, 17 Aug 2023 04:32:22 +0530 Subject: [PATCH 1/6] ASR: Support dataclass field() --- src/lpython/semantics/python_ast_to_asr.cpp | 29 +++++++++++++++++++++ src/runtime/lpython/lpython.py | 4 +-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/lpython/semantics/python_ast_to_asr.cpp b/src/lpython/semantics/python_ast_to_asr.cpp index 0ce768f9d2..c7376cb0dc 100644 --- a/src/lpython/semantics/python_ast_to_asr.cpp +++ b/src/lpython/semantics/python_ast_to_asr.cpp @@ -7802,6 +7802,35 @@ class BodyVisitor : public CommonVisitor { tmp = ASR::make_SizeOfType_t(al, x.base.base.loc, arg_type, size_type, nullptr); return ; + } else if( call_name == "field" ) { + if (x.n_args != 0) { + throw SemanticError("'field' expects only keyword arguments", x.base.base.loc); + } + + if (x.n_keywords != 1) { + throw SemanticError("'field' expects one keyword argument", x.base.base.loc); + } + + args.reserve(al, 1); + visit_expr_list(x.m_args, x.n_args, args); + + if( std::string(x.m_keywords[0].m_arg) != "default_factory" && std::string(x.m_keywords[0].m_arg) != "default" ) { + throw SemanticError("Unrecognised keyword argument, " + + std::string(x.m_keywords[0].m_arg), x.base.base.loc); + } + + if ( std::string(x.m_keywords[0].m_arg) == "default_factory") { + if (!AST::is_a(*x.m_keywords[0].m_value)) { + throw SemanticError("Only lambda functions currently supported as default_factory value", x.base.base.loc); + } + + AST::Lambda_t* lambda_fn = AST::down_cast(x.m_keywords[0].m_value); + this->visit_expr(*lambda_fn->m_body); + } else { + // field has default argument provided + this->visit_expr(*x.m_keywords[0].m_value); + } + return ; } else if( call_name == "f64" || call_name == "f32" || diff --git a/src/runtime/lpython/lpython.py b/src/runtime/lpython/lpython.py index 3ac9811c8c..028c731266 100644 --- a/src/runtime/lpython/lpython.py +++ b/src/runtime/lpython/lpython.py @@ -2,7 +2,7 @@ import os import ctypes import platform -from dataclasses import dataclass as py_dataclass, is_dataclass as py_is_dataclass +from dataclasses import dataclass as py_dataclass, is_dataclass as py_is_dataclass, field import functools @@ -11,7 +11,7 @@ "overload", "ccall", "TypeVar", "pointer", "c_p_pointer", "Pointer", "p_c_pointer", "vectorize", "inline", "Union", "static", "packed", "Const", "sizeof", "ccallable", "ccallback", "Callable", - "Allocatable", "In", "Out", "InOut", "dataclass", "S"] + "Allocatable", "In", "Out", "InOut", "dataclass", "field", "S"] # data-types From 9bdb90eb73d99aff6450944f847cc8d59f6d8158 Mon Sep 17 00:00:00 2001 From: Shaikh Ubaid Date: Thu, 17 Aug 2023 04:48:41 +0530 Subject: [PATCH 2/6] TEST: Add for dataclass field() --- integration_tests/CMakeLists.txt | 1 + integration_tests/structs_35.py | 26 ++++++++++++++++++++++++++ 2 files changed, 27 insertions(+) create mode 100644 integration_tests/structs_35.py diff --git a/integration_tests/CMakeLists.txt b/integration_tests/CMakeLists.txt index 580b9bbb9f..04be6abf67 100644 --- a/integration_tests/CMakeLists.txt +++ b/integration_tests/CMakeLists.txt @@ -685,6 +685,7 @@ RUN(NAME structs_31 LABELS cpython llvm c) RUN(NAME structs_32 LABELS cpython llvm c) RUN(NAME structs_33 LABELS cpython llvm c) RUN(NAME structs_34 LABELS cpython llvm c) +RUN(NAME structs_35 LABELS cpython llvm) RUN(NAME symbolics_01 LABELS cpython_sym c_sym llvm_sym NOFAST) RUN(NAME symbolics_02 LABELS cpython_sym c_sym llvm_sym NOFAST) diff --git a/integration_tests/structs_35.py b/integration_tests/structs_35.py new file mode 100644 index 0000000000..4bdb499d75 --- /dev/null +++ b/integration_tests/structs_35.py @@ -0,0 +1,26 @@ +from lpython import dataclass, field, i32 +from numpy import array + +@dataclass +class X: + a: i32 = 123 + b: bool = True + c: list[i32] = field(default_factory=lambda: [1, 2, 3]) + d: i32[3] = field(default_factory=lambda: array([4, 5, 6])) + e: i32 = field(default=-5) + +def main0(): + x: X = X() + print(x) + assert x.a == 123 + assert x.b == True + assert x.c[0] == 1 + assert x.d[1] == 5 + assert x.e == -5 + x.c[0] = 3 + x.d[0] = 3 + print(x) + assert x.c[0] == 3 + assert x.d[0] == 3 + +main0() From 78d4a5f640c753d5b0620629875a0e43074cc402 Mon Sep 17 00:00:00 2001 From: Shaikh Ubaid Date: Thu, 17 Aug 2023 05:01:20 +0530 Subject: [PATCH 3/6] TEST: Update to use dataclass field() for mutable types This is needed to make integration_tests compatible with CPython --- integration_tests/array_expr_03.py | 4 ++-- integration_tests/cast_01.py | 4 ++-- integration_tests/structs_04.py | 4 ++-- integration_tests/structs_09.py | 6 +++--- integration_tests/structs_10.py | 10 +++++----- integration_tests/structs_17.py | 6 +++--- integration_tests/structs_31.py | 4 ++-- integration_tests/structs_32.py | 4 ++-- integration_tests/structs_33.py | 4 ++-- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/integration_tests/array_expr_03.py b/integration_tests/array_expr_03.py index 46af5748a6..f0af73a57b 100644 --- a/integration_tests/array_expr_03.py +++ b/integration_tests/array_expr_03.py @@ -1,11 +1,11 @@ -from lpython import i8, i32, dataclass +from lpython import i8, i32, dataclass, field from numpy import empty, int8, array @dataclass class LPBHV_small: dim: i32 = 4 - a: i8[4] = empty(4, dtype=int8) + a: i8[4] = field(default_factory=lambda: empty(4, dtype=int8)) def g(): diff --git a/integration_tests/cast_01.py b/integration_tests/cast_01.py index 41c8a124f5..ee2b5b01df 100644 --- a/integration_tests/cast_01.py +++ b/integration_tests/cast_01.py @@ -1,10 +1,10 @@ -from lpython import i32, u8, u32, dataclass +from lpython import i32, u8, u32, dataclass, field from numpy import empty, uint8 @dataclass class LPBHV_small: dim : i32 = 4 - a : u8[4] = empty(4, dtype=uint8) + a : u8[4] = field(default_factory=lambda: empty(4, dtype=uint8)) def main0(): lphv_small : LPBHV_small = LPBHV_small() diff --git a/integration_tests/structs_04.py b/integration_tests/structs_04.py index 087fac365e..b1a1ad4307 100644 --- a/integration_tests/structs_04.py +++ b/integration_tests/structs_04.py @@ -1,4 +1,4 @@ -from lpython import i32, f32, f64, dataclass +from lpython import i32, f32, f64, dataclass, field from copy import deepcopy @dataclass @@ -9,7 +9,7 @@ class A: @dataclass class B: z: i32 - a: A = A(f32(0.0), 0) + a: A = field(default_factory=lambda: A(f32(0.0), 0)) def f(b: B): print(b.z, b.a.x, b.a.y) diff --git a/integration_tests/structs_09.py b/integration_tests/structs_09.py index b32d059afa..9ba7139cbc 100644 --- a/integration_tests/structs_09.py +++ b/integration_tests/structs_09.py @@ -1,4 +1,4 @@ -from lpython import i32, f32, f64, dataclass +from lpython import i32, f32, f64, dataclass, field @dataclass class C: @@ -7,13 +7,13 @@ class C: @dataclass class B: z: i32 - bc: C = C(f32(0.0)) + bc: C = field(default_factory=lambda: C(f32(0.0))) @dataclass class A: y: f32 x: i32 - b: B = B(0, C(f32(0.0))) + b: B = field(default_factory=lambda: B(0, C(f32(0.0)))) def f(a: A): diff --git a/integration_tests/structs_10.py b/integration_tests/structs_10.py index b6f2780778..db067ed825 100644 --- a/integration_tests/structs_10.py +++ b/integration_tests/structs_10.py @@ -1,18 +1,18 @@ -from lpython import i32, f64, dataclass +from lpython import i32, f64, dataclass, field from numpy import empty, float64 @dataclass class Mat: - mat: f64[2, 2] = empty((2, 2), dtype=float64) + mat: f64[2, 2] = field(default_factory=lambda: empty((2, 2), dtype=float64)) @dataclass class Vec: - vec: f64[2] = empty(2, dtype=float64) + vec: f64[2] = field(default_factory=lambda: empty(2, dtype=float64)) @dataclass class MatVec: - mat: Mat = Mat() - vec: Vec = Vec() + mat: Mat = field(default_factory=lambda: Mat()) + vec: Vec = field(default_factory=lambda: Vec()) def rotate(mat_vec: MatVec) -> f64[2]: rotated_vec: f64[2] = empty(2, dtype=float64) diff --git a/integration_tests/structs_17.py b/integration_tests/structs_17.py index 462432c9ba..30042f68f7 100644 --- a/integration_tests/structs_17.py +++ b/integration_tests/structs_17.py @@ -1,4 +1,4 @@ -from lpython import i32, f32, f64, dataclass +from lpython import i32, f32, f64, dataclass, field @dataclass class B: @@ -6,13 +6,13 @@ class B: @dataclass class C: cz: f32 - bc: C = C(f32(0.0)) + bc: C = field(default_factory=lambda: C(f32(0.0))) @dataclass class A: y: f32 x: i32 - b: B = B(0, B.C(f32(0.0))) + b: B = field(default_factory=lambda: B(0, B.C(f32(0.0)))) def f(a: A): diff --git a/integration_tests/structs_31.py b/integration_tests/structs_31.py index 172c6e6408..472bf8d8d4 100644 --- a/integration_tests/structs_31.py +++ b/integration_tests/structs_31.py @@ -1,4 +1,4 @@ -from lpython import packed, dataclass, i32, InOut +from lpython import packed, dataclass, field, i32, InOut @packed @dataclass @@ -8,7 +8,7 @@ class inner_struct: @packed @dataclass class outer_struct: - b: inner_struct = inner_struct(0) + b: inner_struct = field(default_factory=lambda: inner_struct(0)) def update_my_inner_struct(my_inner_struct: InOut[inner_struct]) -> None: my_inner_struct.a = 99999 diff --git a/integration_tests/structs_32.py b/integration_tests/structs_32.py index 32babac66d..6e57217a87 100644 --- a/integration_tests/structs_32.py +++ b/integration_tests/structs_32.py @@ -1,4 +1,4 @@ -from lpython import packed, dataclass, i32, InOut +from lpython import packed, dataclass, field, i32, InOut @packed @@ -10,7 +10,7 @@ class inner_struct: @packed @dataclass class outer_struct: - b: inner_struct = inner_struct(0) + b: inner_struct = field(default_factory=lambda: inner_struct(0)) def update_my_inner_struct(my_inner_struct: InOut[inner_struct]) -> None: diff --git a/integration_tests/structs_33.py b/integration_tests/structs_33.py index c8f02cb8e6..24a537447a 100644 --- a/integration_tests/structs_33.py +++ b/integration_tests/structs_33.py @@ -1,4 +1,4 @@ -from lpython import packed, dataclass, i32, ccallback, CPtr, ccall +from lpython import packed, dataclass, field, i32 # test issue 2125 @@ -11,7 +11,7 @@ class inner_struct: @packed @dataclass class outer_struct: - inner_s : inner_struct = inner_struct() + inner_s : inner_struct = field(default_factory=lambda: inner_struct()) def check() -> None: From 4a0ad3413b04b06f857747dd54060dd060e8de71 Mon Sep 17 00:00:00 2001 From: Shaikh Ubaid Date: Mon, 14 Aug 2023 13:30:20 +0530 Subject: [PATCH 4/6] CI: Test Integration tests with Python 3.11 --- .github/workflows/CI.yml | 55 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 05235fe84f..bfba47443a 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -444,3 +444,58 @@ jobs: cd integration_tests ./run_tests.py -b c_sym cpython_sym llvm_sym ./run_tests.py -b c_sym cpython_sym llvm_sym -f + + python_3_11: + name: Run Integration tests with Python 3.11 + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: mamba-org/setup-micromamba@v1 + with: + environment-name: lp + condarc: | + channels: + - conda-forge + create-args: >- + llvmdev=11.1.0 + bison=3.4 + re2c + zlib + cmake + make + python=3.11.4 + numpy + + - uses: hendrikmuhs/ccache-action@main + with: + key: ${{ github.job }}-${{ matrix.os }} + + - name: Show Python version + shell: bash -e -l {0} + run: python --version + + - name: Build + shell: bash -e -l {0} + run: | + ./build0.sh + cmake . -G"Unix Makefiles" \ + -DCMAKE_BUILD_TYPE=Debug \ + -DWITH_LLVM=yes \ + -DLPYTHON_BUILD_ALL=yes \ + -DWITH_STACKTRACE=no \ + -DWITH_RUNTIME_STACKTRACE=no \ + -DCMAKE_PREFIX_PATH="$CONDA_PREFIX" \ + -DCMAKE_INSTALL_PREFIX=`pwd`/inst \ + -DCMAKE_C_COMPILER_LAUNCHER=ccache \ + -DCMAKE_CXX_COMPILER_LAUNCHER=ccache + + cmake --build . -j16 --target install + + - name: Test + shell: bash -e -l {0} + run: | + cd integration_tests + ./run_tests.py -b cpython From a4928c334697341babb76f8ba8278205378b7013 Mon Sep 17 00:00:00 2001 From: Shaikh Ubaid Date: Thu, 17 Aug 2023 05:07:05 +0530 Subject: [PATCH 5/6] TEST: Update reference tests --- tests/reference/asr-structs_04-387747b.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/reference/asr-structs_04-387747b.json b/tests/reference/asr-structs_04-387747b.json index a6a7a82ac7..d09bcbac44 100644 --- a/tests/reference/asr-structs_04-387747b.json +++ b/tests/reference/asr-structs_04-387747b.json @@ -2,7 +2,7 @@ "basename": "asr-structs_04-387747b", "cmd": "lpython --show-asr --no-color {infile} -o {outfile}", "infile": "tests/../integration_tests/structs_04.py", - "infile_hash": "c19af3c3fbac1430c22c5aaf69aea7c622faa9d7c4e7734edbd0066d", + "infile_hash": "1e20c2ac044ab88183c50ecb481ac7c50992ed622f8bb94772c6df25", "outfile": null, "outfile_hash": null, "stdout": "asr-structs_04-387747b.stdout", From e000102940f9f8706b8fd2c48b4f54438179d720 Mon Sep 17 00:00:00 2001 From: Shaikh Ubaid Date: Mon, 14 Aug 2023 16:10:21 +0530 Subject: [PATCH 6/6] lpython.py: Fix python_lib and rtlib paths for JIT --- src/runtime/lpython/lpython.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/runtime/lpython/lpython.py b/src/runtime/lpython/lpython.py index 028c731266..073c8a4d3f 100644 --- a/src/runtime/lpython/lpython.py +++ b/src/runtime/lpython/lpython.py @@ -717,9 +717,9 @@ def get_rtlib_dir(): python_path = "-I" + get_python_inc() + " " numpy_path = "-I" + get_include() + " " rt_path_01 = "-I" + get_rtlib_dir() + "/../libasr/runtime " - rt_path_02 = "-L" + get_rtlib_dir() + " -Wl,-rpath " \ + rt_path_02 = "-L" + get_rtlib_dir() + " -Wl,-rpath," \ + get_rtlib_dir() + " -llpython_runtime " - python_lib = "-L" + get_python_lib() + "/../.. -lpython" + \ + python_lib = "-L" + get_python_lib() + "/../.." + f" -Wl,-rpath,{get_python_lib()+'/../..'}" + " -lpython" + \ get_python_version() + " -lm" # ----------------------------------------------------------------------