diff --git a/lib/codegen.cpp b/lib/codegen.cpp index 31813cb..84eb36e 100644 --- a/lib/codegen.cpp +++ b/lib/codegen.cpp @@ -282,6 +282,21 @@ 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,6 +322,19 @@ namespace { comments = resolveComment(e->comment); } + // 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 + 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) { + return std::format("new StringListVerifier({})", comments); + } + } + std::string ver = verifier(vt->type, var, currentStruct); return std::format( "new TableVerifier({{{{\"*\",{},Optional::Yes,Private::No,{}}}}})", 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 {