From db3a5397e4eccee6736d232a7a3d18f2a15203f8 Mon Sep 17 00:00:00 2001 From: ALEX Date: Sat, 6 Sep 2025 12:37:02 +0600 Subject: [PATCH 1/4] =?UTF-8?q?Extend=20MAKE=5FFUNCTION=5FA/MAKE=5FCLOSURE?= =?UTF-8?q?=5FA=20handling=20to=20support=20Python=203.0=E2=80=933.14+?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ASTree.cpp | 119 +++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 111 insertions(+), 8 deletions(-) diff --git a/ASTree.cpp b/ASTree.cpp index 6635808e1..f10f5b647 100644 --- a/ASTree.cpp +++ b/ASTree.cpp @@ -1564,8 +1564,7 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) { PycRef fun_code = stack.top(); stack.pop(); - - /* Test for the qualified name of the function (at TOS) */ + // Test for the qualified name of the function (at TOS) int tos_type = fun_code.cast()->object().type(); if (tos_type != PycObject::TYPE_CODE && tos_type != PycObject::TYPE_CODE2) { @@ -1576,14 +1575,118 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) ASTFunction::defarg_t defArgs, kwDefArgs; const int defCount = operand & 0xFF; const int kwDefCount = (operand >> 8) & 0xFF; - for (int i = 0; i < defCount; ++i) { - defArgs.push_front(stack.top()); - stack.pop(); + const int annotationCount = (operand >> 16) & 0x7FFF; + + // Python 3.3 and below + if (mod->verCompare(3, 3) <= 0) { + for (int i = 0; i < defCount; ++i) { + defArgs.push_front(stack.top()); + stack.pop(); + } + // KW Defaults not mentioned in docs, but they come after the positional args + for (int i = 0; i < kwDefCount; ++i) { + kwDefArgs.push_front(stack.top()); + stack.pop(); // KW Pair object + stack.pop(); // KW Pair name + } } - for (int i = 0; i < kwDefCount; ++i) { - kwDefArgs.push_front(stack.top()); - stack.pop(); + // Python 3.4-3.5 + else if (mod->verCompare(3, 5) <= 0) { + // From Py 3.4: annotations and order change (kw first) + if (annotationCount) { + stack.pop(); // Tuple of param names for annotations + for (int i = 0; i < annotationCount; ++i) { + stack.pop(); // Pop annotation objects and ignore + } + } + for (int i = 0; i < kwDefCount; ++i) { + kwDefArgs.push_front(stack.top()); + stack.pop(); // KW Pair object + stack.pop(); // KW Pair name + } + for (int i = 0; i < defCount; ++i) { + defArgs.push_front(stack.top()); + stack.pop(); + } } + // Python 3.6-3.9 (flag mask, annotation dict) + else if (mod->verCompare(3, 9) <= 0) { + if (operand & 0x08) { // Cells for free vars to create a closure + stack.pop(); // Ignore these for syntax generation + } + if (operand & 0x04) { // Annotation dict (3.6-9) + stack.pop(); // Ignore annotations + } + if (operand & 0x02) { // Kwarg Defaults + PycRef kw_tuple = stack.top(); + stack.pop(); + std::vector> kw_values = kw_tuple.cast()->values(); + for (const PycRef& kw : kw_values) { + kwDefArgs.push_front(kw); + } + } + if (operand & 0x01) { // Positional Defaults (including positional-or-KW args) + PycRef pos_tuple = stack.top(); + stack.pop(); + std::vector> pos_values = pos_tuple.cast()->object().cast()->values(); + for (const PycRef& pos : pos_values) { + defArgs.push_back(new ASTObject(pos)); + } + } + } + // Python 3.10-3.13 (annotation can be dict or string, otherwise same) + else if (mod->verCompare(3, 13) <= 0) { + if (operand & 0x08) { // Cells for free vars to create a closure + stack.pop(); // Ignore these for syntax generation + } + if (operand & 0x04) { // Annotation dict (3.10-13) or string (PEP 563) + stack.pop(); // Ignore annotations + } + if (operand & 0x02) { // Kwarg Defaults + PycRef kw_tuple = stack.top(); + stack.pop(); + std::vector> kw_values = kw_tuple.cast()->values(); + for (const PycRef& kw : kw_values) { + kwDefArgs.push_front(kw); + } + } + if (operand & 0x01) { // Positional Defaults (including positional-or-KW args) + PycRef pos_tuple = stack.top(); + stack.pop(); + std::vector> pos_values = pos_tuple.cast()->object().cast()->values(); + for (const PycRef& pos : pos_values) { + defArgs.push_back(new ASTObject(pos)); + } + } + } + else { + if (operand & 0x10) { + stack.pop(); + } + if (operand & 0x08) { + stack.pop(); + } + if (operand & 0x04) { + stack.pop(); + } + if (operand & 0x02) { + PycRef kw_tuple = stack.top(); + stack.pop(); + std::vector> kw_values = kw_tuple.cast()->values(); + for (const PycRef& kw : kw_values) { + kwDefArgs.push_front(kw); + } + } + if (operand & 0x01) { + PycRef pos_tuple = stack.top(); + stack.pop(); + std::vector> pos_values = pos_tuple.cast()->object().cast()->values(); + for (const PycRef& pos : pos_values) { + defArgs.push_back(new ASTObject(pos)); + } + } + } + stack.push(new ASTFunction(fun_code, defArgs, kwDefArgs)); } break; From a86095d8b223a5547a2b572750c6ee4573a82bf9 Mon Sep 17 00:00:00 2001 From: ALEX Date: Tue, 16 Sep 2025 14:52:13 +0600 Subject: [PATCH 2/4] Update ASTree.cpp --- ASTree.cpp | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/ASTree.cpp b/ASTree.cpp index f10f5b647..a09912ab3 100644 --- a/ASTree.cpp +++ b/ASTree.cpp @@ -1559,7 +1559,9 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) case Pyc::LOAD_NAME_A: stack.push(new ASTName(code->getName(operand))); break; + case Pyc::MAKE_CLOSURE_A: + case Pyc::MAKE_FUNCTION: case Pyc::MAKE_FUNCTION_A: { PycRef fun_code = stack.top(); @@ -1659,6 +1661,35 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) } } } + // Python 3.14+ (assume new flag 0x10 for environment dict, otherwise same as 3.13) + else if (mod->verCompare(3, 14) <= 0) { + if (operand & 0x10) { // Hypothetical: function environment variables + stack.pop(); // Ignore environment dict + } + if (operand & 0x08) { // Cells for free vars to create a closure + stack.pop(); // Ignore these for syntax generation + } + if (operand & 0x04) { // Annotation dict or string + stack.pop(); // Ignore annotations + } + if (operand & 0x02) { // Kwarg Defaults + PycRef kw_tuple = stack.top(); + stack.pop(); + std::vector> kw_values = kw_tuple.cast()->values(); + for (const PycRef& kw : kw_values) { + kwDefArgs.push_front(kw); + } + } + if (operand & 0x01) { // Positional Defaults (including positional-or-KW args) + PycRef pos_tuple = stack.top(); + stack.pop(); + std::vector> pos_values = pos_tuple.cast()->object().cast()->values(); + for (const PycRef& pos : pos_values) { + defArgs.push_back(new ASTObject(pos)); + } + } + } + // Unknown future versions: fallback, try 3.14 logic else { if (operand & 0x10) { stack.pop(); @@ -1690,6 +1721,30 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) stack.push(new ASTFunction(fun_code, defArgs, kwDefArgs)); } break; + case Pyc::SET_FUNCTION_ATTRIBUTE_A: + { + PycRef value = stack.top(); stack.pop(); + PycRef func = stack.top(); stack.pop(); + + if (ASTFunction* fn = dynamic_cast(func.get())) { + // Attach attribute depending on type of value + if (ASTDict* dict = dynamic_cast(value.get())) { + // Could be __annotations__ or __kwdefaults__ + fn->add_attribute("__dict__", value); + } else if (ASTTuple* tuple = dynamic_cast(value.get())) { + // Could represent __defaults__ + fn->add_attribute("__defaults__", value); + } else { + // Generic fallback + fn->add_attribute("__extra__", value); + } + } + + // Push back function with attributes attached + stack.push(func); + } + break; + case Pyc::NOP: break; case Pyc::POP_BLOCK: From fc518e208f52c3b3d9535e44d1b87c1ab6a3ab65 Mon Sep 17 00:00:00 2001 From: AHMAD Date: Tue, 16 Sep 2025 15:09:11 +0600 Subject: [PATCH 3/4] fix --- ASTree.cpp | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/ASTree.cpp b/ASTree.cpp index a09912ab3..ef92bc05c 100644 --- a/ASTree.cpp +++ b/ASTree.cpp @@ -1726,25 +1726,15 @@ PycRef BuildFromCode(PycRef code, PycModule* mod) PycRef value = stack.top(); stack.pop(); PycRef func = stack.top(); stack.pop(); - if (ASTFunction* fn = dynamic_cast(func.get())) { - // Attach attribute depending on type of value - if (ASTDict* dict = dynamic_cast(value.get())) { - // Could be __annotations__ or __kwdefaults__ - fn->add_attribute("__dict__", value); - } else if (ASTTuple* tuple = dynamic_cast(value.get())) { - // Could represent __defaults__ - fn->add_attribute("__defaults__", value); - } else { - // Generic fallback - fn->add_attribute("__extra__", value); - } - } + // Currently we can't store the attribute (no AST support yet), + // so just discard 'value' and keep function on stack. + (void)value; // suppress unused warning - // Push back function with attributes attached stack.push(func); } break; + case Pyc::NOP: break; case Pyc::POP_BLOCK: From ccab1828681ff282b4ebb7b737b2b99252884d29 Mon Sep 17 00:00:00 2001 From: AHMAD Date: Tue, 16 Sep 2025 17:10:42 +0600 Subject: [PATCH 4/4] Add tests for python 3.13 --- tests/compiled/test_functions_3_13.3.13.pyc | Bin 0 -> 336 bytes tests/input/test_functions_3_13.py | 4 ++++ tests/tokenized/test_functions_3_13.txt | 7 +++++++ 3 files changed, 11 insertions(+) create mode 100644 tests/compiled/test_functions_3_13.3.13.pyc create mode 100644 tests/input/test_functions_3_13.py create mode 100644 tests/tokenized/test_functions_3_13.txt diff --git a/tests/compiled/test_functions_3_13.3.13.pyc b/tests/compiled/test_functions_3_13.3.13.pyc new file mode 100644 index 0000000000000000000000000000000000000000..66a606e7d78b007100f258ad79701cd7bd6c2bc5 GIT binary patch literal 336 zcmX@j%ge<81pK-uGn|3+V-N=h7@>^MASKfoQW#noq8KU}HJOrODnK+y6*CZj<^d8w zRg5hRwG1^3!3>&Ae#tN+fKm)hKr9TzP=z%>rB$p95QQ&6k}DZB8E-Kr7lSk@{9-Rj zEiTb3sJz9RnU|MZR3%iBU!Z4`lb@WJQ*5UP76WN2<^~cC3^(`%CKz^>cJO=#av6$% z<^l~X;s8<* + +def inner ( c ) : + +return c * 2 + +return inner