Declaration pattern matching
To find and replace a declaration, create a struct that inherits publicly from
bronto::rewrite_decl (either directly or indirectly) and use the
BRONTO_BEFORE() and BRONTO_AFTER() annotations to identify declarations and
define their replacements.
struct StringConstructedFromEmpty : bronto::rewrite_decl {
// Matches string variable declarations initialized with an empty
// string literal.
BRONTO_BEFORE()
void before() { std::string var = ""; }
// Removes the initializer, since this is equivalent to default
// construction.
BRONTO_AFTER()
void after() { std::string var; }
};BRONTO_BEFORE
Requirements
Each struct inheriting from bronto::rewrite_decl must have at least one member
function or template member function marked BRONTO_BEFORE(). These functions
must contain a single variable declaration, representing the variable
declaration to be matched.
Semantics
The declared variable in the function annotated with BRONTO_BEFORE() defines
the pattern to be matched. The declared variable's type and initialization
determine what it matches against as explained below.
Specifically, for the variable type,
- If a concrete type is specified, only variables of that type will be considered.
- If the type is deduced via unconstrained
auto, variables of any type will be considered. - If the type is deduced via
autowith concept constraints, only variables whose type matches those constraints will be considered. - If the variable's type is dependent on some template parameter, only variables whose type matches that pattern will be considered.
- If any qualifiers (
constorvolatile) are specified in the pattern, the type must also have those qualifiers. If the qualifiers are not present, the type may (but is not required to) be qualified. For example, if the pattern specifiesconst int, bothconst intandconst volatile intwill match, but plainintwill not. If the pattern specifiesint, thenint,const int,volatile int, etc. will all match.
For initializers,
- If the variable is declared without an initializer, only default-initialized variables will be considered.
- If the variable is declared with an initializer, the variable must be initialized in order to match (though the style of initialization is not considered). Moreover, the initializing expression in a declaration must match the initializer specified in the pattern.
Notably, variable names are not considered or modified.
Here are several examples:
// Matches an string variable initialized with an empty string literal.
// This includes any initialization form such as `std::string s{""}`
// or `std::string s("")`;
struct InitializedWithStringLiteral : bronto::rewrite_decl {
BRONTO_BEFORE()
void before() {
std::string s = "";
}
};
// Matches any default-initialized `std::function`.
struct StdFunctions : bronto::rewrite_decl {
template <typename FnType>
BRONTO_BEFORE()
void before() {
std::function<FnType> f;
}
};
// Matches any integral type initialized with some expression.
struct Integral : bronto::rewrite_decl {
BRONTO_BEFORE()
void before(auto expr) {
std::integral auto n = expr;
}
};
// Matches any integral type initialized with some expression of a
// different type.
struct IntegralWithCast : bronto::rewrite_decl {
template <std::integral T, typename E>
BRONTO_BEFORE()
void before(E expr) requires (not std::same_as<T, E>) {
T n = expr;
}
};BRONTO_AFTER
Requirements
Each struct inheriting from bronto::rewrite_decl must have at most one member
function or template member function marked BRONTO_AFTER(). The function must
have a single declaration representing the replacement of the matched variable.
Semantics
The variable declared in the function annotated with BRONTO_AFTER represents
the intended replacement. Each captured expression or template parameter is used
in place of the corresponding parameter or template parameter. The replaced
variable name will remain unchanged.
BRONTO_USAGE
Declaration patterns may also have nested structs annotated with
BRONTO_USAGE(...) to indicate that they place constraints on how the variable is
used. Such rules match against references to the declared variable and work the
same as expression patterns.
There are three versions of BRONTO_USAGE(...) described below. Multiple
constraints can be placed on variable usages, and all of them must hold for
the variable to be rewritten.
BRONTO_USAGE(required)
Constrains the rewrite to occur only when at least one instance of the required pattern is found. The rule may also specify how those usages are rewritten.
struct Rule : bronto::rewrite_decl {
BRONTO_BEFORE()
void before(auto expr) {
time_t t = expr;
}
BRONTO_AFTER()
void after(auto expr) {
auto t = std::chrono::system_clock::from_time_t(expr);
}
struct BRONTO_USAGE(required) Usage : bronto::rewrite_expr {
// Only do this rewrite if the variable is wrapped in a
// `std::chrono::time_point<std::chrono::system_clock>` constructor.
BRONTO_BEFORE()
auto before(time_t usage) {
return std::chrono::system_clock::from_time_t(usage);
}
// And remove that wrapper anywhere it is present.
BRONTO_AFTER()
auto before(
std::chrono::time_point<std::chrono::system_clock> usage) {
return usage;
}
};
};BRONTO_USAGE(forbidden)
Constrains the rewrite to occur only when no usages match the pattern.
struct Rule : bronto::rewrite_decl {
BRONTO_BEFORE()
void before(auto expr) {
time_t t = expr;
}
BRONTO_AFTER()
void after(auto expr) {
std::chrono::time_point<std::chrono::system_clock> t(expr);
}
struct BRONTO_USAGE(forbidden) Usage : bronto::rewrite_expr {
// Do not apply this rewrite if the address of the variable is taken.
BRONTO_BEFORE()
auto before(time_t usage) { return &usage; }
};
};BRONTO_USAGE(allowed)
Makes no additional constraints about when a rewrite will occur, but offers rewrites to be applied to usages of the declared variable.
struct Rule : bronto::rewrite_decl {
BRONTO_BEFORE()
void before(auto expr) { time_t t = expr; }
BRONTO_AFTER()
void after(auto expr) {
std::chrono::time_point<std::chrono::system_clock> t(expr);
}
struct BRONTO_USAGE(allowed) Usage : bronto::rewrite_expr {
// Any usage of the variable matching this pattern should be
// rewritten.
BRONTO_BEFORE()
auto before(time_t usage) { return usage = time(0); }
BRONTO_AFTER()
auto after(
std::chrono::time_point<std::chrono::system_clock> usage) {
return usage = std::chrono::system_clock::now();
}
};
};