Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions lib/codegen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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<BasicType*>(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,{}}}}})",
Expand Down
8 changes: 4 additions & 4 deletions tests/execution_structs/execution_structs_attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<TableVerifier*>(e.verifier.get());
CHECK(e.verifier->type() == "List of strings");
StringListVerifier* v = dynamic_cast<StringListVerifier*>(e.verifier.get());
REQUIRE(v);
REQUIRE(v->documentations.size() == 1);
CHECK(v->documentations[0].verifier->type() == "String");
Expand All @@ -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<TableVerifier*>(e.verifier.get());
CHECK(e.verifier->type() == "List of strings");
StringListVerifier* v = dynamic_cast<StringListVerifier*>(e.verifier.get());
REQUIRE(v);
REQUIRE(v->documentations.size() == 1);
CHECK(v->documentations[0].key == "*");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TableVerifier*>(e.verifier.get());
CHECK(e.verifier->type() == "List of strings");
StringListVerifier* t = dynamic_cast<StringListVerifier*>(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<StringVerifier*>(t->documentations[0].verifier.get());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<TableVerifier*>(e.verifier.get());
CHECK(e.verifier->type() == "List of strings");
StringListVerifier* t = dynamic_cast<StringListVerifier*>(e.verifier.get());
REQUIRE(t);
REQUIRE(t->documentations.size() == 1);
CHECK(t->documentations[0].key == "*");
CHECK(t->documentations[0].verifier->type() == "String");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,16 +87,16 @@ 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<OrVerifier*>(e.verifier.get());
REQUIRE(v);
REQUIRE(v->values.size() == 2);
CHECK(v->values[0]->type() == "String");
StringVerifier* w = dynamic_cast<StringVerifier*>(v->values[0].get());
REQUIRE(w);
CHECK(w->mustBeNotEmpty() == false);
CHECK(v->values[1]->type() == "Table");
TableVerifier* u = dynamic_cast<TableVerifier*>(v->values[1].get());
CHECK(v->values[1]->type() == "List of strings");
StringListVerifier* u = dynamic_cast<StringListVerifier*>(v->values[1].get());
REQUIRE(u);
REQUIRE(u->documentations.size() == 1);
CHECK(u->documentations[0].key == "*");
Expand Down
40 changes: 20 additions & 20 deletions tests/execution_structs/execution_structs_variant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<OrVerifier*>(e.verifier.get());
REQUIRE(v);
REQUIRE(v->values.size() == 2);
CHECK(v->values[0]->type() == "Table");
TableVerifier* w = dynamic_cast<TableVerifier*>(v->values[0].get());
CHECK(v->values[0]->type() == "List of strings");
StringListVerifier* w = dynamic_cast<StringListVerifier*>(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<StringVerifier*>(w->documentations[0].verifier.get());
Expand All @@ -705,20 +705,20 @@ 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<OrVerifier*>(e.verifier.get());
REQUIRE(v);
REQUIRE(v->values.size() == 2);
CHECK(v->values[0]->type() == "String");
StringVerifier* w = dynamic_cast<StringVerifier*>(v->values[0].get());
REQUIRE(w);
CHECK(w->mustBeNotEmpty() == false);
CHECK(v->values[1]->type() == "Table");
TableVerifier* u = dynamic_cast<TableVerifier*>(v->values[1].get());
CHECK(v->values[1]->type() == "List of strings");
StringListVerifier* u = dynamic_cast<StringListVerifier*>(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<StringVerifier*>(u->documentations[0].verifier.get());
Expand Down Expand Up @@ -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<OrVerifier*>(v->documentations[0].verifier.get());
REQUIRE(w);
REQUIRE(w->values.size() == 2);
CHECK(w->values[0]->type() == "String");
StringVerifier* u = dynamic_cast<StringVerifier*>(w->values[0].get());
REQUIRE(u);
CHECK(w->values[1]->type() == "Table");
TableVerifier* x = dynamic_cast<TableVerifier*>(w->values[1].get());
CHECK(w->values[1]->type() == "List of strings");
StringListVerifier* x = dynamic_cast<StringListVerifier*>(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");
}
{
Expand All @@ -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<OrVerifier*>(v->documentations[0].verifier.get());
REQUIRE(w);
REQUIRE(w->values.size() == 2);
CHECK(w->values[0]->type() == "String");
StringVerifier* u = dynamic_cast<StringVerifier*>(w->values[0].get());
REQUIRE(u);
CHECK(w->values[1]->type() == "Table");
TableVerifier* x = dynamic_cast<TableVerifier*>(w->values[1].get());
CHECK(w->values[1]->type() == "List of strings");
StringListVerifier* x = dynamic_cast<StringListVerifier*>(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");
}
{
Expand All @@ -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<OrVerifier*>(e.verifier.get());
REQUIRE(v);
REQUIRE(v->values.size() == 2);
CHECK(v->values[0]->type() == "String");
StringVerifier* w = dynamic_cast<StringVerifier*>(v->values[0].get());
REQUIRE(w);
CHECK(v->values[1]->type() == "Table");
TableVerifier* u = dynamic_cast<TableVerifier*>(v->values[1].get());
CHECK(v->values[1]->type() == "List of strings");
StringListVerifier* u = dynamic_cast<StringListVerifier*>(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");
}
}
50 changes: 50 additions & 0 deletions tests/parsing_structs/parsing_structs_attributes.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<std::string> 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<std::string>");

// 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<std::string> 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 {
Expand Down