thrift/compiler/sema/standard_mutator.cc (192 lines of code) (raw):
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <thrift/compiler/sema/standard_mutator.h>
#include <thrift/compiler/sema/patch_mutator.h>
#include <thrift/compiler/sema/standard_mutator_stage.h>
namespace apache {
namespace thrift {
namespace compiler {
constexpr auto kTerseWriteUri =
"facebook.com/thrift/annotation/thrift/TerseWrite";
constexpr auto kMergeFromUri = "facebook.com/thrift/annotation/meta/MergeFrom";
// TODO(afuller): Instead of mutating the AST, readers should look for
// the interaction level annotation and the validation logic should be moved to
// a standard validator.
void propagate_process_in_event_base_annotation(
diagnostic_context& ctx, mutator_context&, t_interaction& node) {
for (auto* func : node.get_functions()) {
func->set_is_interaction_member();
ctx.failure_if(
func->has_annotation("thread"),
"Interaction methods cannot be individually annotated with "
"thread='eb'. Use process_in_event_base on the interaction instead.");
}
if (node.has_annotation("process_in_event_base")) {
ctx.failure_if(
node.has_annotation("serial"), "EB interactions are already serial");
for (auto* func : node.get_functions()) {
func->set_annotation("thread", "eb");
}
}
}
void remove_param_list_field_qualifiers(
diagnostic_context& ctx, mutator_context&, t_function& node) {
for (auto& field : node.params().fields()) {
switch (field.qualifier()) {
case t_field_qualifier::unspecified:
continue;
case t_field_qualifier::required:
ctx.warning("optional keyword is ignored in argument lists.");
break;
case t_field_qualifier::optional:
ctx.warning("required keyword is ignored in argument lists.");
break;
case t_field_qualifier::terse:
ctx.warning(
"@thrift.TerseWrite annotation is ignored in argument lists.");
break;
}
field.set_qualifier(t_field_qualifier::unspecified);
}
}
// Only an unqualified field is eligible for terse write.
void mutate_terse_write_annotation_field(
diagnostic_context& ctx, mutator_context&, t_field& node) {
const t_const* terse_write_annotation =
node.find_structured_annotation_or_null(kTerseWriteUri);
if (terse_write_annotation) {
auto qual = node.qualifier();
ctx.check(qual == t_field_qualifier::unspecified, [&](auto& o) {
o << "`@thrift.TerseWrite` cannot be used with qualified fields. Remove `"
<< (qual == t_field_qualifier::required ? "required" : "optional")
<< "` qualifier from field `" << node.name() << "`.";
});
node.set_qualifier(t_field_qualifier::terse);
}
}
// Only an unqualified field is eligible for terse write.
void mutate_terse_write_annotation_struct(
diagnostic_context&, mutator_context&, t_struct& node) {
const t_const* terse_write_annotation =
node.find_structured_annotation_or_null(kTerseWriteUri);
if (terse_write_annotation) {
for (auto& field : node.fields()) {
if (field.qualifier() == t_field_qualifier::unspecified) {
field.set_qualifier(t_field_qualifier::terse);
}
}
}
}
void mutate_merge_from(
diagnostic_context& ctx, mutator_context&, t_struct& node) {
const t_const* merge_from_annotation =
node.find_structured_annotation_or_null(kMergeFromUri);
if (!merge_from_annotation) {
return;
}
const auto& annotations = merge_from_annotation->value()->get_map();
const auto it = std::find_if(
annotations.begin(), annotations.end(), [](const auto& item) {
return item.first->get_string() == "type";
});
if (it == annotations.end()) {
ctx.failure([&](auto& o) {
o << "`@meta.MergeFrom` cannot be used without `type` specified in `"
<< node.name() << "`.";
});
return;
}
std::string type_string = it->second->get_string();
// If the specified type is from the current program, append the current
// program name.
if (type_string.find(".") == std::string::npos) {
type_string = node.program()->name() + "." + type_string;
}
const auto* type = node.program()->scope()->find_type(type_string);
if (!type) {
ctx.failure([&](auto& o) {
o << "Can not find expected type `" << it->second->get_string()
<< "` specified in `@meta.MergeFrom` in the current scope."
<< " Please check the include.";
});
return;
}
const auto* structured = dynamic_cast<const t_struct*>(type);
// We only allow merging from a struct type.
if (structured == nullptr || type->is_union() || type->is_exception() ||
type->is_paramlist()) {
ctx.failure([&](auto& o) {
o << "`" << it->second->get_string() << "` is not a struct type."
<< " `@meta.MergeFrom` can be only used with a struct type.";
});
return;
}
for (const auto& field : structured->fields()) {
ctx.failure_if(
!node.try_append_field(field.clone_DO_NOT_USE()), [&](auto& o) {
o << "Field id `" << field.id() << "` is already used in `"
<< node.name() << "`.";
});
}
}
void assign_uri(diagnostic_context& ctx, mutator_context&, t_named& node) {
if (auto* uri = node.find_annotation_or_null("thrift.uri")) {
// Manually assigned.
node.set_uri(*uri);
return;
}
auto* program = dynamic_cast<const t_program*>(ctx.root());
assert(program != nullptr);
if (program != nullptr && !program->package().empty()) {
// Derive from package.
node.set_uri(program->package().get_uri(node.name()));
}
}
void rectify_returned_interactions(
diagnostic_context& ctx, mutator_context&, t_function& node) {
auto check_is_interaction = [&](auto node) {
const auto* type = node->get_true_type();
ctx.check(
type->is_service() &&
static_cast<const t_service*>(type)->is_interaction(),
"Only an interaction is allowed in this position");
};
if (node.is_interaction_constructor()) {
// uses old syntax
return;
}
if (const auto& ret = node.returned_interaction()) {
check_is_interaction(*ret);
} else if (node.return_type()->is_service()) {
check_is_interaction(node.return_type());
node.set_returned_interaction(node.return_type());
node.set_return_type(t_base_type::t_void());
} else if (node.return_type()->is_streamresponse()) {
auto& stream =
static_cast<const t_stream_response&>(node.return_type().deref());
if (stream.first_response_type() &&
stream.first_response_type()->deref().is_service()) {
check_is_interaction(&stream.first_response_type()->deref());
node.set_returned_interaction(stream.first_response_type());
const_cast<t_stream_response&>(stream).set_first_response_type(
boost::none);
}
} else if (node.return_type()->is_sink()) {
auto& sink = static_cast<const t_sink&>(node.return_type().deref());
if (sink.first_response_type() &&
sink.first_response_type()->deref().is_service()) {
check_is_interaction(&sink.first_response_type()->deref());
node.set_returned_interaction(sink.first_response_type());
const_cast<t_sink&>(sink).set_first_response_type(boost::none);
}
}
}
ast_mutators standard_mutators() {
ast_mutators mutators;
{
auto& initial = mutators[standard_mutator_stage::initial];
initial.add_root_definition_visitor(&assign_uri);
initial.add_interaction_visitor(
&propagate_process_in_event_base_annotation);
initial.add_function_visitor(&remove_param_list_field_qualifiers);
initial.add_function_visitor(&rectify_returned_interactions);
}
{
auto& main = mutators[standard_mutator_stage::main];
main.add_field_visitor(&mutate_terse_write_annotation_field);
main.add_struct_visitor(&mutate_terse_write_annotation_struct);
main.add_struct_visitor(&mutate_merge_from);
}
add_patch_mutators(mutators);
return mutators;
}
} // namespace compiler
} // namespace thrift
} // namespace apache