diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index b146ec38d0..42979abdce 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -227,7 +227,7 @@ jobs: run: | # Package lpynn has lpython_emulation as dependency # Hence, it should by default install lpython_emulation - python -m pip install lpython_emulation==0.0.1.8 lpynn==0.0.1.3 numpy==1.24.3 + python -m pip install lpython_emulation==0.0.1.9 lpynn==0.0.1.4 numpy==1.24.3 - name: PIP show installed packages shell: bash -l {0} diff --git a/integration_tests/lnn/perceptron/perceptron_main.py b/integration_tests/lnn/perceptron/perceptron_main.py index 924c2d4f14..a60fe8a4e8 100644 --- a/integration_tests/lnn/perceptron/perceptron_main.py +++ b/integration_tests/lnn/perceptron/perceptron_main.py @@ -1,4 +1,4 @@ -from lpython import dataclass, i32, f64 +from lpython import dataclass, i32, f64, InOut from sys import exit @dataclass @@ -27,7 +27,7 @@ def init_weights(size: i32) -> list[f64]: weights.append(0.0) # append bias return weights -def init_perceptron(p: Perceptron, n: i32, rate: f64, iterations_limit: i32, des_accuracy: f64): +def init_perceptron(p: InOut[Perceptron], n: i32, rate: f64, iterations_limit: i32, des_accuracy: f64): if (n < 1 or n > 1000): print("no_of_inputs must be between [1, 1000]") exit(1) @@ -39,7 +39,7 @@ def init_perceptron(p: Perceptron, n: i32, rate: f64, iterations_limit: i32, des p.cur_accuracy = 0.0 p.epochs_cnt = 0 -def train_perceptron(p: Perceptron, input_vector: list[f64], actual_output: i32): +def train_perceptron(p: InOut[Perceptron], input_vector: list[f64], actual_output: i32): predicted_output: i32 = predict_perceptron(p, input_vector) error: i32 = actual_output - predicted_output i: i32 @@ -65,7 +65,7 @@ def train_epoch(p: Perceptron, input_vectors: list[list[f64]], outputs: list[i32 if predict_perceptron(p, input_vector) != outputs[i]: train_perceptron(p, input_vector, outputs[i]) -def train_dataset(p: Perceptron, input_vectors: list[list[f64]], outputs: list[i32]): +def train_dataset(p: InOut[Perceptron], input_vectors: list[list[f64]], outputs: list[i32]): p.cur_accuracy = 0.0 p.epochs_cnt = 0 while p.cur_accuracy < p.des_accuracy and p.epochs_cnt < p.iterations_limit: diff --git a/integration_tests/lnn/regression/regression_main.py b/integration_tests/lnn/regression/regression_main.py index 9caa4983db..f5ad9979f7 100644 --- a/integration_tests/lnn/regression/regression_main.py +++ b/integration_tests/lnn/regression/regression_main.py @@ -1,4 +1,4 @@ -from lpython import dataclass, i32, f64 +from lpython import dataclass, i32, f64, InOut from sys import exit @dataclass @@ -27,7 +27,7 @@ def init_weights(size: i32) -> list[f64]: weights.append(0.0) # append bias return weights -def init_perceptron(p: Perceptron, n: i32, rate: f64, iterations_limit: i32, err_limit: f64): +def init_perceptron(p: InOut[Perceptron], n: i32, rate: f64, iterations_limit: i32, err_limit: f64): p.no_of_inputs = n p.weights = init_weights(n) p.learn_rate = rate @@ -36,7 +36,7 @@ def init_perceptron(p: Perceptron, n: i32, rate: f64, iterations_limit: i32, err p.err = 1.0 p.epochs_cnt = 0 -def train_perceptron(p: Perceptron, input_vector: list[f64], actual_output: f64): +def train_perceptron(p: InOut[Perceptron], input_vector: list[f64], actual_output: f64): predicted_output: f64 = predict_perceptron(p, input_vector) error: f64 = actual_output - predicted_output i: i32 @@ -60,7 +60,7 @@ def train_epoch(p: Perceptron, input_vectors: list[list[f64]], outputs: list[f64 if predict_perceptron(p, input_vector) != outputs[i]: train_perceptron(p, input_vector, outputs[i]) -def train_dataset(p: Perceptron, input_vectors: list[list[f64]], outputs: list[f64]): +def train_dataset(p: InOut[Perceptron], input_vectors: list[list[f64]], outputs: list[f64]): prev_err: f64 = 0.0 p.err = 1.0 p.epochs_cnt = 0 diff --git a/integration_tests/lnn/utils/utils_main.py b/integration_tests/lnn/utils/utils_main.py index fd0b67c232..4ee4f32f66 100644 --- a/integration_tests/lnn/utils/utils_main.py +++ b/integration_tests/lnn/utils/utils_main.py @@ -1,4 +1,4 @@ -from lpython import i32, f64 +from lpython import i32, f64, InOut def normalize(value: f64, leftMin: f64, leftMax: f64, rightMin: f64, rightMax: f64) -> f64: # Figure out how 'wide' each range is @@ -11,7 +11,7 @@ def normalize(value: f64, leftMin: f64, leftMax: f64, rightMin: f64, rightMax: f # Convert the 0-1 range into a value in the right range. return rightMin + (valueScaled * rightSpan) -def normalize_input_vectors(input_vectors: list[list[f64]]): +def normalize_input_vectors(input_vectors: InOut[list[list[f64]]]): rows: i32 = len(input_vectors) cols: i32 = len(input_vectors[0]) @@ -29,7 +29,7 @@ def normalize_input_vectors(input_vectors: list[list[f64]]): for i in range(rows): input_vectors[i][j] = normalize(input_vectors[i][j], colMinVal, colMaxVal, -1.0, 1.0) -def normalize_output_vector(output_vector: list[f64]): +def normalize_output_vector(output_vector: InOut[list[f64]]): rows: i32 = len(output_vector) colMinVal: f64 = output_vector[0] colMaxVal: f64 = output_vector[0] diff --git a/integration_tests/print_list_tuple_01.py b/integration_tests/print_list_tuple_01.py index 841c8baea5..2c599d9c0a 100644 --- a/integration_tests/print_list_tuple_01.py +++ b/integration_tests/print_list_tuple_01.py @@ -1,4 +1,4 @@ -from lpython import f64, i32, c64 +from lpython import f64, i32, c64, InOut def test_print_list(): @@ -20,7 +20,7 @@ def test_print_list(): print(x, y, z, t) -def f(y: list[i32]) -> list[i32]: +def f(y: InOut[list[i32]]) -> list[i32]: y.append(4) return y diff --git a/integration_tests/print_list_tuple_02.py b/integration_tests/print_list_tuple_02.py index 3db9c45a1f..57666c50e5 100644 --- a/integration_tests/print_list_tuple_02.py +++ b/integration_tests/print_list_tuple_02.py @@ -1,6 +1,6 @@ -from lpython import i32, f64 +from lpython import i32, f64, InOut -def insert_tuples_into_list(l: list[tuple[i32, f64, str]], size: i32) -> list[tuple[i32, f64, str]]: +def insert_tuples_into_list(l: InOut[list[tuple[i32, f64, str]]], size: i32) -> list[tuple[i32, f64, str]]: i: i32 string: str t: tuple[i32, f64, str] diff --git a/integration_tests/structs_01.py b/integration_tests/structs_01.py index e588116414..7ad860f5f2 100644 --- a/integration_tests/structs_01.py +++ b/integration_tests/structs_01.py @@ -1,4 +1,4 @@ -from lpython import i32, f32, f64, dataclass +from lpython import i32, f32, f64, dataclass, InOut @dataclass class A: @@ -9,7 +9,7 @@ def f(a: A): print(a.x) print(a.y) -def change_struct(a: A): +def change_struct(a: InOut[A]): a.x = a.x + 1 a.y = a.y + f32(1) diff --git a/integration_tests/structs_05.py b/integration_tests/structs_05.py index b730a5387b..7d66f41a58 100644 --- a/integration_tests/structs_05.py +++ b/integration_tests/structs_05.py @@ -1,4 +1,4 @@ -from lpython import i32, f64, i64, i16, i8, f32, dataclass +from lpython import i32, f64, i64, i16, i8, f32, dataclass, InOut from numpy import empty @dataclass @@ -33,7 +33,7 @@ def verify(s: A[:], x1: i32, y1: f64, x2: i32, y2: f64): assert s1.c == i8(x2) assert s1.d -def update_1(s: A): +def update_1(s: InOut[A]): s.x = 2 s.y = 1.2 s.z = i64(2) diff --git a/integration_tests/test_list_03.py b/integration_tests/test_list_03.py index dc69e445b7..1cb6953e7d 100644 --- a/integration_tests/test_list_03.py +++ b/integration_tests/test_list_03.py @@ -1,4 +1,4 @@ -from lpython import i32 +from lpython import i32, InOut def test_list_01(n: i32) -> i32: a: list[i32] = [] @@ -10,7 +10,7 @@ def test_list_01(n: i32) -> i32: sum += a[i] return sum -def test_list_insert_02(x: list[i32], n: i32) -> list[i32]: +def test_list_insert_02(x: InOut[list[i32]], n: i32) -> list[i32]: i: i32 imod: i32 for i in range(n): diff --git a/integration_tests/test_list_05.py b/integration_tests/test_list_05.py index 325e047b6c..7708b95dec 100644 --- a/integration_tests/test_list_05.py +++ b/integration_tests/test_list_05.py @@ -1,4 +1,4 @@ -from lpython import i32, f64 +from lpython import i32, f64, InOut def check_list_of_tuples(l: list[tuple[i32, f64, str]], sign: i32): size: i32 = len(l) @@ -29,7 +29,7 @@ def fill_list_of_tuples(size: i32) -> list[tuple[i32, f64, str]]: return l1 -def insert_tuples_into_list(l: list[tuple[i32, f64, str]], size: i32) -> list[tuple[i32, f64, str]]: +def insert_tuples_into_list(l: InOut[list[tuple[i32, f64, str]]], size: i32) -> list[tuple[i32, f64, str]]: i: i32 string: str t: tuple[i32, f64, str] diff --git a/integration_tests/test_list_08.py b/integration_tests/test_list_08.py index 9b120f15ae..f5a2cd33f3 100644 --- a/integration_tests/test_list_08.py +++ b/integration_tests/test_list_08.py @@ -1,7 +1,7 @@ -from lpython import i32, f64 +from lpython import i32, f64, InOut from copy import deepcopy -def sort(l: list[i32]) -> list[i32]: +def sort(l: InOut[list[i32]]) -> list[i32]: i: i32; j: i32 for i in range(len(l)): diff --git a/src/lpython/semantics/python_ast_to_asr.cpp b/src/lpython/semantics/python_ast_to_asr.cpp index 8c4dbbc497..e0c2ccc220 100644 --- a/src/lpython/semantics/python_ast_to_asr.cpp +++ b/src/lpython/semantics/python_ast_to_asr.cpp @@ -4662,6 +4662,38 @@ class BodyVisitor : public CommonVisitor { targets.size()); } + bool check_is_assign_to_input_param(AST::expr_t* x) { + if (AST::is_a(*x)) { + AST::Name_t* n = AST::down_cast(x); + ASR::symbol_t* s = current_scope->resolve_symbol(n->m_id); + if (!s) { + throw SemanticError("Variable: '" + std::string(n->m_id) + "' is not declared", + x->base.loc); + } + ASR::Variable_t* v = ASR::down_cast(s); + if (v->m_intent == ASR::intentType::In) { + throw SemanticError("Assignment to an input function parameter `" + + std::string(v->m_name) + "` is not allowed", x->base.loc); + } + return true; + } else if (AST::is_a(*x)) { + AST::Subscript_t* s = AST::down_cast(x); + return check_is_assign_to_input_param(s->m_value); + } else if (AST::is_a(*x)) { + AST::Attribute_t* s = AST::down_cast(x); + return check_is_assign_to_input_param(s->m_value); + } else if (AST::is_a(*x)) { + AST::Tuple_t* t = AST::down_cast(x); + bool is_assign_to_input = false; + for (size_t i = 0; i < t->n_elts && !is_assign_to_input; i++) { + is_assign_to_input = is_assign_to_input || check_is_assign_to_input_param(t->m_elts[i]); + } + return is_assign_to_input; + }else { + throw SemanticError("Unsupported type in check_is_assign_to_input_param()", x->base.loc); + } + } + void visit_Assign(const AST::Assign_t &x) { ASR::expr_t *target, *assign_value = nullptr, *tmp_value; bool is_c_p_pointer_call_copy = is_c_p_pointer_call; @@ -4689,6 +4721,7 @@ class BodyVisitor : public CommonVisitor { } for (size_t i=0; i(*x.m_targets[i])) { AST::Subscript_t *sb = AST::down_cast(x.m_targets[i]); if (AST::is_a(*sb->m_value)) { @@ -4831,13 +4864,6 @@ class BodyVisitor : public CommonVisitor { std::string var_name = std::string(v->m_name); throw SemanticError("Assignment to loop variable `" + std::string(to_lower(var_name)) +"` is not allowed", target->base.loc); } - if (sym->type == ASR::symbolType::Variable) { - ASR::Variable_t *v = ASR::down_cast(sym); - if (v->m_intent == ASR::intentType::In) { - throw SemanticError("Assignment to an input function parameter `" - + std::string(v->m_name) + "` is not allowed", target->base.loc); - } - } } tmp_vec.push_back(ASR::make_Assignment_t(al, x.base.base.loc, target, tmp_value, overloaded)); diff --git a/src/lpython/semantics/python_attribute_eval.h b/src/lpython/semantics/python_attribute_eval.h index eb658913f5..3d07d598e2 100644 --- a/src/lpython/semantics/python_attribute_eval.h +++ b/src/lpython/semantics/python_attribute_eval.h @@ -16,6 +16,7 @@ struct AttributeHandler { const Location &, Vec &, diag::Diagnostics &); std::map attribute_map; + std::set modify_attr_set; AttributeHandler() { attribute_map = { @@ -35,6 +36,10 @@ struct AttributeHandler { {"dict@get", &eval_dict_get}, {"dict@pop", &eval_dict_pop} }; + + modify_attr_set = {"list@append", "list@remove", + "list@reverse", "list@clear", "list@insert", "list@pop", + "set@pop", "set@add", "set@remove", "dict@pop"}; } std::string get_type_name(ASR::ttype_t *t) { @@ -60,6 +65,13 @@ struct AttributeHandler { throw SemanticError("Type name is not implemented yet.", loc); } std::string key = class_name + "@" + attr_name; + if (modify_attr_set.find(key) != modify_attr_set.end()) { + ASR::Variable_t* v = ASRUtils::EXPR2VAR(e); + if (v->m_intent == ASRUtils::intent_in) { + throw SemanticError("Modifying input function parameter `" + + std::string(v->m_name) + "` is not allowed", loc); + } + } auto search = attribute_map.find(key); if (search != attribute_map.end()) { attribute_eval_callback cb = search->second; diff --git a/tests/dictionary1.py b/tests/dictionary1.py index 77d4aae151..b50e326d5a 100644 --- a/tests/dictionary1.py +++ b/tests/dictionary1.py @@ -33,7 +33,7 @@ def test_dict_pop(): x = y.pop("a") -def f(x: dict[i32, i32]): +def f(x: InOut[dict[i32, i32]]): x[2] = 4 def test_issue_204(): diff --git a/tests/reference/asr-dictionary1-a105a36.json b/tests/reference/asr-dictionary1-a105a36.json index 365841f186..52503c6bf0 100644 --- a/tests/reference/asr-dictionary1-a105a36.json +++ b/tests/reference/asr-dictionary1-a105a36.json @@ -2,11 +2,11 @@ "basename": "asr-dictionary1-a105a36", "cmd": "lpython --show-asr --no-color {infile} -o {outfile}", "infile": "tests/dictionary1.py", - "infile_hash": "2d9f15b746aa8185afb3f2dc6415c20a7edccc8e711df1b323178d94", + "infile_hash": "f164e8dbe52a04e4fd541d6ff6edae2994d683074087d8785a1759e4", "outfile": null, "outfile_hash": null, "stdout": "asr-dictionary1-a105a36.stdout", - "stdout_hash": "0bd85eafba7412639903c3cf431f99a5cec2e2e48e265f0f8014a641", + "stdout_hash": "54e029691491889b71111718910b430fa193a93b95b6258fc0c8fbff", "stderr": null, "stderr_hash": null, "returncode": 0 diff --git a/tests/reference/asr-dictionary1-a105a36.stdout b/tests/reference/asr-dictionary1-a105a36.stdout index b344ac9f2a..9d4fba0917 100644 --- a/tests/reference/asr-dictionary1-a105a36.stdout +++ b/tests/reference/asr-dictionary1-a105a36.stdout @@ -17,7 +17,7 @@ 6 x [] - In + InOut () () Default diff --git a/tests/reference/asr-structs_01-be14d49.json b/tests/reference/asr-structs_01-be14d49.json index 4040befa65..a462e8fa55 100644 --- a/tests/reference/asr-structs_01-be14d49.json +++ b/tests/reference/asr-structs_01-be14d49.json @@ -2,11 +2,11 @@ "basename": "asr-structs_01-be14d49", "cmd": "lpython --show-asr --no-color {infile} -o {outfile}", "infile": "tests/../integration_tests/structs_01.py", - "infile_hash": "c8012b0c841b0d8e304c18ca7c6d4365f1d5e41235dc6f4e2dc21664", + "infile_hash": "9f36a4abcc3a50ccc10df1f6b04998b7a20041853142c85a2e86b724", "outfile": null, "outfile_hash": null, "stdout": "asr-structs_01-be14d49.stdout", - "stdout_hash": "d23a95b31aa0235947d1720b9f812b84e9c67cafa0e32a3b27802025", + "stdout_hash": "cca5515fad19fdf65edf97e51effc67b37295513eaaab45f174854a9", "stderr": null, "stderr_hash": null, "returncode": 0 diff --git a/tests/reference/asr-structs_01-be14d49.stdout b/tests/reference/asr-structs_01-be14d49.stdout index 0c3f3ea279..b373dcadba 100644 --- a/tests/reference/asr-structs_01-be14d49.stdout +++ b/tests/reference/asr-structs_01-be14d49.stdout @@ -103,7 +103,7 @@ 4 a [] - In + InOut () () Default diff --git a/tests/reference/asr-structs_05-fa98307.json b/tests/reference/asr-structs_05-fa98307.json index c0e703e984..ec64dc141d 100644 --- a/tests/reference/asr-structs_05-fa98307.json +++ b/tests/reference/asr-structs_05-fa98307.json @@ -2,11 +2,11 @@ "basename": "asr-structs_05-fa98307", "cmd": "lpython --show-asr --no-color {infile} -o {outfile}", "infile": "tests/../integration_tests/structs_05.py", - "infile_hash": "94d06fb2844e1060d5df5291c3ccc4bd0e2bc8a8a59651814f0c4f0e", + "infile_hash": "5c587158fe09782d15aa8f5f9c24468c62795e9f537a9eb439d8e8a4", "outfile": null, "outfile_hash": null, "stdout": "asr-structs_05-fa98307.stdout", - "stdout_hash": "fb8c872aaca346b06d130d093b14f58f616b667e4613f765fb32a2e6", + "stdout_hash": "bde17720270048c509903eac1703b839f3e34c0a9cb1d8df1811bd55", "stderr": null, "stderr_hash": null, "returncode": 0 diff --git a/tests/reference/asr-structs_05-fa98307.stdout b/tests/reference/asr-structs_05-fa98307.stdout index 68bd595cea..020049a803 100644 --- a/tests/reference/asr-structs_05-fa98307.stdout +++ b/tests/reference/asr-structs_05-fa98307.stdout @@ -419,7 +419,7 @@ 195 s [] - In + InOut () () Default diff --git a/tests/reference/ast-dictionary1-1a7e00a.json b/tests/reference/ast-dictionary1-1a7e00a.json index 41583be770..ba29488d33 100644 --- a/tests/reference/ast-dictionary1-1a7e00a.json +++ b/tests/reference/ast-dictionary1-1a7e00a.json @@ -2,11 +2,11 @@ "basename": "ast-dictionary1-1a7e00a", "cmd": "lpython --show-ast --no-color {infile} -o {outfile}", "infile": "tests/dictionary1.py", - "infile_hash": "2d9f15b746aa8185afb3f2dc6415c20a7edccc8e711df1b323178d94", + "infile_hash": "f164e8dbe52a04e4fd541d6ff6edae2994d683074087d8785a1759e4", "outfile": null, "outfile_hash": null, "stdout": "ast-dictionary1-1a7e00a.stdout", - "stdout_hash": "cd85b7fa2f2f43a4be7a88c54d582616f529b9acee1de757eb361bd7", + "stdout_hash": "c347af8c0abbed4664f354dc24c1bd061218c0584aef27889fbf1745", "stderr": null, "stderr_hash": null, "returncode": 0 diff --git a/tests/reference/ast-dictionary1-1a7e00a.stdout b/tests/reference/ast-dictionary1-1a7e00a.stdout index 41c449fc10..d300e7a74d 100644 --- a/tests/reference/ast-dictionary1-1a7e00a.stdout +++ b/tests/reference/ast-dictionary1-1a7e00a.stdout @@ -511,18 +511,25 @@ [(x (Subscript (Name - dict + InOut Load ) - (Tuple - [(Name - i32 + (Subscript + (Name + dict Load ) - (Name - i32 + (Tuple + [(Name + i32 + Load + ) + (Name + i32 + Load + )] Load - )] + ) Load ) Load