From 4b20c169fc86cc9e9fa93b2339968c19cdfc4224 Mon Sep 17 00:00:00 2001 From: Ylva Selling Date: Fri, 12 Jun 2026 15:57:37 +0200 Subject: [PATCH 1/3] Add verifiers --- lib/codegen.cpp | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/lib/codegen.cpp b/lib/codegen.cpp index 31813cb..35b2fbf 100644 --- a/lib/codegen.cpp +++ b/lib/codegen.cpp @@ -307,6 +307,41 @@ namespace { comments = resolveComment(e->comment); } + // Use dedicated list verifiers for basic scalar/vector types + if (vt->type->tag == VariableType::Tag::BasicType) { + BasicType* bt = static_cast(vt->type); + using Type = BasicType::Type; + switch (bt->type) { + case Type::String: + return std::format("new StringListVerifier({})", comments); + case Type::Int: + return std::format("new IntListVerifier({})", comments); + case Type::Ivec2: + return std::format("new Vector2ListVerifier({})", comments); + case Type::Ivec3: + return std::format("new Vector3ListVerifier({})", comments); + case Type::Ivec4: + return std::format("new Vector4ListVerifier({})", comments); + case Type::Dvec2: + case Type::Vec2: + return std::format( + "new Vector2ListVerifier({})", comments + ); + case Type::Dvec3: + case Type::Vec3: + return std::format( + "new Vector3ListVerifier({})", comments + ); + case Type::Dvec4: + case Type::Vec4: + return std::format( + "new Vector4ListVerifier({})", comments + ); + default: + break; + } + } + std::string ver = verifier(vt->type, var, currentStruct); return std::format( "new TableVerifier({{{{\"*\",{},Optional::Yes,Private::No,{}}}}})", From e55ed7b46b1382427030b5b39b9b972a2effcb62 Mon Sep 17 00:00:00 2001 From: Ylva Selling Date: Mon, 22 Jun 2026 15:41:07 +0200 Subject: [PATCH 2/3] Add unit tests for stringlist and remove the other lists --- lib/codegen.cpp | 54 ++++++++----------- .../execution_structs_attributes.cpp | 8 +-- ...on_structs_basic_types_optional_vector.cpp | 7 +-- .../execution_structs_basic_types_vector.cpp | 5 +- ...cution_structs_optional_variant_vector.cpp | 6 +-- .../execution_structs_variant.cpp | 40 +++++++------- .../parsing_structs_attributes.cpp | 50 +++++++++++++++++ 7 files changed, 107 insertions(+), 63 deletions(-) diff --git a/lib/codegen.cpp b/lib/codegen.cpp index 35b2fbf..d485d8d 100644 --- a/lib/codegen.cpp +++ b/lib/codegen.cpp @@ -282,6 +282,20 @@ namespace { } }; + // Returns `true` if the variable has an attribute that the dedicated list verifier + // cannot represent. This covers attributes that constrain the element (which the + // bare list verifier would silently drop) as well as attributes that are invalid for + // the element type (such as `reference` or `directory` on a string), which must still + // flow through the normal path so that codegen reports the unsupported-attribute error + bool hasElementConstrainingAttribute(const Variable& var) { + const Variable::Attributes& a = var.attributes; + return !a.annotation.empty() || !a.inlist.empty() || !a.inrange.empty() || + !a.less.empty() || !a.lessequal.empty() || !a.greater.empty() || + !a.greaterequal.empty() || !a.notinlist.empty() || !a.notinrange.empty() || + !a.reference.empty() || !a.unequal.empty() || a.isColor || a.isDateTime || + a.isIdentifier || a.mustBeNotEmpty || a.isDirectory; + } + std::string verifier(VariableType* type, const Variable& var, Struct* currentStruct) { assert(type); assert(currentStruct); @@ -307,38 +321,16 @@ namespace { comments = resolveComment(e->comment); } - // Use dedicated list verifiers for basic scalar/vector types - if (vt->type->tag == VariableType::Tag::BasicType) { + // Use a dedicated list verifier for a vector of strings, but only when there + // are no attributes constraining the individual elements. The list verifier + // cannot express such constraints, so in that case we fall through to the + // generic TableVerifier path below, which applies the attributes to each + // element + bool isBasic = vt->type->tag == VariableType::Tag::BasicType; + if (isBasic && !hasElementConstrainingAttribute(var)) { BasicType* bt = static_cast(vt->type); - using Type = BasicType::Type; - switch (bt->type) { - case Type::String: - return std::format("new StringListVerifier({})", comments); - case Type::Int: - return std::format("new IntListVerifier({})", comments); - case Type::Ivec2: - return std::format("new Vector2ListVerifier({})", comments); - case Type::Ivec3: - return std::format("new Vector3ListVerifier({})", comments); - case Type::Ivec4: - return std::format("new Vector4ListVerifier({})", comments); - case Type::Dvec2: - case Type::Vec2: - return std::format( - "new Vector2ListVerifier({})", comments - ); - case Type::Dvec3: - case Type::Vec3: - return std::format( - "new Vector3ListVerifier({})", comments - ); - case Type::Dvec4: - case Type::Vec4: - return std::format( - "new Vector4ListVerifier({})", comments - ); - default: - break; + if (bt->type == BasicType::Type::String) { + return std::format("new StringListVerifier({})", comments); } } diff --git a/tests/execution_structs/execution_structs_attributes.cpp b/tests/execution_structs/execution_structs_attributes.cpp index 53cccc6..7d0e8bd 100644 --- a/tests/execution_structs/execution_structs_attributes.cpp +++ b/tests/execution_structs/execution_structs_attributes.cpp @@ -5715,8 +5715,8 @@ TEST_CASE("Execution/Structs/Attributes: Documentation 2/2", "[Execution][Struc CHECK(!e.optional); CHECK(e.isPrivate); CHECK(e.documentation == "vector private value documentation"); - CHECK(e.verifier->type() == "Table"); - TableVerifier* v = dynamic_cast(e.verifier.get()); + CHECK(e.verifier->type() == "List of strings"); + StringListVerifier* v = dynamic_cast(e.verifier.get()); REQUIRE(v); REQUIRE(v->documentations.size() == 1); CHECK(v->documentations[0].verifier->type() == "String"); @@ -5728,8 +5728,8 @@ TEST_CASE("Execution/Structs/Attributes: Documentation 2/2", "[Execution][Struc CHECK(e.optional); CHECK(e.isPrivate); CHECK(e.documentation == "optional vector private value documentation"); - CHECK(e.verifier->type() == "Table"); - TableVerifier* v = dynamic_cast(e.verifier.get()); + CHECK(e.verifier->type() == "List of strings"); + StringListVerifier* v = dynamic_cast(e.verifier.get()); REQUIRE(v); REQUIRE(v->documentations.size() == 1); CHECK(v->documentations[0].key == "*"); diff --git a/tests/execution_structs/execution_structs_basic_types_optional_vector.cpp b/tests/execution_structs/execution_structs_basic_types_optional_vector.cpp index 661ef08..cdc4d65 100644 --- a/tests/execution_structs/execution_structs_basic_types_optional_vector.cpp +++ b/tests/execution_structs/execution_structs_basic_types_optional_vector.cpp @@ -1179,11 +1179,12 @@ TEST_CASE( CHECK(e.optional); CHECK(!e.isPrivate); CHECK(e.documentation == "string value documentation"); - CHECK(e.verifier->type() == "Table"); - TableVerifier* t = dynamic_cast(e.verifier.get()); + CHECK(e.verifier->type() == "List of strings"); + StringListVerifier* t = dynamic_cast(e.verifier.get()); + REQUIRE(t); REQUIRE(t->documentations.size() == 1); CHECK(t->documentations[0].key == "*"); - CHECK(t->documentations[0].optional); + CHECK(!t->documentations[0].optional); CHECK(t->documentations[0].verifier->type() == "String"); StringVerifier* v = dynamic_cast(t->documentations[0].verifier.get()); diff --git a/tests/execution_structs/execution_structs_basic_types_vector.cpp b/tests/execution_structs/execution_structs_basic_types_vector.cpp index fd6780c..4e180aa 100644 --- a/tests/execution_structs/execution_structs_basic_types_vector.cpp +++ b/tests/execution_structs/execution_structs_basic_types_vector.cpp @@ -1104,8 +1104,9 @@ TEST_CASE("Execution/Structs/Basic/Types/Vector: Documentation", "[Execution][S CHECK(!e.optional); CHECK(!e.isPrivate); CHECK(e.documentation == "string value documentation"); - CHECK(e.verifier->type() == "Table"); - TableVerifier* t = dynamic_cast(e.verifier.get()); + CHECK(e.verifier->type() == "List of strings"); + StringListVerifier* t = dynamic_cast(e.verifier.get()); + REQUIRE(t); REQUIRE(t->documentations.size() == 1); CHECK(t->documentations[0].key == "*"); CHECK(t->documentations[0].verifier->type() == "String"); diff --git a/tests/execution_structs/execution_structs_optional_variant_vector.cpp b/tests/execution_structs/execution_structs_optional_variant_vector.cpp index 043d050..c0f0892 100644 --- a/tests/execution_structs/execution_structs_optional_variant_vector.cpp +++ b/tests/execution_structs/execution_structs_optional_variant_vector.cpp @@ -87,7 +87,7 @@ TEST_CASE( CHECK(e.optional); CHECK(!e.isPrivate); CHECK(e.documentation == "optional variant vector documentation"); - CHECK(e.verifier->type() == "String, or Table"); + CHECK(e.verifier->type() == "String, or List of strings"); OrVerifier* v = dynamic_cast(e.verifier.get()); REQUIRE(v); REQUIRE(v->values.size() == 2); @@ -95,8 +95,8 @@ TEST_CASE( StringVerifier* w = dynamic_cast(v->values[0].get()); REQUIRE(w); CHECK(w->mustBeNotEmpty() == false); - CHECK(v->values[1]->type() == "Table"); - TableVerifier* u = dynamic_cast(v->values[1].get()); + CHECK(v->values[1]->type() == "List of strings"); + StringListVerifier* u = dynamic_cast(v->values[1].get()); REQUIRE(u); REQUIRE(u->documentations.size() == 1); CHECK(u->documentations[0].key == "*"); diff --git a/tests/execution_structs/execution_structs_variant.cpp b/tests/execution_structs/execution_structs_variant.cpp index cd1f5d2..59d072a 100644 --- a/tests/execution_structs/execution_structs_variant.cpp +++ b/tests/execution_structs/execution_structs_variant.cpp @@ -679,16 +679,16 @@ TEST_CASE("Execution/Structs/Variant: Documentation", "[Execution][Structs]") { CHECK(!e.optional); CHECK(!e.isPrivate); CHECK(e.documentation == "variant vector documentation"); - CHECK(e.verifier->type() == "Table, or String"); + CHECK(e.verifier->type() == "List of strings, or String"); OrVerifier* v = dynamic_cast(e.verifier.get()); REQUIRE(v); REQUIRE(v->values.size() == 2); - CHECK(v->values[0]->type() == "Table"); - TableVerifier* w = dynamic_cast(v->values[0].get()); + CHECK(v->values[0]->type() == "List of strings"); + StringListVerifier* w = dynamic_cast(v->values[0].get()); REQUIRE(w); REQUIRE(w->documentations.size() == 1); CHECK(w->documentations[0].key == "*"); - CHECK(w->documentations[0].optional); + CHECK(!w->documentations[0].optional); CHECK(w->documentations[0].verifier->type() == "String"); StringVerifier* u = dynamic_cast(w->documentations[0].verifier.get()); @@ -705,7 +705,7 @@ TEST_CASE("Execution/Structs/Variant: Documentation", "[Execution][Structs]") { CHECK(!e.optional); CHECK(!e.isPrivate); CHECK(e.documentation == "variant vector 2 documentation"); - CHECK(e.verifier->type() == "String, or Table"); + CHECK(e.verifier->type() == "String, or List of strings"); OrVerifier* v = dynamic_cast(e.verifier.get()); REQUIRE(v); REQUIRE(v->values.size() == 2); @@ -713,12 +713,12 @@ TEST_CASE("Execution/Structs/Variant: Documentation", "[Execution][Structs]") { StringVerifier* w = dynamic_cast(v->values[0].get()); REQUIRE(w); CHECK(w->mustBeNotEmpty() == false); - CHECK(v->values[1]->type() == "Table"); - TableVerifier* u = dynamic_cast(v->values[1].get()); + CHECK(v->values[1]->type() == "List of strings"); + StringListVerifier* u = dynamic_cast(v->values[1].get()); REQUIRE(u); REQUIRE(u->documentations.size() == 1); CHECK(u->documentations[0].key == "*"); - CHECK(u->documentations[0].optional); + CHECK(!u->documentations[0].optional); CHECK(u->documentations[0].verifier->type() == "String"); StringVerifier* x = dynamic_cast(u->documentations[0].verifier.get()); @@ -879,19 +879,19 @@ TEST_CASE("Execution/Structs/Variant: Documentation", "[Execution][Structs]") { REQUIRE(v->documentations.size() == 1); CHECK(v->documentations[0].key == "Var"); CHECK(!v->documentations[0].isPrivate); - CHECK(v->documentations[0].verifier->type() == "String, or Table"); + CHECK(v->documentations[0].verifier->type() == "String, or List of strings"); OrVerifier* w = dynamic_cast(v->documentations[0].verifier.get()); REQUIRE(w); REQUIRE(w->values.size() == 2); CHECK(w->values[0]->type() == "String"); StringVerifier* u = dynamic_cast(w->values[0].get()); REQUIRE(u); - CHECK(w->values[1]->type() == "Table"); - TableVerifier* x = dynamic_cast(w->values[1].get()); + CHECK(w->values[1]->type() == "List of strings"); + StringListVerifier* x = dynamic_cast(w->values[1].get()); REQUIRE(x); REQUIRE(x->documentations.size() == 1); CHECK(x->documentations[0].key == "*"); - CHECK(x->documentations[0].optional); + CHECK(!x->documentations[0].optional); CHECK(x->documentations[0].verifier->type() == "String"); } { @@ -906,19 +906,19 @@ TEST_CASE("Execution/Structs/Variant: Documentation", "[Execution][Structs]") { REQUIRE(v->documentations.size() == 1); CHECK(v->documentations[0].key == "Var"); CHECK(!v->documentations[0].isPrivate); - CHECK(v->documentations[0].verifier->type() == "String, or Table"); + CHECK(v->documentations[0].verifier->type() == "String, or List of strings"); OrVerifier* w = dynamic_cast(v->documentations[0].verifier.get()); REQUIRE(w); REQUIRE(w->values.size() == 2); CHECK(w->values[0]->type() == "String"); StringVerifier* u = dynamic_cast(w->values[0].get()); REQUIRE(u); - CHECK(w->values[1]->type() == "Table"); - TableVerifier* x = dynamic_cast(w->values[1].get()); + CHECK(w->values[1]->type() == "List of strings"); + StringListVerifier* x = dynamic_cast(w->values[1].get()); REQUIRE(x); REQUIRE(x->documentations.size() == 1); CHECK(x->documentations[0].key == "*"); - CHECK(x->documentations[0].optional); + CHECK(!x->documentations[0].optional); CHECK(x->documentations[0].verifier->type() == "String"); } { @@ -927,19 +927,19 @@ TEST_CASE("Execution/Structs/Variant: Documentation", "[Execution][Structs]") { CHECK(!e.optional); CHECK(!e.isPrivate); CHECK(e.documentation == "variantStringVector"); - CHECK(e.verifier->type() == "String, or Table"); + CHECK(e.verifier->type() == "String, or List of strings"); OrVerifier* v = dynamic_cast(e.verifier.get()); REQUIRE(v); REQUIRE(v->values.size() == 2); CHECK(v->values[0]->type() == "String"); StringVerifier* w = dynamic_cast(v->values[0].get()); REQUIRE(w); - CHECK(v->values[1]->type() == "Table"); - TableVerifier* u = dynamic_cast(v->values[1].get()); + CHECK(v->values[1]->type() == "List of strings"); + StringListVerifier* u = dynamic_cast(v->values[1].get()); REQUIRE(u); REQUIRE(u->documentations.size() == 1); CHECK(u->documentations[0].key == "*"); - CHECK(u->documentations[0].optional); + CHECK(!u->documentations[0].optional); CHECK(u->documentations[0].verifier->type() == "String"); } } diff --git a/tests/parsing_structs/parsing_structs_attributes.cpp b/tests/parsing_structs/parsing_structs_attributes.cpp index d3a6ab1..117d91b 100644 --- a/tests/parsing_structs/parsing_structs_attributes.cpp +++ b/tests/parsing_structs/parsing_structs_attributes.cpp @@ -157,6 +157,56 @@ struct [[codegen::Dictionary(Par), codegen::noexhaustive(true)]] Parameters { CHECK(!r.empty()); } +TEST_CASE( + "Parsing Attribute: Vector list verifiers", "[structs][parsing]") +{ + constexpr std::string_view Source = R"( + struct [[codegen::Dictionary(ListVerifiers)]] Parameters { + // stringVectorValue documentation + std::vector stringVectorValue; +}; +)"; + + Code code = parse(Source); + CHECK(code.structs.size() == 1); + CHECK(code.enums.empty()); + Struct* s = code.structs.front(); + REQUIRE(s); + CHECK(s->attributes.dictionary == "ListVerifiers"); + + REQUIRE(s->variables.size() == 1); + CHECK(generateTypename(s->variables[0]->type) == "std::vector"); + + // A `std::vector` of `std::string` generates a dedicated list verifier instead of a + // generic TableVerifier + const std::string r = generateResult(code); + REQUIRE(!r.empty()); + CHECK(r.find("StringListVerifier") != std::string::npos); +} + +TEST_CASE( + "Parsing Attribute: Vector list verifiers suppressed by attributes", + "[structs][parsing]") +{ + constexpr std::string_view Source = R"( + struct [[codegen::Dictionary(ListVerifiers)]] Parameters { + // listVector documentation + std::vector listVector [[codegen::inlist("a", "b")]]; +}; +)"; + + Code code = parse(Source); + REQUIRE(code.structs.size() == 1); + + // When the vector element carries a constraining attribute, the list verifier cannot + // express it, so codegen must fall back to a TableVerifier whose element verifier + // applies the attribute + const std::string r = generateResult(code); + REQUIRE(!r.empty()); + CHECK(r.find("InListVerifier") != std::string::npos); + CHECK(r.find("StringListVerifier") == std::string::npos); +} + TEST_CASE("Parsing Attribute: Struct Attribute false noexhaustive", "[structs][parsing]") { constexpr std::string_view Source = R"( struct [[codegen::Dictionary(Par), codegen::noexhaustive(false)]] Parameters { From 0ebd79111499f33690e6d3cdbe2a9fcf503e921d Mon Sep 17 00:00:00 2001 From: Alexander Bock Date: Thu, 9 Jul 2026 11:46:08 +0200 Subject: [PATCH 3/3] Adjusting comment block --- lib/codegen.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/codegen.cpp b/lib/codegen.cpp index d485d8d..84eb36e 100644 --- a/lib/codegen.cpp +++ b/lib/codegen.cpp @@ -283,10 +283,11 @@ namespace { }; // Returns `true` if the variable has an attribute that the dedicated list verifier - // cannot represent. This covers attributes that constrain the element (which the - // bare list verifier would silently drop) as well as attributes that are invalid for - // the element type (such as `reference` or `directory` on a string), which must still - // flow through the normal path so that codegen reports the unsupported-attribute error + // cannot represent. This covers attributes that constrain the element (which the bare + // list verifier would silently drop) as well as attributes that are invalid for the + // element type (such as `reference` or `directory` on a string), which must still + // flow through the normal path so that codegen reports the unsupported-attribute + // error bool hasElementConstrainingAttribute(const Variable& var) { const Variable::Attributes& a = var.attributes; return !a.annotation.empty() || !a.inlist.empty() || !a.inrange.empty() || @@ -326,7 +327,7 @@ namespace { // cannot express such constraints, so in that case we fall through to the // generic TableVerifier path below, which applies the attributes to each // element - bool isBasic = vt->type->tag == VariableType::Tag::BasicType; + const bool isBasic = vt->type->tag == VariableType::Tag::BasicType; if (isBasic && !hasElementConstrainingAttribute(var)) { BasicType* bt = static_cast(vt->type); if (bt->type == BasicType::Type::String) {