LLZK 3.0.0
An open-source IR for Zero Knowledge (ZK) circuits
Loading...
Searching...
No Matches
CommonCAPIGen.h
Go to the documentation of this file.
1//===- CommonCAPIGen.h - Common utilities for C API generation ------------===//
2//
3// Part of the LLZK Project, under the Apache License v2.0.
4// See LICENSE.txt for license information.
5// Copyright 2025 Veridise Inc.
6// SPDX-License-Identifier: Apache-2.0
7//
8//===----------------------------------------------------------------------===//
9//
10// Common utilities shared between all CAPI generators (ops, attrs, types)
11//
12//===----------------------------------------------------------------------===//
13
14#pragma once
15
16#include <mlir/TableGen/Dialect.h>
17
18#include <llvm/ADT/StringExtras.h>
19#include <llvm/ADT/StringRef.h>
20#include <llvm/ADT/StringSwitch.h>
21#include <llvm/Support/CommandLine.h>
22#include <llvm/Support/FormatVariadic.h>
23
24#include <memory>
25#include <string>
26
27constexpr bool WARN_SKIPPED_METHODS = false;
28
30template <typename S> inline void warnSkipped(const S &methodName, const std::string &message) {
32 llvm::errs() << "Warning: Skipping method '" << methodName << "' - " << message << '\n';
33 }
34}
35
37template <typename S>
38inline void warnSkippedNoConversion(const S &methodName, const std::string &cppType) {
40 warnSkipped(methodName, "no conversion to C API type for '" + cppType + '\'');
41 }
42}
43
44// Forward declarations for Clang classes
45namespace clang {
46class Lexer;
47class SourceManager;
48} // namespace clang
49
50// Shared command-line options used by all CAPI generators
51extern llvm::cl::OptionCategory OpGenCat;
52extern llvm::cl::opt<std::string> DialectName;
53extern llvm::cl::opt<std::string> FunctionPrefix;
54
55// Shared flags for controlling code generation
56extern llvm::cl::opt<bool> GenIsA;
57extern llvm::cl::opt<bool> GenOpBuild;
58extern llvm::cl::opt<bool> GenOpOperandGetters;
59extern llvm::cl::opt<bool> GenOpOperandSetters;
60extern llvm::cl::opt<bool> GenOpAttributeGetters;
61extern llvm::cl::opt<bool> GenOpAttributeSetters;
62extern llvm::cl::opt<bool> GenOpRegionGetters;
63extern llvm::cl::opt<bool> GenOpResultGetters;
64extern llvm::cl::opt<bool> GenTypeOrAttrGet;
65extern llvm::cl::opt<bool> GenTypeOrAttrParamGetters;
66extern llvm::cl::opt<bool> GenExtraClassMethods;
67
75inline std::string toPascalCase(mlir::StringRef str) {
76 if (str.empty()) {
77 return "";
78 }
79
80 std::string result;
81 result.reserve(str.size());
82 llvm::raw_string_ostream resultStream(result);
83 bool capitalizeNext = true;
84
85 for (char c : str) {
86 if (c == '_' || c == ':') {
87 capitalizeNext = true;
88 } else {
89 resultStream << (capitalizeNext ? llvm::toUpper(c) : c);
90 capitalizeNext = false;
91 }
92 }
93
94 return result;
95}
96
100inline bool isIntegerType(mlir::StringRef type) {
101 // Consume optional root namespace token
102 type.consume_front("::");
103 // Handle special names first
104 if (type == "signed" || type == "unsigned" || type == "size_t" || type == "char32_t" ||
105 type == "char16_t" || type == "char8_t" || type == "wchar_t") {
106 return true;
107 }
108 // Handle standard integer types with optional signed/unsigned prefix
109 type.consume_front("signed ") || type.consume_front("unsigned ");
110 if (type == "char" || type == "int" || type == "short" || type == "short int" || type == "long" ||
111 type == "long int" || type == "long long" || type == "long long int") {
112 return true;
113 }
114 // Handle fixed-width integer types (https://cppreference.com/w/cpp/types/integer.html)
115 type.consume_front("std::"); // optional
116 if (type.consume_back("_t") && (type.consume_front("int") || type.consume_front("uint"))) {
117 // intmax_t, intptr_t, uintmax_t, uintptr_t
118 if (type == "max" || type == "ptr") {
119 return true;
120 }
121 // Optional "_fast" or "_least" followed by bit width to cover the rest
122 type.consume_front("_fast") || type.consume_front("_least");
123 if (type == "8" || type == "16" || type == "32" || type == "64") {
124 return true;
125 }
126 }
127 return false;
128}
129
136inline bool isPrimitiveType(mlir::StringRef cppType) {
137 cppType.consume_front("::");
138 return cppType == "void" || cppType == "bool" || cppType == "float" || cppType == "double" ||
139 cppType == "long double" || isIntegerType(cppType);
140}
141
145inline bool isCppModifierKeyword(mlir::StringRef tokenText) {
146 return llvm::StringSwitch<bool>(tokenText)
147 .Case("inline", true)
148 .Case("static", true)
149 .Case("virtual", true)
150 .Case("explicit", true)
151 .Case("constexpr", true)
152 .Case("consteval", true)
153 .Case("extern", true)
154 .Case("mutable", true)
155 .Case("friend", true)
156 .Default(false);
157}
158
162inline bool isCppLanguageConstruct(mlir::StringRef methodName) {
163 return llvm::StringSwitch<bool>(methodName)
164 .Case("if", true)
165 .Case("for", true)
166 .Case("while", true)
167 .Case("switch", true)
168 .Case("return", true)
169 .Case("sizeof", true)
170 .Case("decltype", true)
171 .Case("alignof", true)
172 .Case("typeid", true)
173 .Case("static_assert", true)
174 .Case("noexcept", true)
175 .Default(false);
176}
177
181inline bool isAPIntType(mlir::StringRef cppType) {
182 cppType.consume_front("::");
183 if (cppType == "llzk::APIntValue") {
184 return true;
185 }
186 cppType.consume_front("llvm::") || cppType.consume_front("mlir::");
187 return cppType == "APInt";
188}
189
193inline bool isArrayRefType(mlir::StringRef cppType) {
194 cppType.consume_front("::");
195 cppType.consume_front("llvm::") || cppType.consume_front("mlir::");
196 return cppType.starts_with("ArrayRef<");
197}
198
200inline mlir::StringRef extractArrayRefElementType(mlir::StringRef cppType) {
201 assert(isArrayRefType(cppType) && "must check `isArrayRefType()` outside");
202
203 // Remove "ArrayRef<" prefix and ">" suffix
204 cppType.consume_front("::");
205 cppType.consume_front("llvm::") || cppType.consume_front("mlir::");
206 cppType.consume_front("ArrayRef<") && cppType.consume_back(">");
207 return cppType;
208}
209
219public:
223 explicit ClangLexerContext(mlir::StringRef source, mlir::StringRef bufferName = "input");
224
227 clang::Lexer &getLexer() const;
228
231 clang::SourceManager &getSourceManager() const;
232
235 bool isValid() const { return lexer != nullptr; }
236
237private:
238 struct Impl;
239 std::unique_ptr<Impl> impl;
240 clang::Lexer *lexer = nullptr;
241};
242
247 std::string type;
249 std::string name;
250
254 MethodParameter(const std::string &paramType, const std::string &paramName)
255 : type(mlir::StringRef(paramType).trim().str()),
256 name(mlir::StringRef(paramName).trim().str()) {}
257};
258
265 std::string returnType;
267 std::string methodName;
269 std::string documentation;
271 bool isConst = false;
273 bool hasParameters = false;
275 std::vector<MethodParameter> parameters;
276};
277
304llvm::SmallVector<ExtraMethod> parseExtraMethods(mlir::StringRef extraDecl);
305
310bool matchesMLIRClass(mlir::StringRef cppType, mlir::StringRef typeName);
311
315std::optional<std::string> tryCppTypeToCapiType(mlir::StringRef cppType);
316
323std::string mapCppTypeToCapiType(mlir::StringRef cppType);
324
330std::optional<std::string> mapCapiTypeToBasicCppType(mlir::StringRef capiType);
331
333struct Generator {
334 Generator(std::string_view recordKind, llvm::raw_ostream &outputStream)
335 : kind(recordKind), os(outputStream), dialectNameCapitalized(toPascalCase(DialectName)) {}
336 virtual ~Generator() = default;
337
341 virtual void
342 setNamespaceAndClassName(const mlir::tblgen::Dialect &d, mlir::StringRef cppClassName) {
343 this->dialectNamespace = d.getCppNamespace();
344 this->className = cppClassName;
345 }
346
349 virtual void genExtraMethods(mlir::StringRef extraDecl) const {
350 if (extraDecl.empty()) {
351 return;
352 }
353 for (const ExtraMethod &method : parseExtraMethods(extraDecl)) {
354 genExtraMethod(method);
355 }
356 }
357
360 virtual void genExtraMethod(const ExtraMethod &method) const = 0;
361
362protected:
363 std::string kind;
364 llvm::raw_ostream &os;
366 mlir::StringRef dialectNamespace;
367 mlir::StringRef className;
368};
369
371struct HeaderGenerator : public Generator {
373 ~HeaderGenerator() override = default;
374
375 virtual void genPrologue() const {
376 os << R"(
377#include "llzk-c/Builder.h"
378#include <mlir-c/IR.h>
380#ifdef __cplusplus
381extern "C" {
382#endif
383)";
384 }
385
386 virtual void genEpilogue() const {
387 os << R"(
388#ifdef __cplusplus
389}
390#endif
391)";
392 }
393
394 virtual void genIsADecl() const {
395 static constexpr char fmt[] = R"(
397MLIR_CAPI_EXPORTED bool {0}{1}IsA_{2}_{3}(Mlir{1});
398)";
399 assert(!dialectNamespace.empty() && "Dialect must be set");
400 os << llvm::formatv(
401 fmt,
402 FunctionPrefix, // {0}
403 kind, // {1}
405 className, // {3}
406 dialectNamespace // {4}
407 );
408 }
409
411 void genExtraMethod(const ExtraMethod &method) const override {
412 // Convert return type to C API type, skip if it can't be converted
413 std::optional<std::string> capiReturnTypeOpt = tryCppTypeToCapiType(method.returnType);
414 if (!capiReturnTypeOpt.has_value()) {
416 return;
417 }
418 std::string capiReturnType = capiReturnTypeOpt.value();
419
420 // Build parameter list
421 std::string paramList;
422 llvm::raw_string_ostream paramListStream(paramList);
423 paramListStream << llvm::formatv("Mlir{0} inp", kind);
424 for (const auto &param : method.parameters) {
425 // Convert C++ type to C API type for parameter, skip if it can't be converted
426 std::optional<std::string> capiParamTypeOpt = tryCppTypeToCapiType(param.type);
427 if (!capiParamTypeOpt.has_value()) {
428 warnSkippedNoConversion(method.methodName, param.type);
429 return;
430 }
431 const std::string &capiParamType = capiParamTypeOpt.value();
432 paramListStream << ", " << capiParamType << ' ' << param.name;
433 }
434
435 // Generate declaration
436 if (method.documentation.empty()) {
437 os << llvm::formatv("\n/// {0}\n", method.methodName);
438 } else {
439 os << llvm::formatv("\n{0}\n", method.documentation);
440 }
441 os << llvm::formatv(
442 "MLIR_CAPI_EXPORTED {0} {1}{2}_{3}{4}({5});\n",
443 capiReturnType, // {0}
444 FunctionPrefix, // {1}
446 className, // {3}
447 toPascalCase(method.methodName), // {4}
448 paramList // {5}
449 );
450 }
451};
452
456 ~ImplementationGenerator() override = default;
457
458 virtual void genPrologue() const {}
459
460 virtual void genIsAImpl() const {
461 static constexpr char fmt[] = R"(
462bool {0}{1}IsA_{2}_{3}(Mlir{1} inp) {{
463 return llvm::isa<{3}>(unwrap(inp));
464}
465)";
466 assert(!className.empty() && "className must be set");
468 }
469
471 void genExtraMethod(const ExtraMethod &method) const override {
472 // Convert return type to C API type, skip if it can't be converted
473 std::optional<std::string> capiReturnTypeOpt = tryCppTypeToCapiType(method.returnType);
474 if (!capiReturnTypeOpt.has_value()) {
476 return;
477 }
478 std::string capiReturnType = capiReturnTypeOpt.value();
479
480 // Build the return statement prefix and suffix
481 std::string returnPrefix;
482 std::string returnSuffix;
483 mlir::StringRef cppReturnType = method.returnType;
484
485 if (cppReturnType == "void") {
486 // "void" type doesn't even need "return"
487 returnPrefix = "";
488 returnSuffix = "";
489 } else {
490 // Check if return needs wrapping
491 if (isPrimitiveType(cppReturnType)) {
492 // Primitive types don't need wrapping
493 returnPrefix = "return ";
494 returnSuffix = "";
495 } else if (capiReturnType.starts_with("Mlir") || isAPIntType(cppReturnType)) {
496 // MLIR C API types and APInt type need wrapping
497 returnPrefix = "return wrap(";
498 returnSuffix = ")";
499 } else {
500 return;
501 }
502 }
503
504 // Build parameter list for C API function signature
505 std::string paramList;
506 llvm::raw_string_ostream paramListStream(paramList);
507 paramListStream << llvm::formatv("Mlir{0} inp", kind);
508 for (const auto &param : method.parameters) {
509 // Convert C++ type to C API type for parameter, skip if it can't be converted
510 std::optional<std::string> capiParamTypeOpt = tryCppTypeToCapiType(param.type);
511 if (!capiParamTypeOpt.has_value()) {
512 warnSkippedNoConversion(method.methodName, param.type);
513 return;
514 }
515 const std::string &capiParamType = capiParamTypeOpt.value();
516 paramListStream << ", " << capiParamType << ' ' << param.name;
517 }
518
519 // Build argument list for C++ method call
520 std::string argList;
521 llvm::raw_string_ostream argListStream(argList);
522 for (size_t i = 0; i < method.parameters.size(); ++i) {
523 if (i > 0) {
524 argListStream << ", ";
525 }
526 const auto &param = method.parameters[i];
527
528 // Check if parameter needs unwrapping
529 mlir::StringRef cppParamType = param.type;
530 if (isPrimitiveType(cppParamType)) {
531 // Primitive types don't need unwrapping
532 argListStream << param.name;
533 } else if (isAPIntType(cppParamType)) {
534 // APInt needs unwrapping
535 argListStream << "unwrap(" << param.name << ')';
536 } else {
537 // Convert C++ type to C API type for parameter, skip if it can't be converted
538 std::optional<std::string> capiParamTypeOpt = tryCppTypeToCapiType(cppParamType);
539 if (capiParamTypeOpt.has_value() && capiParamTypeOpt->starts_with("Mlir")) {
540 // MLIR C API types need unwrapping
541 argListStream << "unwrap(" << param.name << ')';
542 } else {
543 warnSkippedNoConversion(method.methodName, cppParamType.str());
544 return;
545 }
546 }
547 }
548
549 // Generate implementation
550 os << '\n';
551 os << llvm::formatv(
552 "{0} {1}{2}_{3}{4}({5}) {{\n",
553 capiReturnType, // {0}
554 FunctionPrefix, // {1}
556 className, // {3}
557 toPascalCase(method.methodName), // {4}
558 paramList // {5}
559 );
560 os << llvm::formatv(
561 " {0}llvm::cast<{1}>(unwrap(inp)).{2}({3}){4};\n",
562 returnPrefix, // {0}
563 className, // {1}
564 method.methodName, // {2}
565 argList, // {3}
566 returnSuffix // {4}
567 );
568 os << "}\n";
569 }
570};
571
573struct TestGenerator : public Generator {
575 ~TestGenerator() override = default;
576
578 virtual void genTestClassPrologue() const {
579 static constexpr char fmt[] = "class {0}{1}LinkTests : public CAPITest {{};\n";
580 os << llvm::formatv(fmt, dialectNameCapitalized, kind);
581 }
582
584 virtual void genIsATest() const {
585 static constexpr char fmt[] = R"(
587TEST_F({2}{1}LinkTests, IsA_{2}_{3}) {{
588 auto test{1} = createIndex{1}();
589
590 // This will always return false since `createIndex*` returns an MLIR builtin
591 EXPECT_FALSE({0}{1}IsA_{2}_{3}(test{1}));
592
593 {4}(test{1});
594}
595)";
596 assert(!className.empty() && "className must be set");
597 os << llvm::formatv(
598 fmt,
599 FunctionPrefix, // {0}
600 kind, // {1}
602 className, // {3}
603 genCleanup() // {4}
604 );
605 }
606
608 void genExtraMethod(const ExtraMethod &method) const override {
609 // Convert return type to C API type, skip if it can't be converted
610 std::optional<std::string> capiReturnTypeOpt = tryCppTypeToCapiType(method.returnType);
611 if (!capiReturnTypeOpt.has_value()) {
613 return;
614 }
615
616 // Build parameter list for dummy values
617 std::string dummyParams;
618 llvm::raw_string_ostream dummyParamsStream(dummyParams);
619 std::string paramList;
620 llvm::raw_string_ostream paramListStream(paramList);
621
622 for (const auto &param : method.parameters) {
623 // Convert C++ type to C API type for parameter, skip if it can't be converted
624 std::optional<std::string> capiParamTypeOpt = tryCppTypeToCapiType(param.type);
625 if (!capiParamTypeOpt.has_value()) {
626 warnSkippedNoConversion(method.methodName, param.type);
627 return;
628 }
629 const std::string &capiParamType = capiParamTypeOpt.value();
630 std::string name = param.name;
631
632 // Generate dummy value creation for each parameter
633 if (capiParamType == "bool") {
634 dummyParamsStream << " bool " << name << " = false;\n";
635 } else if (capiParamType == "MlirValue") {
636 dummyParamsStream << " auto " << name << " = mlirOperationGetResult(testOp, 0);\n";
637 } else if (capiParamType == "MlirType") {
638 dummyParamsStream << " auto " << name << " = createIndexType();\n";
639 } else if (capiParamType == "MlirAttribute") {
640 dummyParamsStream << " auto " << name << " = createIndexAttribute();\n";
641 } else if (capiParamType == "MlirStringRef") {
642 dummyParamsStream << " auto " << name << " = mlirStringRefCreateFromCString(\"\");\n";
643 } else if (isIntegerType(capiParamType)) {
644 dummyParamsStream << " " << capiParamType << ' ' << name << " = 0;\n";
645 } else {
646 // For unknown types, create a default-initialized variable
647 dummyParamsStream << " " << capiParamType << ' ' << name << " = {};\n";
648 }
649
650 paramListStream << ", " << name;
651 }
652
653 static constexpr char fmt[] = R"(
655TEST_F({2}{1}LinkTests, {0}_{3}_{4}) {{
656 auto test{1} = createIndex{1}();
657
658 if ({0}{1}IsA_{2}_{3}(test{1})) {{
659{5}
660 (void){0}{2}_{3}{4}(test{1}{6});
661 }
662
663 {7}(test{1});
664}
665)";
666 assert(!className.empty() && "className must be set");
667 os << llvm::formatv(
668 fmt,
669 FunctionPrefix, // {0}
670 kind, // {1}
672 className, // {3}
673 toPascalCase(method.methodName), // {4}
674 dummyParams, // {5}
675 paramList, // {6}
676 genCleanup() // {7}
677 );
678 }
679
689 virtual std::string genCleanup() const {
690 // The default case is to just comment out the rest of the cleanup line
691 return "//";
692 }
693};
mlir::StringRef extractArrayRefElementType(mlir::StringRef cppType)
Extract element type from ArrayRef<...>
llvm::cl::OptionCategory OpGenCat
llvm::cl::opt< bool > GenOpOperandSetters
llvm::cl::opt< bool > GenTypeOrAttrParamGetters
bool isPrimitiveType(mlir::StringRef cppType)
Check if a C++ type is a known primitive type.
llvm::cl::opt< bool > GenTypeOrAttrGet
void warnSkippedNoConversion(const S &methodName, const std::string &cppType)
Print warning about skipping a function due to no conversion of C++ type to C API type.
llvm::cl::opt< bool > GenIsA
std::string mapCppTypeToCapiType(mlir::StringRef cppType)
Map C++ type to corresponding C API type.
llvm::cl::opt< bool > GenOpBuild
llvm::cl::opt< std::string > DialectName
bool isCppModifierKeyword(mlir::StringRef tokenText)
Check if a token text represents a C++ modifier/specifier keyword.
bool isAPIntType(mlir::StringRef cppType)
Check if a C++ type is APInt.
llvm::cl::opt< std::string > FunctionPrefix
std::optional< std::string > tryCppTypeToCapiType(mlir::StringRef cppType)
Convert C++ type to MLIR C API type.
bool isArrayRefType(mlir::StringRef cppType)
Check if a C++ type is an ArrayRef type.
llvm::cl::opt< bool > GenOpRegionGetters
constexpr bool WARN_SKIPPED_METHODS
bool isIntegerType(mlir::StringRef type)
Check if a C++ type is a known integer type.
llvm::cl::opt< bool > GenOpResultGetters
llvm::cl::opt< bool > GenOpAttributeGetters
bool matchesMLIRClass(mlir::StringRef cppType, mlir::StringRef typeName)
Check if a C++ type matches an MLIR type pattern.
llvm::cl::opt< bool > GenOpAttributeSetters
std::optional< std::string > mapCapiTypeToBasicCppType(mlir::StringRef capiType)
Map C API type to corresponding basic (not dialect-defined) C++ type.
llvm::cl::opt< bool > GenOpOperandGetters
llvm::SmallVector< ExtraMethod > parseExtraMethods(mlir::StringRef extraDecl)
Parse method declarations from an extraClassDeclaration using Clang's Lexer.
std::string toPascalCase(mlir::StringRef str)
Convert names separated by underscore or colon to PascalCase.
llvm::cl::opt< bool > GenExtraClassMethods
void warnSkipped(const S &methodName, const std::string &message)
Print warning about skipping a function.
bool isCppLanguageConstruct(mlir::StringRef methodName)
Check if a method name represents a C++ control flow keyword or language construct.
Apache License January AND DISTRIBUTION Definitions License shall mean the terms and conditions for and distribution as defined by Sections through of this document Licensor shall mean the copyright owner or entity authorized by the copyright owner that is granting the License Legal Entity shall mean the union of the acting entity and all other entities that control are controlled by or are under common control with that entity For the purposes of this definition control direct or to cause the direction or management of such whether by contract or including but not limited to software source documentation source
Definition LICENSE.txt:28
clang::SourceManager & getSourceManager() const
Get the source manager instance.
bool isValid() const
Check if the lexer was successfully created.
ClangLexerContext(mlir::StringRef source, mlir::StringRef bufferName="input")
Construct a lexer context for the given source code.
clang::Lexer & getLexer() const
Get the lexer instance.
Structure to represent a parsed method signature from an extraClassDeclaration
bool isConst
Whether the method is const-qualified.
bool hasParameters
Whether the method has parameters (unsupported for now)
std::vector< MethodParameter > parameters
The parameters of the method.
std::string returnType
The C++ return type of the method.
std::string methodName
The name of the method.
std::string documentation
Properly escaped documentation comment (if any)
virtual ~Generator()=default
Generator(std::string_view recordKind, llvm::raw_ostream &outputStream)
mlir::StringRef className
mlir::StringRef dialectNamespace
virtual void genExtraMethods(mlir::StringRef extraDecl) const
Generate code for extra methods from an extraClassDeclaration
virtual void setNamespaceAndClassName(const mlir::tblgen::Dialect &d, mlir::StringRef cppClassName)
Set the dialect and class name for code generation.
virtual void genExtraMethod(const ExtraMethod &method) const =0
Generate code for an extra method.
std::string dialectNameCapitalized
llvm::raw_ostream & os
std::string kind
Generator for common C header file elements.
Generator(std::string_view recordKind, llvm::raw_ostream &outputStream)
virtual void genPrologue() const
void genExtraMethod(const ExtraMethod &method) const override
Generate declaration for an extra method from an extraClassDeclaration
~HeaderGenerator() override=default
virtual void genEpilogue() const
virtual void genIsADecl() const
Generator for common C implementation file elements.
Generator(std::string_view recordKind, llvm::raw_ostream &outputStream)
virtual void genIsAImpl() const
void genExtraMethod(const ExtraMethod &method) const override
Generate implementation for an extra method from an extraClassDeclaration
virtual void genPrologue() const
~ImplementationGenerator() override=default
std::string name
The name of the parameter.
std::string type
The C++ type of the parameter.
MethodParameter(const std::string &paramType, const std::string &paramName)
Construct a new Method Parameter object.
Generator for common test implementation file elements.
virtual void genTestClassPrologue() const
Generate the test class prologue.
Generator(std::string_view recordKind, llvm::raw_ostream &outputStream)
~TestGenerator() override=default
void genExtraMethod(const ExtraMethod &method) const override
Generate test for an extra method from extraClassDeclaration.
virtual void genIsATest() const
Generate IsA test for a class.
virtual std::string genCleanup() const
Generate cleanup code for test methods.