From 9ff8d933d7d6c2dc23914a6b0c95f2f1c07c297e Mon Sep 17 00:00:00 2001 From: Aki <75532970+AkiSakurai@users.noreply.github.com> Date: Thu, 24 Sep 2026 23:13:36 +0800 Subject: [PATCH 1/2] core: Preserve endian qualifiers on template arguments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copy m_endian, m_reference, and m_templateParameterIndex when evaluating or cloning ASTNodeTypeApplication so be/le on template arguments survives nested binding. Example (test_data starts with PNG signature 89 50 4E 47): struct Box { T v; }; Box b @ 0; Before: b.v read as 0x474E5089 (le) — 'be' dropped during evaluate() After: b.v read as 0x89504E47 (be) — template argument endian preserved Adds TemplateEndian test. Co-authored-by: opencode --- .../pl/core/ast/ast_node_type_application.cpp | 5 +- tests/CMakeLists.txt | 1 + .../test_pattern_template_endian.hpp | 74 +++++++++++++++++++ tests/source/tests.cpp | 2 + 4 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/include/test_patterns/test_pattern_template_endian.hpp diff --git a/lib/source/pl/core/ast/ast_node_type_application.cpp b/lib/source/pl/core/ast/ast_node_type_application.cpp index 40ef1750..b5144387 100644 --- a/lib/source/pl/core/ast/ast_node_type_application.cpp +++ b/lib/source/pl/core/ast/ast_node_type_application.cpp @@ -37,7 +37,7 @@ namespace pl::core::ast { : m_type(std::move(type)) { }; ASTNodeTypeApplication::ASTNodeTypeApplication(const ASTNodeTypeApplication &other) - : ASTNode(other), m_type(other.m_type) { + : ASTNode(other), m_type(other.m_type), m_reference(other.m_reference), m_endian(other.m_endian), m_templateParameterIndex(other.m_templateParameterIndex) { for (const auto &arg : other.m_templateArguments) this->m_templateArguments.emplace_back(arg->clone()); }; @@ -71,6 +71,9 @@ namespace pl::core::ast { } evaluatedType->m_templateArguments = std::move(evaluatedTemplateArguments); + evaluatedType->m_reference = this->m_reference; + evaluatedType->m_endian = this->m_endian; + evaluatedType->m_templateParameterIndex = this->m_templateParameterIndex; evaluatedType->setLocation(this->getLocation()); evaluatedType->setShouldDocument(this->shouldDocument()); evaluatedType->setDocComment(this->getDocComment()); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 292b15f5..767160e5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -42,6 +42,7 @@ set(AVAILABLE_TESTS Format RValuesAssignmentInStruct TemplateParametersScope + TemplateEndian TypeNameOf CustomBuiltInType Using diff --git a/tests/include/test_patterns/test_pattern_template_endian.hpp b/tests/include/test_patterns/test_pattern_template_endian.hpp new file mode 100644 index 00000000..c1d57eba --- /dev/null +++ b/tests/include/test_patterns/test_pattern_template_endian.hpp @@ -0,0 +1,74 @@ +#pragma once + +#include "test_pattern.hpp" + +namespace pl::test { + + class TestPatternTemplateEndian : public TestPattern { + public: + TestPatternTemplateEndian(core::Evaluator *evaluator) : TestPattern(evaluator, "TemplateEndian") { + } + ~TestPatternTemplateEndian() override = default; + + [[nodiscard]] std::string getSourceCode() const override { + return R"( + struct EqBox { + V v; + }; + struct EqPayload { + u32 x; + }; + struct EqBodyBe { + be V v; + }; + struct EqStructPayload { + be u32 x; + }; + + be u32 eqPlainBe @ 0; + std::assert(eqPlainBe == 0x89504E47, "sanity: plain 'be u32' must read big endian"); + + u32 eqPlainDefault @ 0; + std::assert(eqPlainDefault == 0x474E5089, "sanity: default 'u32' must read little endian"); + + EqBox eqBoxBe @ 0; + std::assert(eqBoxBe.v == 0x89504E47, "'be' on a type template argument must be preserved"); + + EqBox eqBoxCustomBe @ 0; + std::assert(eqBoxCustomBe.v.x == 0x89504E47, "'be' on a custom type template argument must be preserved"); + + EqBox> eqBoxNestedBe @ 0; + std::assert(eqBoxNestedBe.v.v == 0x89504E47, "'be' in a nested template argument must be preserved"); + + using EqBoxAlias = EqBox; + EqBoxAlias eqAliasBe @ 0; + std::assert(eqAliasBe.v == 0x89504E47, "'be' in an alias template argument must be preserved"); + + using EqBoxAliasTemplate = EqBox; + EqBoxAliasTemplate eqAliasTemplateBe @ 0; + std::assert(eqAliasTemplateBe.v == 0x89504E47, "'be' passed through an alias template must be preserved"); + + using EqIdentity = V; + EqIdentity eqIdentityBe @ 0; + std::assert(eqIdentityBe == 0x89504E47, "'be' passed through an identity alias must be preserved"); + + EqBox eqBoxBeArray[2] @ 0; + std::assert(eqBoxBeArray[0].v == 0x89504E47, "'be' in an array of template instantiations must be preserved"); + std::assert(eqBoxBeArray[1].v == 0x0D0A1A0A, "'be' in an array of template instantiations must be preserved"); + + be EqBox eqOuterBe @ 0; + std::assert(eqOuterBe.v == 0x89504E47, "'be' on the outside of a template instantiation must still apply"); + + EqBodyBe eqBodyBe @ 0; + std::assert(eqBodyBe.v == 0x89504E47, "'be' applied to a template parameter inside the body must apply"); + + le EqBox eqInnerBeWins @ 4; + std::assert(eqInnerBeWins.v == 0x0D0A1A0A, "endian of the template argument must win over the outer default"); + + EqBox eqStructArg @ 0; + std::assert(eqStructArg.v.x == 0x89504E47, "a struct with 'be' members must stay big endian when used as a template argument"); + )"; + } + }; + +} diff --git a/tests/source/tests.cpp b/tests/source/tests.cpp index 0e6510ef..75ceeca5 100644 --- a/tests/source/tests.cpp +++ b/tests/source/tests.cpp @@ -30,6 +30,7 @@ #include "test_patterns/test_pattern_format.hpp" #include "test_patterns/test_pattern_rvalues_assignment_in_struct.hpp" #include "test_patterns/test_pattern_template_parameters_scope.hpp" +#include "test_patterns/test_pattern_template_endian.hpp" #include "test_patterns/test_pattern_typenameof.hpp" #include "test_patterns/test_pattern_custom_builtin_type.hpp" #include "test_patterns/test_pattern_using.hpp" @@ -85,6 +86,7 @@ std::array Tests = { TEST(Format), TEST(RValuesAssignmentInStruct), TEST(TemplateParametersScope), + TEST(TemplateEndian), TEST(TypeNameOf), TEST(CustomBuiltinType), TEST(Using), From 4a821d8412ed278d7e170a4601b2add715fe3d99 Mon Sep 17 00:00:00 2001 From: Aki <75532970+AkiSakurai@users.noreply.github.com> Date: Fri, 25 Sep 2026 20:51:09 +0800 Subject: [PATCH 2/2] evaluator: Round-trip non-file patterns with native endian MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setEndian() refuses to pin patterns in the heap, pattern-local, and instantiation sections, but getEndian() still fell back to the ambient default for them. An unset endian therefore answered whatever the evaluator's current default happened to be, so a value written under one default could be read back under another: a template parameter initialized inside a 'be' instantiation but used after the scope restored the little-endian default, or a heap variable read while a 'be' instantiation was being created. Example (test_data starts with PNG signature 89 50 4E 47): struct Test { u32 a = t; }; be Test<5> test; Before: test.t printed 6646139978924579364519035301401722880 (5 << 120) After: test.t prints 5 — unpinned non-file patterns now round-trip with native endian regardless of ambient default changes auto g = 16909060; struct Q { u32 a = g; }; be Q<5> q; Before: q.a printed 0 (g's little-endian bytes decoded as big endian) After: q.a prints 16909060 Extends the TemplateEndian test with non-type template parameters under be/le (placed and local) and a heap variable read under 'be'. fix: #119 Co-authored-by: opencode --- lib/include/pl/patterns/pattern.hpp | 2 ++ .../test_pattern_template_endian.hpp | 25 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/lib/include/pl/patterns/pattern.hpp b/lib/include/pl/patterns/pattern.hpp index e2574e05..48a47f28 100644 --- a/lib/include/pl/patterns/pattern.hpp +++ b/lib/include/pl/patterns/pattern.hpp @@ -217,6 +217,8 @@ namespace pl::ptrn { [[nodiscard]] std::endian getEndian() const { if (this->m_evaluator == nullptr) return std::endian::native; + else if (!this->m_endian.has_value() && + (this->m_section == HeapSectionId || this->m_section == PatternLocalSectionId || this->m_section == InstantiationSectionId)) return std::endian::native; else return this->m_endian.value_or(this->m_evaluator->getDefaultEndian()); } virtual void setEndian(std::endian endian) { diff --git a/tests/include/test_patterns/test_pattern_template_endian.hpp b/tests/include/test_patterns/test_pattern_template_endian.hpp index c1d57eba..fbfa5418 100644 --- a/tests/include/test_patterns/test_pattern_template_endian.hpp +++ b/tests/include/test_patterns/test_pattern_template_endian.hpp @@ -67,6 +67,31 @@ namespace pl::test { EqBox eqStructArg @ 0; std::assert(eqStructArg.v.x == 0x89504E47, "a struct with 'be' members must stay big endian when used as a template argument"); + + struct AutoBox { + u32 f; + u32 a = t; + }; + + be AutoBox<5> autoBe @ 0; + std::assert(autoBe.f == 0x89504E47, "'be' instantiation must still read file data big endian"); + std::assert(autoBe.t == 5, "non-type template parameter must not be endian-swapped by an outer 'be'"); + std::assert(autoBe.a == 5, "member initialized from a non-type template parameter must keep its value"); + + le AutoBox<5> autoLe @ 4; + std::assert(autoLe.f == 0x0A1A0A0D, "'le' instantiation must read file data little endian"); + std::assert(autoLe.t == 5, "non-type template parameter must not be endian-swapped by an outer 'le'"); + + be AutoBox<5> autoBeLocal; + std::assert(autoBeLocal.t == 5, "non-type template parameter must not be endian-swapped by a local 'be' instantiation"); + std::assert(autoBeLocal.a == 5, "member initialized from a non-type template parameter must keep its value when local"); + + auto endianGlobal = 16909060; + struct AutoGlobal { + u32 a = endianGlobal; + }; + be AutoGlobal<5> autoGlobalBe; + std::assert(autoGlobalBe.a == 16909060, "a heap variable read under an outer 'be' must keep its value"); )"; } };