thrift/compiler/generate/t_mstch_py3_generator.cc (1,302 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 <boost/algorithm/string.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/filesystem.hpp>
#include <thrift/compiler/ast/t_service.h>
#include <thrift/compiler/gen/cpp/reference_type.h>
#include <thrift/compiler/generate/common.h>
#include <thrift/compiler/generate/t_mstch_generator.h>
#include <thrift/compiler/generate/t_mstch_objects.h>
#include <thrift/compiler/lib/cpp2/util.h>
#include <thrift/compiler/lib/py3/util.h>
namespace apache {
namespace thrift {
namespace compiler {
namespace {
std::vector<t_function*> lifecycleFunctions() {
static t_function onStartServing_{
nullptr, {t_base_type::t_void()}, "onStartServing"},
onStopRequested_{nullptr, {t_base_type::t_void()}, "onStopRequested"};
return {&onStartServing_, &onStopRequested_};
}
mstch::array createStringArray(const std::vector<std::string>& values) {
mstch::array a;
for (auto it = values.begin(); it != values.end(); ++it) {
a.push_back(mstch::map{
{"value", *it},
{"first?", it == values.begin()},
{"last?", std::next(it) == values.end()},
});
}
return a;
}
std::vector<std::string> get_py3_namespace(const t_program* prog) {
return split_namespace(prog->get_namespace("py3"));
}
std::vector<std::string> get_py3_namespace_with_name(const t_program* prog) {
auto ns = get_py3_namespace(prog);
ns.push_back(prog->name());
return ns;
}
const t_type* get_list_elem_type(const t_type& type) {
assert(type.is_list());
return dynamic_cast<const t_list&>(type).get_elem_type();
}
const t_type* get_set_elem_type(const t_type& type) {
assert(type.is_set());
return dynamic_cast<const t_set&>(type).get_elem_type();
}
const t_type* get_map_key_type(const t_type& type) {
assert(type.is_map());
return dynamic_cast<const t_map&>(type).get_key_type();
}
const t_type* get_map_val_type(const t_type& type) {
assert(type.is_map());
return dynamic_cast<const t_map&>(type).get_val_type();
}
std::string get_cpp_template(const t_type& type) {
if (const auto* val =
type.find_annotation_or_null({"cpp.template", "cpp2.template"})) {
return *val;
} else if (type.is_list()) {
return "std::vector";
} else if (type.is_set()) {
return "std::set";
} else if (type.is_map()) {
return "std::map";
}
return {};
}
const t_type* get_stream_first_response_type(const t_type& type) {
assert(type.is_streamresponse());
return dynamic_cast<const t_stream_response&>(type).get_first_response_type();
}
const t_type* get_stream_elem_type(const t_type& type) {
assert(type.is_streamresponse());
return dynamic_cast<const t_stream_response&>(type).get_elem_type();
}
bool is_func_supported(bool no_stream, const t_function* func) {
return !(no_stream && func->returns_stream()) && !func->returns_sink() &&
!func->get_returntype()->is_service();
}
class mstch_py3_type : public mstch_type {
public:
struct CachedProperties {
const std::string cppTemplate;
std::string cppType;
std::string flatName;
};
mstch_py3_type(
const t_type* type,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION const pos,
const t_program* prog,
CachedProperties& cachedProps)
: mstch_type(type, generators, cache, pos),
prog_{prog},
cachedProps_{cachedProps} {
strip_cpp_comments_and_newlines(cachedProps_.cppType);
register_methods(
this,
{
{"type:modulePath", &mstch_py3_type::modulePath},
{"type:need_module_path?", &mstch_py3_type::need_module_path},
{"type:flat_name", &mstch_py3_type::flatName},
{"type:cppNamespaces", &mstch_py3_type::cppNamespaces},
{"type:cppTemplate", &mstch_py3_type::cppTemplate},
{"type:cythonTemplate", &mstch_py3_type::cythonTemplate},
{"type:defaultTemplate?", &mstch_py3_type::isDefaultTemplate},
{"type:cppCustomType", &mstch_py3_type::cppType},
{"type:cythonCustomType", &mstch_py3_type::cythonType},
{"type:hasCustomType?", &mstch_py3_type::hasCustomType},
{"type:number?", &mstch_py3_type::isNumber},
{"type:integer?", &mstch_py3_type::isInteger},
{"type:containerOfString?", &mstch_py3_type::isContainerOfString},
{"type:cythonTypeNoneable?", &mstch_py3_type::cythonTypeNoneable},
{"type:hasCythonType?", &mstch_py3_type::hasCythonType},
{"type:iobuf?", &mstch_py3_type::isIOBuf},
{"type:iobufRef?", &mstch_py3_type::isIOBufRef},
{"type:iobufWrapper?", &mstch_py3_type::isIOBufWrapper},
{"type:flexibleBinary?", &mstch_py3_type::isFlexibleBinary},
{"type:hasCustomTypeBehavior?",
&mstch_py3_type::hasCustomTypeBehavior},
{"type:simple?", &mstch_py3_type::isSimple},
{"type:resolves_to_complex_return?",
&mstch_py3_type::resolves_to_complex_return},
});
}
mstch::node need_module_path() {
if (!has_option("is_types_file")) {
return true;
}
if (const t_program* prog = type_->program()) {
if (prog != prog_) {
return true;
}
}
return false;
}
mstch::node modulePath() {
return "_" + boost::algorithm::join(get_type_py3_namespace(), "_");
}
mstch::node flatName() { return cachedProps_.flatName; }
mstch::node cppNamespaces() {
return createStringArray(get_type_cpp2_namespace());
}
mstch::node cppTemplate() { return cachedProps_.cppTemplate; }
mstch::node cythonTemplate() { return to_cython_template(); }
mstch::node isDefaultTemplate() { return is_default_template(); }
mstch::node cppType() { return cachedProps_.cppType; }
mstch::node cythonType() { return to_cython_type(); }
mstch::node hasCustomType() { return has_custom_cpp_type(); }
mstch::node isNumber() { return is_number(); }
mstch::node isInteger() { return is_integer(); }
mstch::node isContainerOfString() {
return is_list_of_string() || is_set_of_string();
}
mstch::node cythonTypeNoneable() { return !is_number() && has_cython_type(); }
mstch::node hasCythonType() { return has_cython_type(); }
mstch::node isIOBuf() { return is_iobuf(); }
mstch::node isIOBufRef() { return is_iobuf_ref(); }
mstch::node isIOBufWrapper() { return is_iobuf() || is_iobuf_ref(); }
mstch::node isFlexibleBinary() { return is_flexible_binary(); }
mstch::node hasCustomTypeBehavior() { return has_custom_type_behavior(); }
mstch::node isSimple() {
return (type_->is_base_type() || type_->is_enum()) &&
!has_custom_type_behavior();
}
mstch::node resolves_to_complex_return() {
return resolved_type_->is_container() ||
resolved_type_->is_string_or_binary() || resolved_type_->is_struct() ||
resolved_type_->is_xception();
}
const std::string& get_flat_name() const { return cachedProps_.flatName; }
void set_flat_name(std::string extra) {
std::string custom_prefix;
if (!is_default_template()) {
custom_prefix = to_cython_template() + "__";
} else {
if (cachedProps_.cppType != "") {
custom_prefix = to_cython_type() + "__";
}
}
const t_program* typeProgram = type_->program();
if (typeProgram && typeProgram != prog_) {
custom_prefix += typeProgram->name() + "_";
}
custom_prefix += extra;
cachedProps_.flatName = std::move(custom_prefix);
}
bool is_default_template() const {
return (!type_->is_container() && cachedProps_.cppTemplate == "") ||
(type_->is_list() && cachedProps_.cppTemplate == "std::vector") ||
(type_->is_set() && cachedProps_.cppTemplate == "std::set") ||
(type_->is_map() && cachedProps_.cppTemplate == "std::map");
}
bool has_custom_cpp_type() const { return cachedProps_.cppType != ""; }
protected:
const t_program* get_type_program() const {
if (const t_program* p = type_->program()) {
return p;
}
return prog_;
}
std::vector<std::string> get_type_py3_namespace() const {
auto ns = get_py3_namespace_with_name(get_type_program());
ns.push_back("types");
return ns;
}
std::vector<std::string> get_type_cpp2_namespace() const {
return cpp2::get_gen_namespace_components(*get_type_program());
}
std::string to_cython_template() const {
// handle special built-ins first:
if (cachedProps_.cppTemplate == "std::vector") {
return "vector";
} else if (cachedProps_.cppTemplate == "std::set") {
return "cset";
} else if (cachedProps_.cppTemplate == "std::map") {
return "cmap";
}
// then default handling:
return boost::algorithm::replace_all_copy(
cachedProps_.cppTemplate, "::", "_");
}
std::string to_cython_type() const {
if (cachedProps_.cppType == "") {
return "";
}
std::string cython_type = cachedProps_.cppType;
boost::algorithm::replace_all(cython_type, "::", "_");
boost::algorithm::replace_all(cython_type, "<", "_");
boost::algorithm::replace_all(cython_type, ">", "");
boost::algorithm::replace_all(cython_type, " ", "");
boost::algorithm::replace_all(cython_type, ", ", "_");
boost::algorithm::replace_all(cython_type, ",", "_");
return cython_type;
}
bool is_integer() const { return type_->is_any_int() || type_->is_byte(); }
bool is_number() const { return is_integer() || type_->is_floating_point(); }
bool is_list_of_string() {
if (!type_->is_list()) {
return false;
}
return get_list_elem_type(*type_)->is_string_or_binary();
}
bool is_set_of_string() {
if (!type_->is_set()) {
return false;
}
return get_set_elem_type(*type_)->is_string_or_binary();
}
bool has_cython_type() const { return !type_->is_container(); }
bool is_iobuf() const { return cachedProps_.cppType == "folly::IOBuf"; }
bool is_iobuf_ref() const {
return cachedProps_.cppType == "std::unique_ptr<folly::IOBuf>";
}
bool is_flexible_binary() const {
return type_->is_binary() && has_custom_cpp_type() && !is_iobuf() &&
!is_iobuf_ref() &&
// We know that folly::fbstring is completely substitutable for
// std::string and it's a common-enough type to special-case:
cachedProps_.cppType != "folly::fbstring" &&
cachedProps_.cppType != "::folly::fbstring";
}
bool has_custom_type_behavior() const {
return is_iobuf() || is_iobuf_ref() || is_flexible_binary();
}
const t_program* prog_;
CachedProperties& cachedProps_;
};
template <bool ForContainers = false>
class type_py3_generator : public type_generator {
public:
explicit type_py3_generator(const t_program* prog) : prog_{prog} {}
std::shared_ptr<mstch_base> generate(
const t_type* type,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION pos,
int32_t index) const override;
protected:
const t_program* prog_;
};
class mstch_py3_program : public mstch_program {
public:
mstch_py3_program(
const t_program* program,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION const pos)
: mstch_program{program, generators, cache, pos} {
register_methods(
this,
{
{"program:unique_functions_by_return_type",
&mstch_py3_program::unique_functions_by_return_type},
{"program:cppNamespaces", &mstch_py3_program::getCpp2Namespace},
{"program:py3Namespaces", &mstch_py3_program::getPy3Namespace},
{"program:includeNamespaces",
&mstch_py3_program::includeNamespaces},
{"program:cppIncludes", &mstch_py3_program::getCppIncludes},
{"program:containerTypes", &mstch_py3_program::getContainerTypes},
{"program:customTemplates", &mstch_py3_program::getCustomTemplates},
{"program:customTypes", &mstch_py3_program::getCustomTypes},
{"program:moveContainerTypes",
&mstch_py3_program::getMoveContainerTypes},
{"program:has_stream?", &mstch_py3_program::hasStream},
{"program:stream_types", &mstch_py3_program::getStreamTypes},
{"program:response_and_stream_types",
&mstch_py3_program::getResponseAndStreamTypes},
{"program:stream_exceptions",
&mstch_py3_program::getStreamExceptions},
{"program:cpp_gen_path", &mstch_py3_program::getCppGenPath},
});
gather_included_program_namespaces();
visit_types_for_services_and_interactions();
visit_types_for_objects();
visit_types_for_constants();
visit_types_for_typedefs();
visit_types_for_mixin_fields();
for (auto func : lifecycleFunctions()) {
addFunctionByUniqueReturnType(func);
}
}
mstch::node getCppGenPath() {
return std::string(has_option("py3cpp") ? "gen-py3cpp" : "gen-cpp2");
}
mstch::node getContainerTypes() {
type_py3_generator<true> generator{program_};
return generate_elements(containers_, &generator);
}
mstch::node getCppIncludes() {
mstch::array a;
for (const auto& include : program_->cpp_includes()) {
a.push_back(include);
}
return a;
}
mstch::node unique_functions_by_return_type() {
std::vector<const t_function*> functions;
bool no_stream = has_option("no_stream");
for (auto& kv : uniqueFunctionsByReturnType_) {
if (is_func_supported(no_stream, kv.second)) {
functions.push_back(kv.second);
}
}
return generate_functions(functions);
}
mstch::node getCustomTemplates() {
return generate_types_array(customTemplates_);
}
mstch::node getCustomTypes() { return generate_types_array(customTypes_); }
mstch::node getMoveContainerTypes() {
std::vector<const t_type*> types;
for (auto& it : moveContainers_) {
types.push_back(it.second);
}
return generate_types_array(types);
}
mstch::node getResponseAndStreamTypes() {
return generate_types_array(responseAndStreamTypes_);
}
mstch::node getStreamExceptions() {
std::vector<const t_type*> types;
for (auto& it : streamExceptions_) {
types.push_back(it.second);
}
return generate_types_array(types);
}
mstch::node getStreamTypes() {
std::vector<const t_type*> types;
if (!has_option("no_stream")) {
for (auto& it : streamTypes_) {
types.push_back(it.second);
}
}
return generate_types_array(types);
}
mstch::node includeNamespaces() {
mstch::array a;
for (auto& it : includeNamespaces_) {
a.push_back(mstch::map{
{"includeNamespace", createStringArray(it.second.ns)},
{"hasServices?", it.second.hasServices},
{"hasTypes?", it.second.hasTypes}});
}
return a;
}
mstch::node getCpp2Namespace() {
return createStringArray(cpp2::get_gen_namespace_components(*program_));
}
mstch::node getPy3Namespace() {
return createStringArray(get_py3_namespace(program_));
}
mstch::node hasStream() {
return !has_option("no_stream") && !streamTypes_.empty();
}
protected:
struct Namespace {
std::vector<std::string> ns;
bool hasServices;
bool hasTypes;
};
mstch::array generate_types_array(const std::vector<const t_type*>& types) {
return generate_types(types);
}
void gather_included_program_namespaces() {
for (const t_program* included_program :
program_->get_included_programs()) {
bool hasTypes =
!(included_program->objects().empty() &&
included_program->enums().empty() &&
included_program->typedefs().empty() &&
included_program->consts().empty());
includeNamespaces_[included_program->path()] = Namespace{
get_py3_namespace_with_name(included_program),
!included_program->services().empty(),
hasTypes,
};
}
}
void add_typedef_namespace(const t_type* type) {
auto prog = type->program();
if (prog && prog != program_) {
const auto& path = prog->path();
if (includeNamespaces_.find(path) != includeNamespaces_.end()) {
return;
}
auto ns = Namespace();
ns.ns = get_py3_namespace_with_name(prog);
ns.hasServices = false;
ns.hasTypes = true;
includeNamespaces_[path] = std::move(ns);
}
}
void addFunctionByUniqueReturnType(const t_function* function) {
const t_type* return_type = function->get_returntype();
auto sa = cpp2::is_stack_arguments(cache_->parsed_options_, *function);
uniqueFunctionsByReturnType_.insert(
{{visit_type(return_type), sa}, function});
}
void visit_type_single_service(const t_service* service) {
for (const auto& function : service->functions()) {
for (const auto& field : function.get_paramlist()->fields()) {
visit_type(field.get_type());
}
for (const auto& field : function.get_stream_xceptions()->fields()) {
const t_type* exType = field.get_type();
streamExceptions_.emplace(visit_type(field.get_type()), exType);
}
addFunctionByUniqueReturnType(&function);
}
}
void visit_types_for_services_and_interactions() {
for (const auto* service : program_->services()) {
visit_type_single_service(service);
}
for (const auto* interaction : program_->interactions()) {
visit_type_single_service(interaction);
}
}
void visit_types_for_objects() {
for (const auto& object : program_->objects()) {
for (auto&& field : object->fields()) {
visit_type(field.get_type());
}
}
}
void visit_types_for_constants() {
for (const auto& constant : program_->consts()) {
visit_type(constant->get_type());
}
}
void visit_types_for_typedefs() {
for (const auto typedef_def : program_->typedefs()) {
visit_type(typedef_def->get_type());
}
}
void visit_types_for_mixin_fields() {
for (const auto& strct : program_->structs()) {
for (const auto& m : cpp2::get_mixins_and_members(*strct)) {
visit_type(m.member->get_type());
}
}
}
enum TypeDef { NoTypedef, HasTypedef };
std::string visit_type(const t_type* orig_type) {
return visit_type_with_typedef(orig_type, TypeDef::NoTypedef);
}
std::string visit_type_with_typedef(
const t_type* orig_type, TypeDef isTypedef) {
auto trueType = orig_type->get_true_type();
auto baseType =
generators_->type_generator_->generate(trueType, generators_, cache_);
mstch_py3_type* type = dynamic_cast<mstch_py3_type*>(baseType.get());
const std::string& flatName = type->get_flat_name();
// Import all types either beneath a typedef, even if the current type is
// not directly a typedef
isTypedef = isTypedef == TypeDef::HasTypedef || orig_type->is_typedef()
? TypeDef::HasTypedef
: TypeDef::NoTypedef;
if (flatName.empty()) {
std::string extra;
if (trueType->is_list()) {
extra = "List__" +
visit_type_with_typedef(get_list_elem_type(*trueType), isTypedef);
} else if (trueType->is_set()) {
extra = "Set__" +
visit_type_with_typedef(get_set_elem_type(*trueType), isTypedef);
} else if (trueType->is_map()) {
extra = "Map__" +
visit_type_with_typedef(get_map_key_type(*trueType), isTypedef) +
"_" +
visit_type_with_typedef(get_map_val_type(*trueType), isTypedef);
} else if (trueType->is_binary()) {
extra = "binary";
} else if (trueType->is_streamresponse()) {
const t_type* respType = get_stream_first_response_type(*trueType);
const t_type* elemType = get_stream_elem_type(*trueType);
if (respType) {
extra += "ResponseAndStream__" +
visit_type_with_typedef(respType, isTypedef) + "_";
} else {
extra = "Stream__";
}
const auto& elemTypeName = visit_type_with_typedef(elemType, isTypedef);
extra += elemTypeName;
streamTypes_.emplace(elemTypeName, elemType);
} else if (trueType->is_sink()) {
return "";
} else {
extra = trueType->get_name();
}
type->set_flat_name(std::move(extra));
}
assert(!flatName.empty());
// If this type or a parent of this type is a typedef,
// then add the namespace of the *resolved* type:
// (parent matters if you have eg. typedef list<list<type>>)
if (isTypedef == TypeDef::HasTypedef) {
add_typedef_namespace(trueType);
}
bool inserted = seenTypeNames_.insert(flatName).second;
if (inserted) {
if (trueType->is_container()) {
containers_.push_back(trueType);
moveContainers_.emplace(
boost::algorithm::replace_all_copy(flatName, "binary", "string"),
trueType);
}
if (!type->is_default_template()) {
customTemplates_.push_back(trueType);
}
if (type->has_custom_cpp_type()) {
customTypes_.push_back(trueType);
}
if (trueType->is_streamresponse() &&
get_stream_first_response_type(*trueType)) {
responseAndStreamTypes_.push_back(trueType);
}
}
return flatName;
}
std::vector<const t_type*> containers_;
std::map<std::string, const t_type*> moveContainers_;
std::vector<const t_type*> customTemplates_;
std::vector<const t_type*> customTypes_;
std::unordered_set<std::string> seenTypeNames_;
std::map<std::string, Namespace> includeNamespaces_;
std::map<std::tuple<std::string, bool>, t_function const*>
uniqueFunctionsByReturnType_;
std::vector<const t_type*> responseAndStreamTypes_;
std::map<std::string, const t_type*> streamTypes_;
std::map<std::string, const t_type*> streamExceptions_;
};
class mstch_py3_function : public mstch_function {
public:
mstch_py3_function(
t_function const* function,
std::shared_ptr<mstch_generators const> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION const pos)
: mstch_function(function, generators, cache, pos),
cppName_{cpp2::get_name(function)} {
register_methods(
this,
{{"function:eb", &mstch_py3_function::event_based},
{"function:stack_arguments?", &mstch_py3_function::stack_arguments},
{"function:cppName", &mstch_py3_function::cppName}});
}
mstch::node cppName() { return cppName_; }
mstch::node event_based() {
return function_->get_annotation("thread") == "eb";
}
mstch::node stack_arguments() {
return cpp2::is_stack_arguments(cache_->parsed_options_, *function_);
}
protected:
const std::string cppName_;
};
class mstch_py3_service : public mstch_service {
public:
mstch_py3_service(
const t_service* service,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION const pos,
const t_program* prog)
: mstch_service(service, generators, cache, pos), prog_{prog} {
register_methods(
this,
{
{"service:externalProgram?", &mstch_py3_service::isExternalProgram},
{"service:cppNamespaces", &mstch_py3_service::cppNamespaces},
{"service:py3Namespaces", &mstch_py3_service::py3Namespaces},
{"service:programName", &mstch_py3_service::programName},
{"service:includePrefix", &mstch_py3_service::includePrefix},
{"service:cpp_name", &mstch_py3_service::cpp_name},
{"service:parent_service_name",
&mstch_py3_service::parent_service_name},
{"service:parent_service_cpp_name",
&mstch_py3_service::parent_service_cpp_name},
{"service:qualified_name", &mstch_py3_service::qualified_name},
{"service:supportedFunctions",
&mstch_py3_service::get_supported_functions},
{"service:lifecycleFunctions",
&mstch_py3_service::get_lifecycle_functions},
{"service:supportedFunctionsWithLifecycle",
&mstch_py3_service::get_supported_functions_with_lifecycle},
});
}
mstch::node isExternalProgram() { return prog_ != service_->program(); }
mstch::node cppNamespaces() {
return createStringArray(
cpp2::get_gen_namespace_components(*service_->program()));
}
mstch::node py3Namespaces() {
return createStringArray(
split_namespace(service_->program()->get_namespace("py3")));
}
mstch::node programName() { return service_->program()->name(); }
mstch::node includePrefix() { return service_->program()->include_prefix(); }
mstch::node cpp_name() { return cpp2::get_name(service_); }
mstch::node qualified_name() {
return cpp2::get_gen_namespace(*service_->program()) +
"::" + cpp2::get_name(service_);
}
mstch::node parent_service_name() {
return cache_->parsed_options_.at("parent_service_name");
}
mstch::node parent_service_cpp_name() {
return cache_->parsed_options_.at("parent_service_cpp_name");
}
std::vector<t_function*> supportedFunctions() {
std::vector<t_function*> funcs;
bool no_stream = has_option("no_stream");
for (auto func : service_->get_functions()) {
if (is_func_supported(no_stream, func)) {
funcs.push_back(func);
}
}
return funcs;
}
mstch::node get_lifecycle_functions() {
return generate_functions(lifecycleFunctions());
}
mstch::node get_supported_functions() {
return generate_functions(supportedFunctions());
}
mstch::node get_supported_functions_with_lifecycle() {
auto funcs = supportedFunctions();
for (auto func : lifecycleFunctions()) {
funcs.push_back(func);
}
return generate_functions(funcs);
}
protected:
const t_program* prog_;
};
class mstch_py3_field : public mstch_field {
public:
enum class RefType : uint8_t {
NotRef,
Unique,
Shared,
SharedConst,
IOBuf,
};
mstch_py3_field(
const t_field* field,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION const pos,
int32_t index,
field_generator_context const* field_context)
: mstch_field(field, generators, cache, pos, index, field_context),
pyName_{py3::get_py3_name(*field)},
cppName_{cpp2::get_name(field)} {
register_methods(
this,
{
{"field:py_name", &mstch_py3_field::pyName},
{"field:reference?", &mstch_py3_field::isRef},
{"field:unique_ref?", &mstch_py3_field::isUniqueRef},
{"field:shared_ref?", &mstch_py3_field::isSharedRef},
{"field:shared_const_ref?", &mstch_py3_field::isSharedConstRef},
{"field:iobuf_ref?", &mstch_py3_field::isIOBufRef},
{"field:has_ref_accessor?", &mstch_py3_field::hasRefAccessor},
{"field:hasDefaultValue?", &mstch_py3_field::hasDefaultValue},
{"field:PEP484Optional?", &mstch_py3_field::isPEP484Optional},
{"field:isset?", &mstch_py3_field::isSet},
{"field:cppName", &mstch_py3_field::cppName},
{"field:hasModifiedName?", &mstch_py3_field::hasModifiedName},
{"field:hasPyName?", &mstch_py3_field::hasPyName},
{"field:boxed_ref?", &mstch_py3_field::boxed_ref},
});
}
mstch::node isRef() { return is_ref(); }
mstch::node isUniqueRef() { return get_ref_type() == RefType::Unique; }
mstch::node isSharedRef() { return get_ref_type() == RefType::Shared; }
mstch::node isSharedConstRef() {
return get_ref_type() == RefType::SharedConst;
}
mstch::node isIOBufRef() { return get_ref_type() == RefType::IOBuf; }
mstch::node hasRefAccessor() {
auto ref_type = get_ref_type();
return (ref_type == RefType::NotRef || ref_type == RefType::IOBuf);
}
mstch::node hasDefaultValue() { return has_default_value(); }
mstch::node isPEP484Optional() { return !has_default_value(); }
mstch::node isSet() {
auto ref_type = get_ref_type();
return (ref_type == RefType::NotRef || ref_type == RefType::IOBuf) &&
field_->get_req() != t_field::e_req::required;
}
mstch::node pyName() { return pyName_; }
mstch::node cppName() { return cppName_; }
mstch::node hasModifiedName() { return pyName_ != cppName_; }
mstch::node hasPyName() { return pyName_ != field_->get_name(); }
bool has_default_value() {
return !is_ref() && (field_->get_value() != nullptr || !is_optional());
}
mstch::node boxed_ref() {
return gen::cpp::find_ref_type(*field_) == gen::cpp::reference_type::boxed;
}
protected:
RefType get_ref_type() {
if (ref_type_cached_) {
return ref_type_;
}
ref_type_cached_ = true;
switch (gen::cpp::find_ref_type(*field_)) {
case gen::cpp::reference_type::unique: {
return ref_type_ = RefType::Unique;
}
case gen::cpp::reference_type::shared_const: {
return ref_type_ = RefType::SharedConst;
}
case gen::cpp::reference_type::shared_mutable: {
return ref_type_ = RefType::Shared;
}
case gen::cpp::reference_type::boxed: {
return ref_type_ = RefType::NotRef;
}
case gen::cpp::reference_type::none: {
const t_type* resolved_type = field_->get_type()->get_true_type();
if (cpp2::get_type(resolved_type) == "std::unique_ptr<folly::IOBuf>") {
return ref_type_ = RefType::IOBuf;
}
return ref_type_ = RefType::NotRef;
}
case gen::cpp::reference_type::unrecognized: {
// It is legal to get here but hopefully nobody will in practice, since
// we're not set up to handle other kinds of refs:
throw std::runtime_error{"Unrecognized ref_type"};
}
}
// Suppress "control reaches end of non-void function" warning
throw std::logic_error{"Unhandled ref_type"};
}
bool is_optional() const {
return field_->get_req() == t_field::e_req::optional;
}
bool is_ref() { return get_ref_type() != RefType::NotRef; }
RefType ref_type_{RefType::NotRef};
bool ref_type_cached_ = false;
const std::string pyName_;
const std::string cppName_;
};
class mstch_py3_struct : public mstch_struct {
public:
mstch_py3_struct(
const t_struct* strct,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION const pos)
: mstch_struct(strct, generators, cache, pos) {
register_methods(
this,
{
{"struct:size", &mstch_py3_struct::getSize},
{"struct:is_struct_orderable?",
&mstch_py3_struct::isStructOrderable},
{"struct:cpp_noncomparable", &mstch_py3_struct::cppNonComparable},
{"struct:cpp_noncopyable?", &mstch_py3_struct::cppNonCopyable},
{"struct:exception_message?",
&mstch_py3_struct::hasExceptionMessage},
{"struct:exception_message", &mstch_py3_struct::exceptionMessage},
{"struct:fields_and_mixin_fields",
&mstch_py3_struct::fields_and_mixin_fields},
{"struct:py3_fields", &mstch_py3_struct::py3_fields},
{"struct:py3_fields?", &mstch_py3_struct::has_py3_fields},
});
py3_fields_ = strct_->fields().copy();
py3_fields_.erase(
std::remove_if(
py3_fields_.begin(),
py3_fields_.end(),
[](t_field const* field) {
return field->has_annotation("py3.hidden");
}),
py3_fields_.end());
}
mstch::node getSize() { return std::to_string(py3_fields_.size()); }
mstch::node isStructOrderable() {
return cpp2::is_orderable(*strct_) &&
!strct_->has_annotation("no_default_comparators");
}
mstch::node cppNonComparable() {
return strct_->has_annotation({"cpp.noncomparable", "cpp2.noncomparable"});
}
mstch::node cppNonCopyable() {
return strct_->has_annotation({"cpp.noncopyable", "cpp2.noncopyable"});
}
mstch::node hasExceptionMessage() {
return strct_->has_annotation("message");
}
mstch::node exceptionMessage() { return strct_->get_annotation("message"); }
mstch::node py3_fields() { return generate_fields(py3_fields_); }
mstch::node has_py3_fields() { return !py3_fields_.empty(); }
mstch::node fields_and_mixin_fields() {
std::vector<t_field const*> fields = py3_fields_;
for (auto m : cpp2::get_mixins_and_members(*strct_)) {
fields.push_back(m.member);
}
return generate_fields(fields);
}
private:
std::vector<t_field const*> py3_fields_;
};
class mstch_py3_enum : public mstch_enum {
public:
mstch_py3_enum(
const t_enum* enm,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION const pos)
: mstch_enum(enm, generators, cache, pos) {
register_methods(
this,
{
{"enum:flags?", &mstch_py3_enum::hasFlags},
{"enum:cpp_name", &mstch_py3_enum::cpp_name},
});
}
mstch::node hasFlags() { return enm_->has_annotation("py3.flags"); }
mstch::node cpp_name() { return cpp2::get_name(enm_); }
};
class mstch_py3_enum_value : public mstch_enum_value {
public:
mstch_py3_enum_value(
const t_enum_value* enm_value,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION const pos)
: mstch_enum_value(enm_value, generators, cache, pos) {
register_methods(
this,
{
{"enum_value:py_name", &mstch_py3_enum_value::pyName},
{"enum_value:cppName", &mstch_py3_enum_value::cppName},
{"enum_value:hasPyName?", &mstch_py3_enum_value::hasPyName},
});
}
mstch::node pyName() { return py3::get_py3_name(*enm_value_); }
mstch::node cppName() { return cpp2::get_name(enm_value_); }
mstch::node hasPyName() {
return py3::get_py3_name(*enm_value_) != enm_value_->get_name();
}
};
class mstch_py3_container_type : public mstch_py3_type {
public:
mstch_py3_container_type(
const t_type* type,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION const pos,
const t_program* prog,
CachedProperties& cachedProps)
: mstch_py3_type(type, generators, cache, pos, prog, cachedProps) {
register_methods(
this,
{
{"containerType:flat_name",
&mstch_py3_container_type::containerTypeFlatName},
});
}
mstch::node containerTypeFlatName() {
assert(type_->is_container());
return cachedProps_.flatName;
}
};
class mstch_py3_annotation : public mstch_annotation {
public:
mstch_py3_annotation(
const t_annotation& annotation,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION pos,
int32_t index)
: mstch_annotation(
annotation.first,
annotation.second,
generators,
cache,
pos,
index) {
register_methods(
this,
{
{"annotation:value?", &mstch_py3_annotation::hasValue},
{"annotation:py_quoted_key", &mstch_py3_annotation::pyQuotedKey},
{"annotation:py_quoted_value",
&mstch_py3_annotation::pyQuotedValue},
});
}
mstch::node hasValue() { return !val_.value.empty(); }
mstch::node pyQuotedKey() { return to_python_string_literal(key_); }
mstch::node pyQuotedValue() { return to_python_string_literal(val_.value); }
protected:
std::string to_python_string_literal(std::string val) const {
std::string quotes = "\"\"\"";
boost::algorithm::replace_all(val, "\\", "\\\\");
boost::algorithm::replace_all(val, "\"", "\\\"");
return quotes + val + quotes;
}
};
class program_py3_generator : public program_generator {
public:
std::shared_ptr<mstch_base> generate(
const t_program* program,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION pos,
int32_t /*index*/) const override {
const std::string& id = program->path();
auto it = cache->programs_.find(id);
if (it != cache->programs_.end()) {
return it->second;
}
auto r = cache->programs_.emplace(
id,
std::make_shared<mstch_py3_program>(program, generators, cache, pos));
return r.first->second;
}
std::unordered_map<const t_type*, mstch_py3_type::CachedProperties>
typePropsCache;
};
class struct_py3_generator : public struct_generator {
public:
std::shared_ptr<mstch_base> generate(
const t_struct* strct,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION pos,
int32_t /*index*/) const override {
return std::make_shared<mstch_py3_struct>(strct, generators, cache, pos);
}
};
class function_py3_generator : public function_generator {
public:
function_py3_generator() = default;
~function_py3_generator() override = default;
std::shared_ptr<mstch_base> generate(
t_function const* function,
std::shared_ptr<mstch_generators const> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION pos,
int32_t /*index*/) const override {
return std::make_shared<mstch_py3_function>(
function, generators, cache, pos);
}
};
class service_py3_generator : public service_generator {
public:
explicit service_py3_generator(const t_program* prog) : prog_{prog} {}
std::shared_ptr<mstch_base> generate(
const t_service* service,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION pos,
int32_t /*index*/) const override {
return std::make_shared<mstch_py3_service>(
service, generators, cache, pos, prog_);
}
protected:
const t_program* prog_;
};
class field_py3_generator : public field_generator {
public:
std::shared_ptr<mstch_base> generate(
const t_field* field,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION pos,
int32_t index,
field_generator_context const* field_context) const override {
return std::make_shared<mstch_py3_field>(
field, generators, cache, pos, index, field_context);
}
};
class enum_py3_generator : public enum_generator {
public:
std::shared_ptr<mstch_base> generate(
const t_enum* enm,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION pos,
int32_t /*index*/) const override {
return std::make_shared<mstch_py3_enum>(enm, generators, cache, pos);
}
};
class enum_value_py3_generator : public enum_value_generator {
public:
std::shared_ptr<mstch_base> generate(
const t_enum_value* enm_value,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION pos,
int32_t /*index*/) const override {
return std::make_shared<mstch_py3_enum_value>(
enm_value, generators, cache, pos);
}
};
class annotation_py3_generator : public annotation_generator {
public:
std::shared_ptr<mstch_base> generate(
const t_annotation& annotation,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION pos,
int32_t index) const override {
return std::make_shared<mstch_py3_annotation>(
annotation, generators, cache, pos, index);
}
};
/**
* Generator-specific validator that enforces that reserved key is
* not used as namespace field name.
*
*/
class no_reserved_key_in_namespace_validator : virtual public validator {
public:
using validator::visit;
bool visit(t_program* const prog) override {
set_program(prog);
validate(prog);
return true;
}
private:
void validate(t_program* const prog) {
const auto& py3_namespace = prog->get_namespace("py3");
if (py3_namespace.empty()) {
return;
}
std::vector<std::string> namespace_tokens = split_namespace(py3_namespace);
for (const auto& field_name : namespace_tokens) {
if (get_python_reserved_names().find(field_name) !=
get_python_reserved_names().end()) {
std::ostringstream ss;
ss << "Namespace '" << py3_namespace << "' contains reserved keyword '"
<< field_name << "'";
add_error(boost::none, ss.str());
}
}
std::string filepath_delimiters("\\/.");
std::vector<std::string> fields;
boost::split(fields, prog->path(), boost::is_any_of(filepath_delimiters));
for (const auto& field : fields) {
if (field == "include") {
std::ostringstream ss;
ss << "Path '" << prog->path()
<< "' contains reserved keyword 'include'";
add_error(boost::none, ss.str());
}
}
}
};
/**
* Generator-specific validator that enforces "name" and "value" is not used as
* enum member or union field names (thrift-py3)
*/
class enum_member_union_field_names_validator : virtual public validator {
public:
using validator::visit;
bool visit(t_enum* enm) override {
for (const t_enum_value* ev : enm->get_enum_values()) {
validate(ev, ev->get_name());
}
return true;
}
bool visit(t_struct* s) override {
if (!s->is_union()) {
return false;
}
for (const t_field& f : s->fields()) {
validate(&f, f.name());
}
return true;
}
private:
void validate(const t_named* node, const std::string& name) {
const auto& pyname = node->get_annotation("py3.name", &name);
if (pyname == "name" || pyname == "value") {
std::ostringstream ss;
ss << "'" << pyname
<< "' should not be used as an enum/union field name in thrift-py3. "
<< "Use a different name or annotate the field with `(py3.name=\"<new_py_name>\")`";
add_error(node->get_lineno(), ss.str());
}
}
};
class t_mstch_py3_generator : public t_mstch_generator {
public:
t_mstch_py3_generator(
t_program* program,
t_generation_context context,
const std::map<std::string, std::string>& parsed_options,
const std::string& /* option_string unused */)
: t_mstch_generator(program, std::move(context), "py3", parsed_options),
generateRootPath_{package_to_path()} {
out_dir_base_ = "gen-py3";
auto include_prefix = get_option("include_prefix");
if (!include_prefix.empty()) {
program->set_include_prefix(std::move(include_prefix));
}
}
void generate_program() override {
set_mstch_generators();
generate_init_files();
generate_types();
generate_services();
}
void fill_validator_list(validator_list& vl) const override {
vl.add<no_reserved_key_in_namespace_validator>();
vl.add<enum_member_union_field_names_validator>();
}
enum TypesFile { IsTypesFile, NotTypesFile };
protected:
bool should_resolve_typedefs() const override { return true; }
void set_mstch_generators();
void generate_init_files();
void generate_file(
const std::string& file,
TypesFile is_types_file,
const boost::filesystem::path& base);
void generate_types();
void generate_services();
boost::filesystem::path package_to_path();
const boost::filesystem::path generateRootPath_;
};
} // namespace
template <bool ForContainers>
std::shared_ptr<mstch_base> type_py3_generator<ForContainers>::generate(
const t_type* type,
std::shared_ptr<const mstch_generators> generators,
std::shared_ptr<mstch_cache> cache,
ELEMENT_POSITION pos,
int32_t /*index*/) const {
using T = std::
conditional_t<ForContainers, mstch_py3_container_type, mstch_py3_type>;
auto trueType = type->get_true_type();
auto& propsCache =
dynamic_cast<program_py3_generator*>(generators->program_generator_.get())
->typePropsCache;
auto it = propsCache.find(trueType);
if (it == propsCache.end()) {
propsCache.emplace(
trueType,
mstch_py3_type::CachedProperties{
get_cpp_template(*trueType), cpp2::get_type(trueType), {}});
}
return std::make_shared<T>(
trueType, generators, cache, pos, prog_, propsCache.at(trueType));
}
void t_mstch_py3_generator::set_mstch_generators() {
generators_->set_program_generator(std::make_unique<program_py3_generator>());
generators_->set_struct_generator(std::make_unique<struct_py3_generator>());
generators_->set_function_generator(
std::make_unique<function_py3_generator>());
generators_->set_service_generator(
std::make_unique<service_py3_generator>(get_program()));
generators_->set_field_generator(std::make_unique<field_py3_generator>());
generators_->set_enum_generator(std::make_unique<enum_py3_generator>());
generators_->set_enum_value_generator(
std::make_unique<enum_value_py3_generator>());
generators_->set_type_generator(
std::make_unique<type_py3_generator<false>>(get_program()));
generators_->set_annotation_generator(
std::make_unique<annotation_py3_generator>());
}
void t_mstch_py3_generator::generate_init_files() {
boost::filesystem::path p = generateRootPath_;
auto nodePtr = generators_->program_generator_->generate(
get_program(), generators_, cache_);
while (!p.empty()) {
render_to_file(nodePtr, "common/auto_generated_py", p / "__init__.py");
p = p.parent_path();
}
}
boost::filesystem::path t_mstch_py3_generator::package_to_path() {
auto package = get_program()->get_namespace("py3");
return boost::algorithm::replace_all_copy(package, ".", "/");
}
void t_mstch_py3_generator::generate_file(
const std::string& file,
TypesFile is_types_file,
const boost::filesystem::path& base = {}) {
auto program = get_program();
const auto& name = program->name();
if (is_types_file == IsTypesFile) {
cache_->parsed_options_["is_types_file"] = "";
} else {
cache_->parsed_options_.erase("is_types_file");
}
auto nodePtr =
generators_->program_generator_->generate(program, generators_, cache_);
render_to_file(nodePtr, file, base / name / file);
}
void t_mstch_py3_generator::generate_types() {
std::vector<std::string> cythonFilesWithTypeContext{
"types.pyx",
"types.pxd",
"types.pyi",
};
std::vector<std::string> cythonFilesNoTypeContext{
"types_reflection.pxd",
"types_reflection.pyx",
"types_fields.pxd",
"types_fields.pyx",
"builders.pxd",
"builders.pyx",
"builders.pyi",
"metadata.pxd",
"metadata.pyi",
"metadata.pyx",
};
std::vector<std::string> cppFilesWithTypeContext{
"types.h",
};
std::vector<std::string> cppFilesWithNoTypeContext{
"metadata.h",
"metadata.cpp",
};
for (const auto& file : cythonFilesWithTypeContext) {
generate_file(file, IsTypesFile, generateRootPath_);
}
for (const auto& file : cppFilesWithTypeContext) {
generate_file(file, IsTypesFile);
}
for (const auto& file : cythonFilesNoTypeContext) {
generate_file(file, NotTypesFile, generateRootPath_);
}
for (const auto& file : cppFilesWithNoTypeContext) {
generate_file(file, NotTypesFile);
}
}
void t_mstch_py3_generator::generate_services() {
if (get_program()->services().empty()) {
// There is no need to generate empty / broken code for non existent
// services.
return;
}
std::vector<std::string> cythonFiles{
"clients.pxd",
"clients.pyx",
"clients.pyi",
"clients_wrapper.pxd",
"services.pxd",
"services.pyx",
"services.pyi",
"services_wrapper.pxd",
"services_reflection.pxd",
"services_reflection.pyx",
};
std::vector<std::string> cppFiles{
"clients_wrapper.h",
"clients_wrapper.cpp",
"services_wrapper.h",
"services_wrapper.cpp",
};
for (const auto& file : cythonFiles) {
generate_file(file, NotTypesFile, generateRootPath_);
}
for (const auto& file : cppFiles) {
generate_file(file, NotTypesFile);
}
}
THRIFT_REGISTER_GENERATOR(
mstch_py3,
"Python 3",
" include_prefix: Use full include paths in generated files.\n");
} // namespace compiler
} // namespace thrift
} // namespace apache