thrift/compiler/generate/t_hack_generator.cc (5,896 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 <stdlib.h>
#include <thrift/compiler/ast/t_sink.h>
#include <thrift/compiler/ast/t_stream.h>
#include <fstream>
#include <iostream>
#include <queue>
#include <set>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include <boost/filesystem.hpp>
#include <thrift/compiler/ast/t_const_value.h>
#include <thrift/compiler/ast/t_field.h>
#include <thrift/compiler/ast/t_union.h>
#include <thrift/compiler/generate/common.h>
#include <thrift/compiler/generate/t_oop_generator.h>
namespace apache {
namespace thrift {
namespace compiler {
namespace {
class t_name_generator {
public:
std::string operator()() { return operator()("tmp_"); }
std::string operator()(const char* prefix) {
return prefix + std::to_string(counter_++);
}
void decrement_counter() {
if (counter_ == 0) {
throw std::runtime_error(
"Name generator error, counter value is already 0");
}
counter_--;
}
private:
int counter_ = 0;
};
} // namespace
/**
* Hack code generator.
*
*/
class t_hack_generator : public t_oop_generator {
public:
t_hack_generator(
t_program* program,
t_generation_context context,
const std::map<std::string, std::string>& parsed_options,
const std::string& /*option_string*/)
: t_oop_generator(program, std::move(context)) {
json_ = option_is_specified(parsed_options, "json");
phps_ = option_is_specified(parsed_options, "server");
strict_types_ = option_is_specified(parsed_options, "stricttypes");
arraysets_ = option_is_specified(parsed_options, "arraysets");
no_nullables_ = option_is_specified(parsed_options, "nonullables");
from_map_construct_ =
option_is_specified(parsed_options, "frommap_construct");
struct_trait_ = option_is_specified(parsed_options, "structtrait");
shapes_ = option_is_specified(parsed_options, "shapes");
shape_arraykeys_ = option_is_specified(parsed_options, "shape_arraykeys");
shapes_allow_unknown_fields_ =
option_is_specified(parsed_options, "shapes_allow_unknown_fields");
array_migration_ = option_is_specified(parsed_options, "array_migration");
arrays_ = option_is_specified(parsed_options, "arrays");
no_use_hack_collections_ =
option_is_specified(parsed_options, "no_use_hack_collections");
nullable_everything_ =
option_is_specified(parsed_options, "nullable_everything");
const_collections_ =
option_is_specified(parsed_options, "const_collections");
enum_extratype_ = option_is_specified(parsed_options, "enum_extratype");
enum_transparenttype_ =
option_is_specified(parsed_options, "enum_transparenttype");
soft_attribute_ = option_is_specified(parsed_options, "soft_attribute");
protected_unions_ = option_is_specified(parsed_options, "protected_unions");
mangled_services_ = option_is_set(parsed_options, "mangledsvcs", false);
typedef_ = option_is_specified(parsed_options, "typedef");
has_hack_namespace = !hack_namespace(program).empty();
// no_use_hack_collections_ is only used to migrate away from php gen
if (no_use_hack_collections_ && strict_types_) {
throw std::runtime_error(
"Don't use no_use_hack_collections with strict_types");
} else if (no_use_hack_collections_ && !arraysets_) {
throw std::runtime_error(
"Don't use no_use_hack_collections without arraysets");
} else if (no_use_hack_collections_ && arrays_) {
throw std::runtime_error(
"Don't use no_use_hack_collections with arrays. Just use arrays");
} else if (mangled_services_ && has_hack_namespace) {
throw std::runtime_error("Don't use mangledsvcs with hack namespaces");
}
out_dir_base_ = "gen-hack";
array_keyword_ = array_migration_ ? "darray" : "dict";
}
/**
* Init and close methods
*/
void init_generator() override;
void close_generator() override;
/**
* Program-level generation functions
*/
void generate_typedef(const t_typedef* ttypedef) override;
void generate_enum(const t_enum* tenum) override;
void generate_const(const t_const* tconst) override;
void generate_struct(const t_struct* tstruct) override;
void generate_xception(const t_struct* txception) override;
void generate_service(const t_service* tservice) override;
std::string render_const_value(
const t_type* type,
const t_const_value* value,
bool immutable_collections = false);
std::string render_const_value_helper(
const t_type* type,
const t_const_value* value,
std::ostream& temp_var_initializations_out,
t_name_generator& namer,
bool immutable_collections = false);
std::string render_default_value(const t_type* type);
/**
* Metadata Types functions
*/
/**
* Keep synced with : thrift/lib/thrift/metadata.thrift
*/
enum ThriftPrimitiveType {
THRIFT_BOOL_TYPE = 1,
THRIFT_BYTE_TYPE = 2,
THRIFT_I16_TYPE = 3,
THRIFT_I32_TYPE = 4,
THRIFT_I64_TYPE = 5,
THRIFT_FLOAT_TYPE = 6,
THRIFT_DOUBLE_TYPE = 7,
THRIFT_BINARY_TYPE = 8,
THRIFT_STRING_TYPE = 9,
THRIFT_VOID_TYPE = 10,
};
ThriftPrimitiveType base_to_t_primitive(const t_base_type* base_type);
std::unique_ptr<t_const_value> type_to_tmeta(const t_type* type);
std::unique_ptr<t_const_value> field_to_tmeta(const t_field* field);
std::unique_ptr<t_const_value> function_to_tmeta(const t_function* function);
std::unique_ptr<t_const_value> service_to_tmeta(const t_service* service);
std::unique_ptr<t_const_value> enum_to_tmeta(const t_enum* tenum);
std::unique_ptr<t_const_value> struct_to_tmeta(
const t_struct* tstruct, bool is_exception);
void append_to_t_enum(
t_enum* tenum, t_program* program, ThriftPrimitiveType value);
const t_type* tmeta_ThriftType_type();
const t_type* tmeta_ThriftField_type();
const t_type* tmeta_ThriftFunction_type();
const t_type* tmeta_ThriftService_type();
const t_type* tmeta_ThriftEnum_type();
const t_type* tmeta_ThriftStruct_type();
const t_type* tmeta_ThriftException_type();
const t_type* tmeta_ThriftMetadata_type();
/**
* Structs!
*/
enum class ThriftStructType {
STRUCT,
EXCEPTION,
ARGS,
RESULT,
};
enum class ThriftShapishStructType {
SYNC = 1,
ASYNC = 2,
VISITED = 3,
};
enum class ThriftAsyncStructCreationMethod {
FROM_CONSTRUCTOR_SHAPE = 1,
FROM_MAP = 2,
FROM_SHAPE = 3,
};
bool is_async_struct(const t_struct* tstruct);
// Only use this to determine if struct uses IThriftShapishAsyncStruct
bool is_async_shapish_struct(const t_struct* tstruct);
bool is_async_type(const t_type* type);
bool is_async_field(const t_field& field);
void generate_php_struct_definition(
std::ofstream& out,
const t_struct* tstruct,
ThriftStructType type = ThriftStructType::STRUCT,
const std::string& name = "");
void _generate_php_struct_definition(
std::ofstream& out,
const t_struct* tstruct,
ThriftStructType type,
const std::string& name);
void generate_php_function_result_helpers(
const t_function* tfunction,
const t_type* ttype,
const t_throws* ex,
const std::string& prefix,
const std::string& suffix,
bool is_void);
void generate_php_function_args_helpers(
const t_function* tfunction, const std::string& prefix);
void generate_php_function_helpers(
const t_service* tservice, const t_function* tfunction);
void generate_php_stream_function_helpers(
const t_function* tfunction, const std::string& prefix);
void generate_php_sink_function_helpers(
const t_function* tfunction, const std::string& prefix);
void generate_php_interaction_function_helpers(
const t_service* tservice,
const t_service* interaction,
const t_function* tfunction);
void generate_php_union_enum(
std::ofstream& out, const t_struct* tstruct, const std::string& name);
void generate_php_union_methods(
std::ofstream& out,
const t_struct* tstruct,
const std::string& struct_hack_name);
void generate_php_struct_fields(
std::ofstream& out,
const t_struct* tstruct,
const std::string& struct_hack_name,
ThriftStructType type = ThriftStructType::STRUCT);
void generate_php_struct_field_methods(
std::ofstream& out, const t_field* field, bool is_exception);
void generate_php_field_wrapper_methods(
std::ofstream& out,
const t_field& field,
bool is_union,
bool nullable,
const std::string& struct_class_name);
void generate_php_struct_methods(
std::ofstream& out,
const t_struct* tstruct,
ThriftStructType type,
const std::string& name,
bool is_async_struct,
bool is_async_shapish_struct);
void generate_php_struct_constructor(
std::ofstream& out,
const t_struct* tstruct,
ThriftStructType type,
const std::string& name);
void generate_php_struct_default_constructor(
std::ofstream& out,
const t_struct* tstruct,
ThriftStructType type,
const std::string& name);
void generate_php_struct_withDefaultValues_method(std::ofstream& out);
void generate_php_struct_constructor_field_assignment(
std::ofstream& out,
const t_field& field,
const t_struct* tstruct,
ThriftStructType type,
const std::string& name = "",
bool is_default_assignment = false);
void generate_php_struct_metadata_method(
std::ofstream& out, const t_struct* tstruct);
void generate_php_struct_structured_annotations_method(
std::ofstream& out, const t_struct* tstruct);
void generate_php_struct_shape_spec(
std::ofstream& out,
const t_struct* tstruct,
bool is_constructor_shape = false);
void generate_php_struct_shape_collection_value_lambda(
std::ostream& out, t_name_generator& namer, const t_type* t);
void generate_hack_array_from_shape_lambda(
std::ostream& out, t_name_generator& namer, const t_type* t);
void generate_shape_from_hack_array_lambda(
std::ostream& out, t_name_generator& namer, const t_type* t);
void generate_php_struct_from_shape(
std::ofstream& out, const t_struct* tstruct);
void generate_php_struct_from_map(
std::ofstream& out, const t_struct* tstruct);
void generate_php_struct_async_struct_creation_method(
std::ofstream& out,
const t_struct* tstruct,
const std::string& struct_hack_name,
ThriftAsyncStructCreationMethod method_type);
void generate_php_struct_async_struct_creation_method_header(
std::ofstream& out, ThriftAsyncStructCreationMethod method_type);
void generate_php_struct_async_struct_creation_method_footer(
std::ofstream& out);
void generate_php_struct_async_struct_creation_method_field_assignment(
std::ofstream& out,
const t_struct* tstruct,
const t_field& tfield,
const std::string& field_ref,
const std::string& struct_hack_name);
bool type_has_nested_struct(const t_type* t);
bool field_is_nullable(
const t_struct* tstruct, const t_field* field, std::string dval);
void generate_php_struct_shape_methods(
std::ofstream& out, const t_struct* tstruct);
void generate_php_struct_stringifyMapKeys_method(std::ofstream& out);
void generate_php_struct_async_shape_methods(
std::ofstream& out,
const t_struct* tstruct,
const std::string& struct_hack_name);
bool generate_php_struct_async_fromShape_method_helper(
std::ostream& out,
const t_type* ttype,
t_name_generator& namer,
std::string val);
bool generate_php_struct_async_toShape_method_helper(
std::ostream& out,
const t_type* ttype,
t_name_generator& namer,
std::string val);
void generate_adapter_type_checks(
std::ofstream& out, const t_struct* tstruct);
void generate_php_type_spec(std::ofstream& out, const t_type* t);
void generate_php_struct_spec(std::ofstream& out, const t_struct* tstruct);
void generate_php_struct_struct_trait(
std::ofstream& out, const t_struct* tstruct, const std::string& name);
void generate_php_structural_id(
std::ofstream& out, const t_struct* tstruct, bool asFunction);
/**
* Service-level generation functions
*/
void generate_service(const t_service* tservice, bool mangle);
void generate_service_helpers(const t_service* tservice, bool mangle);
void generate_service_interactions(const t_service* tservice, bool mangle);
void generate_service_interface(
const t_service* tservice, bool mangle, bool async, bool client);
void generate_service_client(const t_service* tservice, bool mangle);
void _generate_service_client(
std::ofstream& out, const t_service* tservice, bool mangle);
void _generate_recvImpl(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction);
void _generate_stream_decode_recvImpl(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction);
void _generate_sink_encode_sendImpl(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction);
void _generate_sink_final_response_decode_recvImpl(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction);
void _generate_sendImpl(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction);
void _generate_sendImpl_arg(
std::ofstream& out,
t_name_generator& namer,
const std::string& var,
const t_type* t);
void _generate_service_client_children(
std::ofstream& out, const t_service* tservice, bool mangle, bool async);
void _generate_service_client_child_fn(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction,
bool legacy_arrays = false);
void _generate_service_client_stream_child_fn(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction,
bool legacy_arrays = false);
void _generate_service_client_sink_child_fn(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction,
bool legacy_arrays = false);
void generate_service_processor(
const t_service* tservice, bool mangle, bool async);
void generate_process_function(
const t_service* tservice, const t_function* tfunction, bool async);
void generate_process_metadata_function(
const t_service* tservice, bool mangle, bool async);
void generate_processor_event_handler_functions(std::ofstream& out);
void generate_client_event_handler_functions(std::ofstream& out);
void generate_event_handler_functions(std::ofstream& out, std::string cl);
/**
* Read thrift object from JSON string, generated using the
* TSimpleJSONProtocol.
*/
void generate_json_enum(
std::ofstream& out,
t_name_generator& namer,
const t_enum* tenum,
const std::string& prefix_thrift,
const std::string& prefix_json);
void generate_json_struct(
std::ofstream& out,
t_name_generator& namer,
const t_struct* tstruct,
const std::string& prefix_thrift,
const std::string& prefix_json);
void generate_json_field(
std::ofstream& out,
t_name_generator& namer,
const t_field* tfield,
const std::string& prefix_thrift = "",
const std::string& suffix_thrift = "",
const std::string& prefix_json = "");
void generate_json_container(
std::ofstream& out,
t_name_generator& namer,
const t_type* ttype,
const std::string& prefix_thrift = "",
const std::string& prefix_json = "");
void generate_json_set_element(
std::ofstream& out,
t_name_generator& namer,
const t_set* tset,
const std::string& value,
const std::string& prefix_thrift);
void generate_json_list_element(
std::ofstream& out,
t_name_generator& namer,
const t_list* list,
const std::string& value,
const std::string& prefix_thrift);
void generate_json_map_element(
std::ofstream& out,
t_name_generator& namer,
const t_map* tmap,
const std::string& key,
const std::string& value,
const std::string& prefix_thrift);
void generate_json_reader(std::ofstream& out, const t_struct* tstruct);
void generate_instance_key(std::ofstream& out);
/**
* Helper rendering functions
*/
enum class PhpFunctionNameSuffix {
ARGS = 0,
RESULT = 1,
STREAM_RESPONSE = 2,
FIRST_RESPONSE = 3,
SINK_PAYLOAD = 4,
SINK_FINAL_RESPONSE = 5,
};
std::string declare_field(
const t_field* tfield,
bool init = false,
bool obj = false,
bool thrift = false);
std::string function_signature(
const t_function* tfunction,
std::string more_tail_parameters = "",
std::string typehint = "");
std::string argument_list(
const t_struct* tstruct,
std::string more_tail_parameters = "",
bool typehints = true,
bool force_nullable = false);
std::string generate_rpc_function_name(
const t_service* tservice, const t_function* tfunction) const;
std::string generate_function_helper_name(
const t_service* tservice,
const t_function* tfunction,
PhpFunctionNameSuffix suffix);
std::string type_to_cast(const t_type* ttype);
std::string type_to_enum(const t_type* ttype);
void generate_php_docstring(std::ofstream& out, const t_node* tdoc);
void generate_php_docstring(std::ofstream& out, const t_enum* tenum);
void generate_php_docstring(std::ofstream& out, const t_service* tservice);
void generate_php_docstring(std::ofstream& out, const t_const* tconst);
void generate_php_docstring(std::ofstream& out, const t_function* tfunction);
void generate_php_docstring(std::ofstream& out, const t_field* tfield);
void generate_php_docstring(
std::ofstream& out, const t_struct* tstruct, bool is_exception = false);
void generate_php_docstring_args(
std::ofstream& out, int start_pos, const t_struct* arg_list);
void generate_php_docstring_stream_exceptions(
std::ofstream& out, const t_throws* ex);
std::string render_string(std::string value);
std::string field_to_typehint(
const t_field& tfield,
const std::string& struct_class_name,
bool is_field_nullable = false,
bool is_type_nullable = false,
bool shape = false,
bool immutable_collections = false,
bool ignore_adapter = false);
std::string get_stream_function_return_typehint(
const t_stream_response* tstream);
std::string get_sink_function_return_typehint(const t_sink* tsink);
std::string type_to_typehint(
const t_type* ttype,
bool nullable = false,
bool shape = false,
bool immutable_collections = false,
bool ignore_adapter = false);
std::string type_to_param_typehint(
const t_type* ttype, bool nullable = false);
bool is_type_arraykey(const t_type* type);
std::string union_enum_name(
const std::string& name, const t_program* program, bool decl = false) {
// <StructName>Type
return hack_name(name, program, decl) + "Enum";
}
std::string union_enum_name(const t_struct* tstruct, bool decl = false) {
return union_enum_name(tstruct->name(), tstruct->program(), decl);
}
std::string union_field_to_enum(
const t_struct* tstruct, const t_field* tfield, const std::string& name) {
// If null is passed, it refer to empty;
if (tfield) {
return union_enum_name(name, tstruct->program()) + "::" + tfield->name();
} else {
return union_enum_name(name, tstruct->program()) + "::" + UNION_EMPTY;
}
}
bool is_bitmask_enum(const t_enum* tenum) {
return tenum->has_annotation("bitmask");
}
const std::string* find_hack_adapter(const t_type* type) {
return t_typedef::get_first_annotation_or_null(type, {"hack.adapter"});
}
const std::string* find_hack_wrapper(const t_field& node) {
if (const auto annotation = node.find_structured_annotation_or_null(
"facebook.com/thrift/annotation/hack/FieldWrapper")) {
for (const auto& item : annotation->value()->get_map()) {
if (item.first->get_string() == "name") {
return &item.second->get_string();
}
}
}
return nullptr;
}
std::string hack_namespace(const t_program* p) {
std::string ns;
ns = p->get_namespace("hack");
std::replace(ns.begin(), ns.end(), '.', '\\');
return ns;
}
std::string php_namespace(const t_program* p) {
std::string ns = hack_namespace(p);
p->get_namespace("hack");
if (!ns.empty()) {
return ns;
}
ns = p->get_namespace("php");
if (!ns.empty()) {
ns.push_back('_');
return ns;
}
return "";
}
std::string php_namespace(const t_service* s) {
return php_namespace(s->program());
}
std::string hack_name(
std::string name, const t_program* prog, bool decl = false) {
std::string ns;
ns = hack_namespace(prog);
if (!ns.empty()) {
if (decl) {
return name;
}
return "\\" + ns + "\\" + name;
}
ns = prog->get_namespace("php");
return (!decl && has_hack_namespace ? "\\" : "") +
(!ns.empty() ? ns + "_" : "") + name;
}
std::string hack_name(const t_type* t, bool decl = false) {
return hack_name(t->name(), t->program(), decl);
}
std::string hack_name(const t_service* s, bool decl = false) {
return hack_name(s->name(), s->program(), decl);
}
std::string php_path(const t_program* p) {
std::string ns = p->get_namespace("php_path");
if (ns.empty()) {
return p->name();
}
// Transform the java-style namespace into a path.
for (char& c : ns) {
if (c == '.') {
c = '/';
}
}
return ns + '/' + p->name();
}
std::string php_path(const t_service* s) { return php_path(s->program()); }
const char* UNION_EMPTY = "_EMPTY_";
std::string generate_array_typehint(
const std::string& key_type, const std::string& value_type);
bool is_base_exception_property(const t_field*);
std::string render_service_metadata_response(
const t_service* service, const bool mangle);
std::string render_structured_annotations(
const std::vector<const t_const*>& annotations,
std::ostream& temp_var_initializations_out,
t_name_generator& namer);
private:
/**
* Generate the namespace mangled string, if necessary
*/
std::string php_servicename_mangle(
bool mangle,
const t_service* svc,
const std::string& name,
bool extends = false) {
if (extends && !hack_namespace(svc->program()).empty()) {
return hack_name(name, svc->program());
}
return (extends && has_hack_namespace ? "\\" : "") +
(mangle ? php_namespace(svc) : "") + name;
}
std::string php_servicename_mangle(
bool mangle, const t_service* svc, bool extends = false) {
return php_servicename_mangle(mangle, svc, svc->name(), extends);
}
/**
* Return the correct function to be used on a Hack Collection, only when
* generating shape structures.
* - If array_migration_ is set, we'll want to use varray / darray
* - If we're operating on a list, we'll want to use varray / vec over
* darray / dict
*/
std::string generate_to_array_method(
const t_type* t, const std::string& array) {
if (!t->is_container()) {
throw std::logic_error("not a container");
}
if (array_migration_ && !t->is_list()) {
return "ThriftUtil::toDArray(" + array + ", static::class)";
} else {
return t->is_list() ? "vec(" + array + ")" : "dict(" + array + ")";
}
}
bool is_hack_const_type(const t_type* type);
std::vector<const t_function*> get_supported_server_functions(
const t_service* tservice) {
std::vector<const t_function*> funcs;
for (auto func : tservice->get_functions()) {
if (!is_client_only_function(func) &&
!func->get_returntype()->is_service()) {
funcs.push_back(func);
}
}
return funcs;
}
std::vector<const t_function*> get_supported_client_functions(
const t_service* tservice) {
auto funcs = get_supported_server_functions(tservice);
for (auto func : tservice->get_functions()) {
if (is_client_only_function(func)) {
funcs.push_back(func);
}
}
return funcs;
}
bool is_client_only_function(const t_function* func) {
return func->returns_stream() || func->returns_sink();
}
std::vector<const t_service*> get_interactions(
const t_service* tservice) const {
std::vector<const t_service*> interactions;
for (const auto& func : tservice->get_functions()) {
if (const auto* interaction =
dynamic_cast<const t_service*>(func->get_returntype())) {
interactions.push_back(interaction);
}
}
return interactions;
}
/**
* File streams
*/
std::ofstream f_types_;
std::ofstream f_consts_;
std::ofstream f_helpers_;
std::ofstream f_service_;
/**
* True iff we should generate a function parse json to thrift object.
*/
bool json_;
/**
* Generate stubs for a PHP server
*/
bool phps_;
/**
* * Whether to use collection classes everywhere vs KeyedContainer
*/
bool strict_types_;
/**
* Whether to generate protected members for thrift unions
*/
bool protected_unions_;
/**
* Whether to generate array sets or Set objects
*/
bool arraysets_;
/**
* memory of the values of the constants in array initialisation form
* for use with generate_const
*/
std::vector<std::string> constants_values_;
/**
* True iff mangled service classes should be emitted
*/
bool mangled_services_;
/**
* True if struct fields within structs should be instantiated rather than
* nullable typed
*/
bool no_nullables_;
/**
* True if struct should generate fromMap_DEPRECATED method with a semantic
* of a legacy map constructor.
*/
bool from_map_construct_;
/**
* True if we should add a "use StructNameTrait" to the generated class
*/
bool struct_trait_;
/**
* True if we should generate Shape types for the generated structs
*/
bool shapes_;
/**
* True if we should generate array<arraykey, TValue> instead of array<string,
* TValue>
*/
bool shape_arraykeys_;
/**
* True if we should allow implicit subtyping for shapes (i.e. '...')
*/
bool shapes_allow_unknown_fields_;
/**
* True to use darrays instead of dicts for internal constructs
*/
bool array_migration_;
/**
* True to use Hack arrays instead of collections
*/
bool arrays_;
/**
* True to never use hack collection objects. Only used for migrations
*/
bool no_use_hack_collections_;
/**
* True to force client methods to accept null arguments. Only used for
* migrations
*/
bool nullable_everything_;
/**
* True to force hack collection members be const collection objects
*/
bool const_collections_;
/**
* True to generate explicit types for Hack enums: 'type FooType = Foo'
*/
bool enum_extratype_;
/**
* True to use transparent typing for Hack enums: 'enum FooBar: int as int'.
*/
bool enum_transparenttype_;
/**
* True to generate soft typehints as __Soft instead of @
*/
bool soft_attribute_;
/**
* True to generate type aliases for typedefs defined
*/
bool typedef_;
std::string array_keyword_;
bool has_hack_namespace;
std::map<std::string, ThriftShapishStructType> struct_async_type_;
};
void t_hack_generator::generate_json_enum(
std::ofstream& out,
t_name_generator& /* namer */,
const t_enum* tenum,
const std::string& prefix_thrift,
const std::string& prefix_json) {
indent(out) << prefix_thrift << " = " << hack_name(tenum) << "::coerce("
<< prefix_json << ");";
}
void t_hack_generator::generate_json_struct(
std::ofstream& out,
t_name_generator& namer,
const t_struct* tstruct,
const std::string& prefix_thrift,
const std::string& prefix_json) {
std::string enc = namer("$_tmp");
indent(out) << enc << " = "
<< "json_encode(" << prefix_json << ");\n";
std::string tmp = namer("$_tmp");
t_field felem(tstruct, tmp);
indent(out) << declare_field(&felem, true, true, true).substr(1) << "\n";
indent(out) << tmp << "->readFromJson(" << enc << ");\n";
indent(out) << prefix_thrift << " = " << tmp << ";\n";
}
void t_hack_generator::generate_json_field(
std::ofstream& out,
t_name_generator& namer,
const t_field* tfield,
const std::string& prefix_thrift,
const std::string& suffix_thrift,
const std::string& prefix_json) {
const t_type* type = tfield->get_type()->get_true_type();
if (type->is_void()) {
throw std::runtime_error(
"CANNOT READ JSON FIELD WITH void TYPE: " + prefix_thrift +
tfield->name());
}
std::string name = prefix_thrift + tfield->name() + suffix_thrift;
if (const auto* tstruct = dynamic_cast<const t_struct*>(type)) {
generate_json_struct(out, namer, tstruct, name, prefix_json);
} else if (const auto* tconatiner = dynamic_cast<const t_container*>(type)) {
generate_json_container(out, namer, tconatiner, name, prefix_json);
} else if (const auto* tenum = dynamic_cast<const t_enum*>(type)) {
generate_json_enum(out, namer, tenum, name, prefix_json);
} else if (const auto* tbase_type = dynamic_cast<const t_base_type*>(type)) {
std::string typeConversionString = "";
std::string number_limit = "";
switch (tbase_type->get_base()) {
case t_base_type::TYPE_VOID:
case t_base_type::TYPE_STRING:
case t_base_type::TYPE_BINARY:
case t_base_type::TYPE_BOOL:
case t_base_type::TYPE_I64:
case t_base_type::TYPE_DOUBLE:
case t_base_type::TYPE_FLOAT:
break;
case t_base_type::TYPE_BYTE:
number_limit = "0x7f";
typeConversionString = "(int)";
break;
case t_base_type::TYPE_I16:
number_limit = "0x7fff";
typeConversionString = "(int)";
break;
case t_base_type::TYPE_I32:
number_limit = "0x7fffffff";
typeConversionString = "(int)";
break;
default:
throw std::runtime_error(
"compiler error: no PHP reader for base type " +
t_base_type::t_base_name(tbase_type->get_base()) + name);
}
if (number_limit.empty()) {
indent(out) << name << " = " << typeConversionString << prefix_json
<< ";\n";
} else {
std::string temp = namer("$_tmp");
indent(out) << temp << " = (int)" << prefix_json << ";\n";
indent(out) << "if (" << temp << " > " << number_limit << ") {\n";
indent_up();
indent(out) << "throw new \\TProtocolException(\"number exceeds "
<< "limit in field\");\n";
indent_down();
indent(out) << "} else {\n";
indent_up();
indent(out) << name << " = " << typeConversionString << temp << ";\n";
indent_down();
indent(out) << "}\n";
}
}
}
void t_hack_generator::generate_json_container(
std::ofstream& out,
t_name_generator& namer,
const t_type* ttype,
const std::string& prefix_thrift,
const std::string& prefix_json) {
std::string size = namer("$_size");
std::string key = namer("$_key");
std::string value = namer("$_value");
std::string json = namer("$_json");
std::string container = namer("$_container");
indent(out) << json << " = " << prefix_json << ";\n";
if (ttype->is_map()) {
if (arrays_ || no_use_hack_collections_) {
indent(out) << container << " = dict[];\n";
} else {
indent(out) << container << " = Map {};\n";
}
} else if (ttype->is_list()) {
if (arrays_ || no_use_hack_collections_) {
indent(out) << container << " = vec[];\n";
} else {
indent(out) << container << " = Vector {};\n";
}
} else if (ttype->is_set()) {
if (arrays_) {
indent(out) << container << " = keyset[];\n";
} else if (arraysets_) {
indent(out) << container << " = dict[];\n";
} else {
indent(out) << container << " = Set {};\n";
}
}
indent(out) << "foreach(/* HH_FIXME[4110] */ " << json << " as " << key
<< " => " << value << ") {\n";
indent_up();
if (const auto* tlist = dynamic_cast<const t_list*>(ttype)) {
generate_json_list_element(out, namer, tlist, value, container);
} else if (const auto* tset = dynamic_cast<const t_set*>(ttype)) {
generate_json_set_element(out, namer, tset, value, container);
} else if (const auto* tmap = dynamic_cast<const t_map*>(ttype)) {
generate_json_map_element(out, namer, tmap, key, value, container);
} else {
throw std::runtime_error("compiler error: no PHP reader for this type.");
}
indent_down();
indent(out) << "}\n";
indent(out) << prefix_thrift << " = " << container << ";\n";
}
void t_hack_generator::generate_json_list_element(
std::ofstream& out,
t_name_generator& namer,
const t_list* tlist,
const std::string& value,
const std::string& prefix_thrift) {
std::string elem = namer("$_elem");
t_field felem(tlist->get_elem_type(), elem);
indent(out) << declare_field(&felem, true, true, true).substr(1) << "\n";
generate_json_field(out, namer, &felem, "", "", value);
indent(out) << prefix_thrift << " []= " << elem << ";\n";
}
void t_hack_generator::generate_json_set_element(
std::ofstream& out,
t_name_generator& namer,
const t_set* tset,
const std::string& value,
const std::string& prefix_thrift) {
std::string elem = namer("$_elem");
t_field felem(tset->get_elem_type(), elem);
indent(out) << declare_field(&felem, true, true, true).substr(1) << "\n";
generate_json_field(out, namer, &felem, "", "", value);
if (arrays_) {
indent(out) << prefix_thrift << " []= " << elem << ";\n";
} else if (arraysets_) {
indent(out) << prefix_thrift << "[" << elem << "] = true;\n";
} else {
indent(out) << prefix_thrift << "->add(" << elem << ");\n";
}
}
void t_hack_generator::generate_json_map_element(
std::ofstream& out,
t_name_generator& namer,
const t_map* tmap,
const std::string& key,
const std::string& value,
const std::string& prefix_thrift) {
const t_type* keytype = tmap->get_key_type()->get_true_type();
std::string error_msg =
"compiler error: Thrift Hack compiler"
"does not support complex types as the key of a map.";
if (!keytype->is_enum() && !keytype->is_base_type()) {
throw error_msg;
}
if (const auto* tbase_type = dynamic_cast<const t_base_type*>(keytype)) {
switch (tbase_type->get_base()) {
case t_base_type::TYPE_VOID:
case t_base_type::TYPE_DOUBLE:
case t_base_type::TYPE_FLOAT:
throw error_msg;
default:
break;
}
}
std::string _value = namer("$_value");
t_field vfelem(tmap->get_val_type(), _value);
indent(out) << declare_field(&vfelem, true, true, true).substr(1) << "\n";
generate_json_field(out, namer, &vfelem, "", "", value);
indent(out) << prefix_thrift << "[" << key << "] = " << _value << ";\n";
}
void t_hack_generator::generate_json_reader(
std::ofstream& out, const t_struct* tstruct) {
if (!json_) {
return;
}
t_name_generator namer;
std::string name = tstruct->name();
indent(out) << "public function readFromJson(string $jsonText): void {\n";
indent_up();
if (tstruct->is_union()) {
indent(out) << "$this->_type = "
<< union_field_to_enum(tstruct, nullptr, tstruct->name())
<< ";\n";
}
indent(out) << "$parsed = json_decode($jsonText, true);\n\n";
indent(out)
<< "if ($parsed === null || !($parsed is KeyedContainer<_, _>)) {\n";
indent_up();
indent(out) << "throw new \\TProtocolException(\"Cannot parse the given json"
<< " string.\");\n";
indent_down();
indent(out) << "}\n\n";
for (const auto& tf : tstruct->fields()) {
indent(out) << "if (idx($parsed, '" << tf.name() << "') !== null) {\n";
indent_up();
generate_json_field(
out,
namer,
&tf,
"$this->",
"",
"/* HH_FIXME[4110] */ $parsed['" + tf.name() + "']");
if (tstruct->is_union()) {
indent(out) << "$this->_type = "
<< union_field_to_enum(tstruct, &tf, tstruct->name())
<< ";\n";
}
indent_down();
indent(out) << "}";
if (tf.get_req() == t_field::e_req::required) {
out << " else {\n";
indent_up();
indent(out) << "throw new \\TProtocolException(\"Required field "
<< tf.name() << " cannot be found.\");\n";
indent_down();
indent(out) << "}";
}
indent(out) << "\n";
}
indent_down();
indent(out) << "}\n\n";
}
void t_hack_generator::generate_instance_key(std::ofstream& out) {
indent(out) << "public function getInstanceKey()[write_props]: string {\n";
indent_up();
indent(out) << "return \\TCompactSerializer::serialize($this);\n";
indent_down();
indent(out) << "}\n\n";
}
/**
* Prepares for file generation by opening up the necessary file output
* streams.
*
* @param tprogram The program to generate
*/
void t_hack_generator::init_generator() {
// Make output directory
boost::filesystem::create_directory(get_out_dir());
// Make output file
std::string f_types_name = get_out_dir() + program_name_ + "_types.php";
f_types_.open(f_types_name.c_str());
record_genfile(f_types_name);
// Print header
f_types_ << "<?hh\n" << autogen_comment() << "\n";
std::string hack_ns = hack_namespace(program_);
if (!hack_ns.empty()) {
f_types_ << "namespace " << hack_ns << ";\n\n";
}
// Print header
if (!program_->consts().empty()) {
std::string f_consts_name =
get_out_dir() + program_name_ + "_constants.php";
f_consts_.open(f_consts_name.c_str());
record_genfile(f_consts_name);
f_consts_ << "<?hh\n" << autogen_comment();
constants_values_.clear();
std::string const_namespace = php_namespace(program_);
if (!hack_ns.empty()) {
f_consts_ << "namespace " << hack_ns << ";\n\n";
}
f_consts_ << "class "
<< (!hack_ns.empty() || const_namespace == ""
? program_name_ + "_"
: const_namespace)
<< "CONSTANTS implements \\IThriftConstants {\n";
}
}
/**
* Close up (or down) some filez.
*/
void t_hack_generator::close_generator() {
// Close types file
f_types_.close();
if (!program_->consts().empty()) {
// write out the values array
indent_up();
f_consts_ << "\n";
// write structured annotations
f_consts_ << indent()
<< "public static function getAllStructuredAnnotations()[]: "
"dict<string, dict<string, \\IThriftStruct>> {\n";
indent_up();
std::stringstream annotations_out;
std::stringstream annotations_temp_var_initializations_out;
t_name_generator namer;
annotations_out << indent() << "return dict[\n";
indent_up();
for (const auto& tconst : program_->consts()) {
if (tconst->structured_annotations().empty()) {
continue;
}
annotations_out << indent() << "'" << tconst->name() << "' => "
<< render_structured_annotations(
tconst->structured_annotations(),
annotations_temp_var_initializations_out,
namer)
<< ",\n";
}
f_consts_ << annotations_temp_var_initializations_out.str();
f_consts_ << annotations_out.str();
indent_down();
f_consts_ << indent() << "];\n";
indent_down();
f_consts_ << indent() << "}\n";
indent_down();
// close constants class
f_consts_ << "}\n\n";
f_consts_.close();
}
}
/**
* Generates a typedef.
*
* @param ttypedef The type definition
*/
void t_hack_generator::generate_typedef(const t_typedef* ttypedef) {
if (typedef_) {
f_types_ << "type " << ttypedef->get_name() << " = "
<< type_to_typehint(ttypedef->get_type()) << ";\n";
}
}
/**
* Generates code for an enumerated type. Since define is expensive to lookup
* in PHP, we use a global array for this.
*
* @param tenum The enumeration
*/
void t_hack_generator::generate_enum(const t_enum* tenum) {
std::string typehint;
generate_php_docstring(f_types_, tenum);
bool hack_enum = false;
if (is_bitmask_enum(tenum)) {
typehint = "int";
f_types_ << "final class " << hack_name(tenum, true)
<< " extends \\Flags {\n";
} else {
hack_enum = true;
typehint = hack_name(tenum, true);
if (std::string const* attributes =
tenum->find_annotation_or_null("hack.attributes")) {
f_types_ << "<<" << *attributes << ">>\n";
}
f_types_ << "enum " << hack_name(tenum, true) << ": int"
<< (enum_transparenttype_ ? " as int" : "") << " {\n";
}
indent_up();
for (const auto* constant : tenum->get_enum_values()) {
int32_t value = constant->get_value();
generate_php_docstring(f_types_, constant);
if (!hack_enum) {
indent(f_types_) << "const " << typehint << " ";
}
indent(f_types_) << constant->name() << " = " << value << ";\n";
}
indent_down();
f_types_ << "}\n";
if (hack_enum && enum_extratype_) {
f_types_ << "type " << typehint << "Type = " << typehint << ";\n";
}
f_types_ << "\n";
f_types_ << indent() << "class " << hack_name(tenum, true)
<< "_TEnumStaticMetadata implements \\IThriftEnumStaticMetadata {\n";
indent_up();
// Expose enum metadata
f_types_ << indent() << "public static function getEnumMetadata()[]: "
<< "\\tmeta_ThriftEnum {\n";
indent_up();
bool saved_arrays_ = arrays_;
arrays_ = true;
f_types_ << indent() << "return "
<< render_const_value(
tmeta_ThriftEnum_type(), enum_to_tmeta(tenum).get())
<< ";\n";
arrays_ = saved_arrays_;
indent_down();
f_types_ << indent() << "}\n\n";
// Structured annotations
f_types_ << indent()
<< "public static function getAllStructuredAnnotations()[]: "
"\\TEnumAnnotations {\n";
indent_up();
std::stringstream annotations_out;
std::stringstream annotations_temp_var_initializations_out;
t_name_generator namer;
annotations_out << indent() << "return shape(\n";
indent_up();
annotations_out << indent() << "'enum' => "
<< render_structured_annotations(
tenum->structured_annotations(),
annotations_temp_var_initializations_out,
namer)
<< ",\n";
annotations_out << indent() << "'constants' => dict[\n";
indent_up();
for (const auto& constant : tenum->get_enum_values()) {
if (constant->structured_annotations().empty()) {
continue;
}
annotations_out << indent() << "'" << constant->name() << "' => "
<< render_structured_annotations(
constant->structured_annotations(),
annotations_temp_var_initializations_out,
namer)
<< ",\n";
}
f_types_ << annotations_temp_var_initializations_out.str();
f_types_ << annotations_out.str();
indent_down();
f_types_ << indent() << "],\n";
indent_down();
f_types_ << indent() << ");\n";
indent_down();
f_types_ << indent() << "}\n";
indent_down();
f_types_ << indent() << "}\n\n";
}
/**
* Generate a constant value
*/
void t_hack_generator::generate_const(const t_const* tconst) {
const t_type* type = tconst->get_type();
std::string name = tconst->name();
t_const_value* value = tconst->get_value();
indent_up();
generate_php_docstring(f_consts_, tconst);
bool is_hack_const = is_hack_const_type(type);
f_consts_ << indent();
std::stringstream consts_out;
std::stringstream consts_temp_var_initializations_out;
t_name_generator namer;
// for base hack types, use const (guarantees optimization in hphp)
if (is_hack_const) {
f_consts_ << "const " << type_to_typehint(type) << " " << name << " = ";
// cannot use const for objects (incl arrays). use static
} else {
f_consts_ << "<<__Memoize>>\n"
<< indent() << "public static function " << name
<< "()[]: " << type_to_typehint(type, false, false, true)
<< "{\n";
indent_up();
consts_out << indent() << "return ";
}
consts_out
<< render_const_value_helper(
type, value, consts_temp_var_initializations_out, namer, true)
<< ";\n";
f_consts_ << consts_temp_var_initializations_out.str();
f_consts_ << consts_out.str();
if (!is_hack_const) {
indent_down();
f_consts_ << indent() << "}\n";
}
f_consts_ << "\n";
indent_down();
}
bool t_hack_generator::is_hack_const_type(const t_type* type) {
type = type->get_true_type();
if (type->is_base_type() || type->is_enum()) {
return true;
} else if (arrays_ && type->is_container()) {
if (const auto* tlist = dynamic_cast<const t_list*>(type)) {
return is_hack_const_type(tlist->get_elem_type());
} else if (const auto* tset = dynamic_cast<const t_set*>(type)) {
return is_hack_const_type(tset->get_elem_type());
} else if (const auto* tmap = dynamic_cast<const t_map*>(type)) {
return is_hack_const_type(tmap->get_key_type()) &&
is_hack_const_type(tmap->get_val_type());
}
}
return false;
}
std::string t_hack_generator::generate_array_typehint(
const std::string& key_type, const std::string& value_type) {
std::ostringstream stream;
stream << array_keyword_ << "<" << key_type << ", " << value_type << ">";
return stream.str();
}
std::string t_hack_generator::render_string(std::string value) {
std::ostringstream out;
size_t pos = 0;
while ((pos = value.find('"', pos)) != std::string::npos) {
value.insert(pos, 1, '\\');
pos += 2;
}
out << "\"" << value << "\"";
return out.str();
}
/**
* Prints the value of a constant with the given type. Note that type checking
* is NOT performed in this function as it is always run beforehand using the
* validate_types method in main.cc
*/
std::string t_hack_generator::render_const_value(
const t_type* type,
const t_const_value* value,
bool immutable_collections) {
std::ostringstream out;
std::ostringstream initialization_out;
t_name_generator namer;
auto const_val = render_const_value_helper(
type, value, initialization_out, namer, immutable_collections);
out << initialization_out.str();
out << const_val;
return out.str();
}
std::string t_hack_generator::render_const_value_helper(
const t_type* type,
const t_const_value* value,
std::ostream& temp_var_initializations_out,
t_name_generator& namer,
bool immutable_collections) {
std::ostringstream out;
type = type->get_true_type();
if (const auto* tbase_type = dynamic_cast<const t_base_type*>(type)) {
switch (tbase_type->get_base()) {
case t_base_type::TYPE_STRING:
case t_base_type::TYPE_BINARY:
out << render_string(value->get_string());
break;
case t_base_type::TYPE_BOOL:
out << (value->get_integer() > 0 ? "true" : "false");
break;
case t_base_type::TYPE_BYTE:
case t_base_type::TYPE_I16:
case t_base_type::TYPE_I32:
case t_base_type::TYPE_I64:
out << value->get_integer();
break;
case t_base_type::TYPE_DOUBLE:
case t_base_type::TYPE_FLOAT:
if (value->get_type() == t_const_value::CV_INTEGER) {
out << value->get_integer();
} else {
out << value->get_double();
}
if (out.str().find('.') == std::string::npos &&
out.str().find('e') == std::string::npos) {
out << ".0";
}
break;
default:
throw std::runtime_error(
"compiler error: no const of base type " +
t_base_type::t_base_name(tbase_type->get_base()));
}
} else if (const auto* tenum = dynamic_cast<const t_enum*>(type)) {
const t_enum_value* val = tenum->find_value(value->get_integer());
if (val != nullptr) {
out << hack_name(tenum) << "::" << val->name();
} else {
out << hack_name(tenum) << "::coerce(" << value->get_integer() << ")";
}
} else if (const auto* tstruct = dynamic_cast<const t_struct*>(type)) {
auto struct_name = hack_name(type);
if (is_async_struct(tstruct)) {
auto temp_val = namer(("$" + struct_name).c_str());
int preserved_indent_size = get_indent();
set_indent(2);
temp_var_initializations_out << indent() << temp_val << " = "
<< hack_name(type)
<< "::withDefaultValues();\n";
for (const auto& entry : value->get_map()) {
const auto* field =
tstruct->get_field_by_name(entry.first->get_string());
if (field == nullptr) {
throw std::runtime_error(
"type error: " + type->name() + " has no field " +
entry.first->get_string());
}
t_const_value* v = entry.second;
std::stringstream inner;
if (v) {
auto* field_wrapper = find_hack_wrapper(*field);
if (field_wrapper) {
inner << indent() << temp_val << "->get_" << field->name()
<< "()->setValue_DO_NOT_USE_THRIFT_INTERNAL(";
} else {
inner << indent() << temp_val << "->" << field->name() << " = ";
}
inner << render_const_value_helper(
field->get_type(), v, temp_var_initializations_out, namer);
if (field_wrapper) {
inner << ");\n";
} else {
inner << ";\n";
}
}
temp_var_initializations_out << inner.str() << "\n";
}
out << temp_val;
set_indent(preserved_indent_size);
} else {
out << hack_name(type) << "::fromShape(\n";
indent_up();
indent(out) << "shape(\n";
indent_up();
for (const auto& entry : value->get_map()) {
const auto* field =
tstruct->get_field_by_name(entry.first->get_string());
if (field == nullptr) {
throw std::runtime_error(
"type error: " + type->name() + " has no field " +
entry.first->get_string());
}
}
for (const auto& field : tstruct->fields()) {
t_const_value* k = nullptr;
t_const_value* v = nullptr;
for (const auto& entry : value->get_map()) {
if (field.name() == entry.first->get_string()) {
k = entry.first;
v = entry.second;
}
}
if (v != nullptr) {
indent(out) << render_const_value_helper(
&t_base_type::t_string(),
k,
temp_var_initializations_out,
namer)
<< " => "
<< render_const_value_helper(
field.get_type(),
v,
temp_var_initializations_out,
namer)
<< ",\n";
}
}
indent_down();
indent(out) << ")\n";
indent_down();
indent(out) << ")";
}
} else if (const auto* tmap = dynamic_cast<const t_map*>(type)) {
const t_type* ktype = tmap->get_key_type();
const t_type* vtype = tmap->get_val_type();
if (arrays_) {
out << "dict[\n";
} else if (no_use_hack_collections_) {
out << "darray[\n";
} else {
out << (immutable_collections ? "Imm" : "") << "Map {\n";
}
indent_up();
for (const auto& entry : value->get_map()) {
out << indent();
out << render_const_value_helper(
ktype,
entry.first,
temp_var_initializations_out,
namer,
immutable_collections);
out << " => ";
out << render_const_value_helper(
vtype,
entry.second,
temp_var_initializations_out,
namer,
immutable_collections);
out << ",\n";
}
indent_down();
if (arrays_ || no_use_hack_collections_) {
indent(out) << "]";
} else {
indent(out) << "}";
}
} else if (const auto* tlist = dynamic_cast<const t_list*>(type)) {
const t_type* etype = tlist->get_elem_type();
if (arrays_) {
out << "vec[\n";
} else if (no_use_hack_collections_) {
out << "varray[\n";
} else {
out << (immutable_collections ? "Imm" : "") << "Vector {\n";
}
indent_up();
for (const auto* val : value->get_list()) {
out << indent();
out << render_const_value_helper(
etype,
val,
temp_var_initializations_out,
namer,
immutable_collections);
out << ",\n";
}
indent_down();
if (arrays_ || no_use_hack_collections_) {
indent(out) << "]";
} else {
indent(out) << "}";
}
} else if (const auto* tset = dynamic_cast<const t_set*>(type)) {
const t_type* etype = tset->get_elem_type();
indent_up();
const auto& vals = value->get_list();
if (arrays_) {
out << "keyset[\n";
for (const auto* val : vals) {
out << indent();
out << render_const_value_helper(
etype,
val,
temp_var_initializations_out,
namer,
immutable_collections);
out << ",\n";
}
indent_down();
indent(out) << "]";
} else if (arraysets_) {
out << "dict[\n";
for (const auto* val : vals) {
out << indent();
out << render_const_value_helper(
etype,
val,
temp_var_initializations_out,
namer,
immutable_collections);
out << " => true";
out << ",\n";
}
indent_down();
indent(out) << "]";
} else {
out << (immutable_collections ? "Imm" : "") << "Set {\n";
for (const auto* val : vals) {
out << indent();
out << render_const_value_helper(
etype,
val,
temp_var_initializations_out,
namer,
immutable_collections);
out << ",\n";
}
indent_down();
indent(out) << "}";
}
}
return out.str();
}
std::string t_hack_generator::render_default_value(const t_type* type) {
std::string dval;
type = type->get_true_type();
if (const auto* tbase_type = dynamic_cast<const t_base_type*>(type)) {
t_base_type::t_base tbase = tbase_type->get_base();
switch (tbase) {
case t_base_type::TYPE_STRING:
case t_base_type::TYPE_BINARY:
dval = "''";
break;
case t_base_type::TYPE_BOOL:
dval = "false";
break;
case t_base_type::TYPE_BYTE:
case t_base_type::TYPE_I16:
case t_base_type::TYPE_I32:
case t_base_type::TYPE_I64:
dval = "0";
break;
case t_base_type::TYPE_DOUBLE:
case t_base_type::TYPE_FLOAT:
dval = "0.0";
break;
default:
throw std::runtime_error(
"compiler error: no const of base type " +
t_base_type::t_base_name(tbase));
}
} else if (type->is_enum()) {
dval = "null";
} else if (const auto* tstruct = dynamic_cast<const t_struct*>(type)) {
if (no_nullables_) {
dval = hack_name(tstruct) + "::withDefaultValues()";
} else {
dval = "null";
}
} else if (type->is_map()) {
if (arrays_ || no_use_hack_collections_) {
dval = "dict[]";
} else {
dval = "Map {}";
}
} else if (type->is_list()) {
if (arrays_ || no_use_hack_collections_) {
dval = "vec[]";
} else {
dval = "Vector {}";
}
} else if (type->is_set()) {
if (arrays_) {
dval = "keyset[]";
} else if (arraysets_) {
dval = "dict[]";
} else {
dval = "Set {}";
}
}
return dval;
}
t_hack_generator::ThriftPrimitiveType t_hack_generator::base_to_t_primitive(
const t_base_type* tbase) {
switch (tbase->get_base()) {
case t_base_type::TYPE_BOOL:
return ThriftPrimitiveType::THRIFT_BOOL_TYPE;
case t_base_type::TYPE_BYTE:
return ThriftPrimitiveType::THRIFT_BYTE_TYPE;
case t_base_type::TYPE_I16:
return ThriftPrimitiveType::THRIFT_I16_TYPE;
case t_base_type::TYPE_I32:
return ThriftPrimitiveType::THRIFT_I32_TYPE;
case t_base_type::TYPE_I64:
return ThriftPrimitiveType::THRIFT_I64_TYPE;
case t_base_type::TYPE_FLOAT:
return ThriftPrimitiveType::THRIFT_FLOAT_TYPE;
case t_base_type::TYPE_DOUBLE:
return ThriftPrimitiveType::THRIFT_DOUBLE_TYPE;
case t_base_type::TYPE_BINARY:
return ThriftPrimitiveType::THRIFT_BINARY_TYPE;
case t_base_type::TYPE_STRING:
return ThriftPrimitiveType::THRIFT_STRING_TYPE;
case t_base_type::TYPE_VOID:
return ThriftPrimitiveType::THRIFT_VOID_TYPE;
default:
throw std::invalid_argument(
"compiler error: no ThriftPrimitiveType mapped to base type " +
t_base_type::t_base_name(tbase->get_base()));
}
}
std::unique_ptr<t_const_value> t_hack_generator::type_to_tmeta(
const t_type* type) {
auto tmeta_ThriftType = std::make_unique<t_const_value>();
if (const auto* tbase_type = dynamic_cast<const t_base_type*>(type)) {
tmeta_ThriftType->add_map(
std::make_unique<t_const_value>("t_primitive"),
std::make_unique<t_const_value>(base_to_t_primitive(tbase_type)));
} else if (const auto* tlist = dynamic_cast<const t_list*>(type)) {
auto tlist_tmeta = std::make_unique<t_const_value>();
tlist_tmeta->add_map(
std::make_unique<t_const_value>("valueType"),
type_to_tmeta(tlist->get_elem_type()));
tmeta_ThriftType->add_map(
std::make_unique<t_const_value>("t_list"), std::move(tlist_tmeta));
} else if (const auto* tset = dynamic_cast<const t_set*>(type)) {
auto tset_tmeta = std::make_unique<t_const_value>();
tset_tmeta->add_map(
std::make_unique<t_const_value>("valueType"),
type_to_tmeta(tset->get_elem_type()));
tmeta_ThriftType->add_map(
std::make_unique<t_const_value>("t_set"), std::move(tset_tmeta));
} else if (const auto* tmap = dynamic_cast<const t_map*>(type)) {
auto tmap_tmeta = std::make_unique<t_const_value>();
tmap_tmeta->add_map(
std::make_unique<t_const_value>("keyType"),
type_to_tmeta(tmap->get_key_type()));
tmap_tmeta->add_map(
std::make_unique<t_const_value>("valueType"),
type_to_tmeta(tmap->get_val_type()));
tmeta_ThriftType->add_map(
std::make_unique<t_const_value>("t_map"), std::move(tmap_tmeta));
} else if (type->is_enum()) {
auto tenum_tmeta = std::make_unique<t_const_value>();
tenum_tmeta->add_map(
std::make_unique<t_const_value>("name"),
std::make_unique<t_const_value>(type->get_scoped_name()));
tmeta_ThriftType->add_map(
std::make_unique<t_const_value>("t_enum"), std::move(tenum_tmeta));
} else if (type->is_struct() || type->is_exception()) {
auto tstruct_tmeta = std::make_unique<t_const_value>();
tstruct_tmeta->add_map(
std::make_unique<t_const_value>("name"),
std::make_unique<t_const_value>(type->get_scoped_name()));
tmeta_ThriftType->add_map(
std::make_unique<t_const_value>("t_struct"), std::move(tstruct_tmeta));
} else if (type->is_union()) {
auto tunion_tmeta = std::make_unique<t_const_value>();
tunion_tmeta->add_map(
std::make_unique<t_const_value>("name"),
std::make_unique<t_const_value>(type->get_scoped_name()));
tmeta_ThriftType->add_map(
std::make_unique<t_const_value>("t_union"), std::move(tunion_tmeta));
} else if (const auto* ttypedef = dynamic_cast<const t_typedef*>(type)) {
auto ttypedef_tmeta = std::make_unique<t_const_value>();
ttypedef_tmeta->add_map(
std::make_unique<t_const_value>("name"),
std::make_unique<t_const_value>(ttypedef->get_scoped_name()));
ttypedef_tmeta->add_map(
std::make_unique<t_const_value>("underlyingType"),
type_to_tmeta(ttypedef->get_type()));
tmeta_ThriftType->add_map(
std::make_unique<t_const_value>("t_typedef"),
std::move(ttypedef_tmeta));
} else if (const auto* tsink = dynamic_cast<const t_sink*>(type)) {
auto tsink_tmeta = std::make_unique<t_const_value>();
tsink_tmeta->add_map(
std::make_unique<t_const_value>("elemType"),
type_to_tmeta(tsink->get_sink_type()));
tsink_tmeta->add_map(
std::make_unique<t_const_value>("finalResponseType"),
type_to_tmeta(tsink->get_final_response_type()));
if (tsink->sink_has_first_response()) {
tsink_tmeta->add_map(
std::make_unique<t_const_value>("initialResponseType"),
type_to_tmeta(tsink->get_first_response_type()));
} else {
auto first_response_type = std::make_unique<t_const_value>();
first_response_type->add_map(
std::make_unique<t_const_value>("t_primitive"),
std::make_unique<t_const_value>(
ThriftPrimitiveType::THRIFT_VOID_TYPE));
tsink_tmeta->add_map(
std::make_unique<t_const_value>("initialResponseType"),
std::move(first_response_type));
}
tmeta_ThriftType->add_map(
std::make_unique<t_const_value>("t_sink"), std::move(tsink_tmeta));
} else if (
const auto* tstream = dynamic_cast<const t_stream_response*>(type)) {
auto tstream_tmeta = std::make_unique<t_const_value>();
tstream_tmeta->add_map(
std::make_unique<t_const_value>("elemType"),
type_to_tmeta(tstream->get_elem_type()));
if (tstream->has_first_response()) {
tstream_tmeta->add_map(
std::make_unique<t_const_value>("initialResponseType"),
type_to_tmeta(tstream->get_first_response_type()));
} else {
auto first_response_type = std::make_unique<t_const_value>();
first_response_type->add_map(
std::make_unique<t_const_value>("t_primitive"),
std::make_unique<t_const_value>(
ThriftPrimitiveType::THRIFT_VOID_TYPE));
tstream_tmeta->add_map(
std::make_unique<t_const_value>("initialResponseType"),
std::move(first_response_type));
}
tmeta_ThriftType->add_map(
std::make_unique<t_const_value>("t_stream"), std::move(tstream_tmeta));
} else {
// Unsupported type
}
return tmeta_ThriftType;
}
std::unique_ptr<t_const_value> t_hack_generator::field_to_tmeta(
const t_field* field) {
auto tmeta_ThriftField = std::make_unique<t_const_value>();
tmeta_ThriftField->add_map(
std::make_unique<t_const_value>("id"),
std::make_unique<t_const_value>(field->id()));
tmeta_ThriftField->add_map(
std::make_unique<t_const_value>("type"),
type_to_tmeta(field->get_type()));
tmeta_ThriftField->add_map(
std::make_unique<t_const_value>("name"),
std::make_unique<t_const_value>(field->name()));
if (field->get_req() == t_field::e_req::optional) {
auto is_optional = std::make_unique<t_const_value>();
is_optional->set_bool(true);
tmeta_ThriftField->add_map(
std::make_unique<t_const_value>("is_optional"), std::move(is_optional));
}
return tmeta_ThriftField;
}
std::unique_ptr<t_const_value> t_hack_generator::function_to_tmeta(
const t_function* function) {
auto tmeta_ThriftFunction = std::make_unique<t_const_value>();
tmeta_ThriftFunction->add_map(
std::make_unique<t_const_value>("name"),
std::make_unique<t_const_value>(function->name()));
tmeta_ThriftFunction->add_map(
std::make_unique<t_const_value>("return_type"),
type_to_tmeta(function->get_returntype()));
if (function->get_paramlist()->has_fields()) {
auto arguments = std::make_unique<t_const_value>();
arguments->set_list();
for (const auto& field : function->get_paramlist()->fields()) {
arguments->add_list(field_to_tmeta(&field));
}
tmeta_ThriftFunction->add_map(
std::make_unique<t_const_value>("arguments"), std::move(arguments));
}
if (!t_throws::is_null_or_empty(function->exceptions())) {
auto exceptions = std::make_unique<t_const_value>();
exceptions->set_list();
for (auto&& field : function->exceptions()->fields()) {
exceptions->add_list(field_to_tmeta(&field));
}
tmeta_ThriftFunction->add_map(
std::make_unique<t_const_value>("exceptions"), std::move(exceptions));
}
if (function->qualifier() == t_function_qualifier::one_way) {
auto is_oneway = std::make_unique<t_const_value>();
is_oneway->set_bool(true);
tmeta_ThriftFunction->add_map(
std::make_unique<t_const_value>("is_oneway"), std::move(is_oneway));
}
return tmeta_ThriftFunction;
}
std::unique_ptr<t_const_value> t_hack_generator::service_to_tmeta(
const t_service* service) {
auto tmeta_ThriftService = std::make_unique<t_const_value>();
tmeta_ThriftService->add_map(
std::make_unique<t_const_value>("name"),
std::make_unique<t_const_value>(service->get_scoped_name()));
auto functions = get_supported_client_functions(service);
if (!functions.empty()) {
auto tmeta_functions = std::make_unique<t_const_value>();
tmeta_functions->set_list();
for (const auto& function : functions) {
tmeta_functions->add_list(function_to_tmeta(function));
}
tmeta_ThriftService->add_map(
std::make_unique<t_const_value>("functions"),
std::move(tmeta_functions));
}
const t_service* parent = service->get_extends();
if (parent) {
tmeta_ThriftService->add_map(
std::make_unique<t_const_value>("parent"),
std::make_unique<t_const_value>(parent->get_scoped_name()));
}
return tmeta_ThriftService;
}
std::unique_ptr<t_const_value> t_hack_generator::enum_to_tmeta(
const t_enum* tenum) {
auto tmeta_ThriftEnum = std::make_unique<t_const_value>();
tmeta_ThriftEnum->add_map(
std::make_unique<t_const_value>("name"),
std::make_unique<t_const_value>(tenum->get_scoped_name()));
auto enum_values = tenum->get_enum_values();
if (!enum_values.empty()) {
auto tmeta_elements = std::make_unique<t_const_value>();
tmeta_elements->set_map();
for (const auto& constant : tenum->get_enum_values()) {
auto enum_val = std::make_unique<t_const_value>();
auto enum_name = std::make_unique<t_const_value>();
enum_val->set_integer(constant->get_value());
enum_name->set_string(constant->name());
tmeta_elements->add_map(std::move(enum_val), std::move(enum_name));
}
tmeta_ThriftEnum->add_map(
std::make_unique<t_const_value>("elements"), std::move(tmeta_elements));
}
return tmeta_ThriftEnum;
}
std::unique_ptr<t_const_value> t_hack_generator::struct_to_tmeta(
const t_struct* tstruct, bool is_exception) {
auto tmeta = std::make_unique<t_const_value>();
tmeta->add_map(
std::make_unique<t_const_value>("name"),
std::make_unique<t_const_value>(tstruct->get_scoped_name()));
auto fields = tstruct->get_members();
if (!fields.empty()) {
auto tmeta_fields = std::make_unique<t_const_value>();
tmeta_fields->set_list();
for (const auto& field : fields) {
tmeta_fields->add_list(field_to_tmeta(field));
}
tmeta->add_map(
std::make_unique<t_const_value>("fields"), std::move(tmeta_fields));
}
if (!is_exception) {
auto is_union = std::make_unique<t_const_value>();
is_union->set_bool(tstruct->is_union());
tmeta->add_map(
std::make_unique<t_const_value>("is_union"), std::move(is_union));
}
return tmeta;
}
void t_hack_generator::append_to_t_enum(
t_enum* tenum, t_program* program, ThriftPrimitiveType value) {
std::unique_ptr<t_enum_value> enum_value;
switch (value) {
case ThriftPrimitiveType::THRIFT_BOOL_TYPE:
enum_value = std::make_unique<t_enum_value>("THRIFT_BOOL_TYPE");
break;
case ThriftPrimitiveType::THRIFT_BYTE_TYPE:
enum_value = std::make_unique<t_enum_value>("THRIFT_BYTE_TYPE");
break;
case ThriftPrimitiveType::THRIFT_I16_TYPE:
enum_value = std::make_unique<t_enum_value>("THRIFT_I16_TYPE");
break;
case ThriftPrimitiveType::THRIFT_I32_TYPE:
enum_value = std::make_unique<t_enum_value>("THRIFT_I32_TYPE");
break;
case ThriftPrimitiveType::THRIFT_I64_TYPE:
enum_value = std::make_unique<t_enum_value>("THRIFT_I64_TYPE");
break;
case ThriftPrimitiveType::THRIFT_FLOAT_TYPE:
enum_value = std::make_unique<t_enum_value>("THRIFT_FLOAT_TYPE");
break;
case ThriftPrimitiveType::THRIFT_DOUBLE_TYPE:
enum_value = std::make_unique<t_enum_value>("THRIFT_DOUBLE_TYPE");
break;
case ThriftPrimitiveType::THRIFT_BINARY_TYPE:
enum_value = std::make_unique<t_enum_value>("THRIFT_BINARY_TYPE");
break;
case ThriftPrimitiveType::THRIFT_STRING_TYPE:
enum_value = std::make_unique<t_enum_value>("THRIFT_STRING_TYPE");
break;
case ThriftPrimitiveType::THRIFT_VOID_TYPE:
enum_value = std::make_unique<t_enum_value>("THRIFT_VOID_TYPE");
break;
}
enum_value->set_value(value);
auto const_value = std::make_unique<t_const>(
program,
tenum,
enum_value->name(),
std::make_unique<t_const_value>(value));
tenum->append(std::move(enum_value), std::move(const_value));
}
const t_type* t_hack_generator::tmeta_ThriftType_type() {
static t_program empty_program("");
static t_union type(&empty_program, "tmeta_ThriftType");
static t_enum primitive_type(&empty_program, "");
static t_struct list_type(&empty_program, "tmeta_ThriftListType");
static t_struct set_type(&empty_program, "tmeta_ThriftSetType");
static t_struct map_type(&empty_program, "tmeta_ThriftMapType");
static t_struct enum_type(&empty_program, "tmeta_ThriftEnumType");
static t_struct struct_type(&empty_program, "tmeta_ThriftStructType");
static t_struct union_type(&empty_program, "tmeta_ThriftUnionType");
static t_struct typedef_type(&empty_program, "tmeta_ThriftTypedefType");
static t_struct stream_type(&empty_program, "tmeta_ThriftStreamType");
static t_struct sink_type(&empty_program, "tmeta_ThriftSinkType");
if (type.has_fields()) {
return &type;
}
primitive_type.set_name("tmeta_ThriftPrimitiveType");
append_to_t_enum(
&primitive_type, &empty_program, ThriftPrimitiveType::THRIFT_BOOL_TYPE);
append_to_t_enum(
&primitive_type, &empty_program, ThriftPrimitiveType::THRIFT_BYTE_TYPE);
append_to_t_enum(
&primitive_type, &empty_program, ThriftPrimitiveType::THRIFT_I16_TYPE);
append_to_t_enum(
&primitive_type, &empty_program, ThriftPrimitiveType::THRIFT_I32_TYPE);
append_to_t_enum(
&primitive_type, &empty_program, ThriftPrimitiveType::THRIFT_I64_TYPE);
append_to_t_enum(
&primitive_type, &empty_program, ThriftPrimitiveType::THRIFT_FLOAT_TYPE);
append_to_t_enum(
&primitive_type, &empty_program, ThriftPrimitiveType::THRIFT_DOUBLE_TYPE);
append_to_t_enum(
&primitive_type, &empty_program, ThriftPrimitiveType::THRIFT_BINARY_TYPE);
append_to_t_enum(
&primitive_type, &empty_program, ThriftPrimitiveType::THRIFT_STRING_TYPE);
append_to_t_enum(
&primitive_type, &empty_program, ThriftPrimitiveType::THRIFT_VOID_TYPE);
list_type.append(std::make_unique<t_field>(&type, "valueType"));
set_type.append(std::make_unique<t_field>(&type, "valueType"));
map_type.append(std::make_unique<t_field>(&type, "keyType"));
map_type.append(std::make_unique<t_field>(&type, "valueType"));
enum_type.append(std::make_unique<t_field>(&t_base_type::t_string(), "name"));
struct_type.append(
std::make_unique<t_field>(&t_base_type::t_string(), "name"));
union_type.append(
std::make_unique<t_field>(&t_base_type::t_string(), "name"));
typedef_type.append(
std::make_unique<t_field>(&t_base_type::t_string(), "name"));
typedef_type.append(std::make_unique<t_field>(&type, "underlyingType"));
stream_type.append(std::make_unique<t_field>(&type, "elemType"));
stream_type.append(std::make_unique<t_field>(&type, "initialResponseType"));
sink_type.append(std::make_unique<t_field>(&type, "elemType"));
sink_type.append(std::make_unique<t_field>(&type, "finalResponseType"));
sink_type.append(std::make_unique<t_field>(&type, "initialResponseType"));
type.append(std::make_unique<t_field>(&primitive_type, "t_primitive"));
type.append(std::make_unique<t_field>(&list_type, "t_list"));
type.append(std::make_unique<t_field>(&set_type, "t_set"));
type.append(std::make_unique<t_field>(&map_type, "t_map"));
type.append(std::make_unique<t_field>(&enum_type, "t_enum"));
type.append(std::make_unique<t_field>(&struct_type, "t_struct"));
type.append(std::make_unique<t_field>(&union_type, "t_union"));
type.append(std::make_unique<t_field>(&typedef_type, "t_typedef"));
type.append(std::make_unique<t_field>(&stream_type, "t_stream"));
type.append(std::make_unique<t_field>(&sink_type, "t_sink"));
return &type;
}
const t_type* t_hack_generator::tmeta_ThriftField_type() {
static t_program empty_program("");
static t_struct type(&empty_program, "tmeta_ThriftField");
if (type.has_fields()) {
return &type;
}
type.append(std::make_unique<t_field>(&t_base_type::t_i64(), "id"));
type.append(std::make_unique<t_field>(tmeta_ThriftType_type(), "type"));
type.append(std::make_unique<t_field>(&t_base_type::t_string(), "name"));
type.append(std::make_unique<t_field>(&t_base_type::t_bool(), "is_optional"));
return &type;
}
const t_type* t_hack_generator::tmeta_ThriftFunction_type() {
static t_program empty_program("");
static t_struct type(&empty_program, "tmeta_ThriftFunction");
static t_list tlist(tmeta_ThriftField_type());
if (type.has_fields()) {
return &type;
}
type.append(std::make_unique<t_field>(&t_base_type::t_string(), "name"));
type.append(
std::make_unique<t_field>(tmeta_ThriftType_type(), "return_type"));
type.append(std::make_unique<t_field>(&tlist, "arguments"));
type.append(std::make_unique<t_field>(&tlist, "exceptions"));
type.append(std::make_unique<t_field>(&t_base_type::t_bool(), "is_oneway"));
return &type;
}
const t_type* t_hack_generator::tmeta_ThriftService_type() {
static t_program empty_program("");
static t_struct type(&empty_program, "tmeta_ThriftService");
static t_list tlist(tmeta_ThriftFunction_type());
if (type.has_fields()) {
return &type;
}
type.append(std::make_unique<t_field>(&t_base_type::t_string(), "name"));
type.append(std::make_unique<t_field>(&tlist, "functions"));
type.append(std::make_unique<t_field>(&t_base_type::t_string(), "parent"));
return &type;
}
const t_type* t_hack_generator::tmeta_ThriftEnum_type() {
static t_program empty_program("");
static t_struct type(&empty_program, "tmeta_ThriftEnum");
static t_map tmap(&t_base_type::t_i32(), &t_base_type::t_string());
if (type.has_fields()) {
return &type;
}
type.append(std::make_unique<t_field>(&t_base_type::t_string(), "name"));
type.append(std::make_unique<t_field>(&tmap, "elements"));
return &type;
}
const t_type* t_hack_generator::tmeta_ThriftStruct_type() {
static t_program empty_program("");
static t_struct type(&empty_program, "tmeta_ThriftStruct");
static t_list tlist(tmeta_ThriftField_type());
if (type.has_fields()) {
return &type;
}
type.append(std::make_unique<t_field>(&t_base_type::t_string(), "name"));
type.append(std::make_unique<t_field>(&tlist, "fields"));
type.append(std::make_unique<t_field>(&t_base_type::t_bool(), "is_union"));
return &type;
}
const t_type* t_hack_generator::tmeta_ThriftException_type() {
static t_program empty_program("");
static t_struct type(&empty_program, "tmeta_ThriftException");
static t_list tlist(tmeta_ThriftField_type());
if (type.has_fields()) {
return &type;
}
type.append(std::make_unique<t_field>(&t_base_type::t_string(), "name"));
type.append(std::make_unique<t_field>(&tlist, "fields"));
return &type;
}
const t_type* t_hack_generator::tmeta_ThriftMetadata_type() {
static t_program empty_program("");
static t_struct type(&empty_program, "tmeta_ThriftMetadata");
static t_map tmap_enums(&t_base_type::t_string(), tmeta_ThriftEnum_type());
static t_map tmap_structs(
&t_base_type::t_string(), tmeta_ThriftStruct_type());
static t_map tmap_exceptions(
&t_base_type::t_string(), tmeta_ThriftException_type());
static t_map tmap_services(
&t_base_type::t_string(), tmeta_ThriftService_type());
if (type.has_fields()) {
return &type;
}
type.append(std::make_unique<t_field>(&tmap_enums, "enums"));
type.append(std::make_unique<t_field>(&tmap_structs, "structs"));
type.append(std::make_unique<t_field>(&tmap_exceptions, "exceptions"));
type.append(std::make_unique<t_field>(&tmap_services, "services"));
return &type;
}
/**
* Make a struct
*/
void t_hack_generator::generate_struct(const t_struct* tstruct) {
generate_php_struct_definition(f_types_, tstruct);
}
/**
* Generates a struct definition for a thrift exception. Basically the same
* as a struct but extends the Exception class.
*
* @param txception The struct definition
*/
void t_hack_generator::generate_xception(const t_struct* txception) {
generate_php_struct_definition(
f_types_, txception, ThriftStructType::EXCEPTION);
}
void t_hack_generator::generate_php_type_spec(
std::ofstream& out, const t_type* t) {
// Check the adapter before resolving typedefs.
if (const auto* adapter = find_hack_adapter(t)) {
indent(out) << "'adapter' => " << *adapter << "::class,\n";
}
t = t->get_true_type();
indent(out) << "'type' => " << type_to_enum(t) << ",\n";
if (t->is_base_type()) {
// Noop, type is all we need
} else if (t->is_enum()) {
indent(out) << "'enum' => " << hack_name(t) << "::class,\n";
} else if (t->is_struct() || t->is_xception()) {
indent(out) << "'class' => " << hack_name(t) << "::class,\n";
} else if (const auto* tmap = dynamic_cast<const t_map*>(t)) {
const t_type* ktype = tmap->get_key_type();
const t_type* vtype = tmap->get_val_type();
if (find_hack_adapter(ktype)) {
throw std::runtime_error(
"using hack.adapter annotation with map keys is not supported yet");
}
indent(out) << "'ktype' => " << type_to_enum(ktype) << ",\n";
indent(out) << "'vtype' => " << type_to_enum(vtype) << ",\n";
indent(out) << "'key' => shape(\n";
indent_up();
generate_php_type_spec(out, ktype);
indent_down();
indent(out) << "),\n";
indent(out) << "'val' => shape(\n";
indent_up();
generate_php_type_spec(out, vtype);
indent_down();
indent(out) << "),\n";
if (arrays_) {
indent(out) << "'format' => 'harray',\n";
} else if (no_use_hack_collections_) {
indent(out) << "'format' => 'array',\n";
} else {
indent(out) << "'format' => 'collection',\n";
}
} else if (const auto* tlist = dynamic_cast<const t_list*>(t)) {
const t_type* etype = tlist->get_elem_type();
indent(out) << "'etype' => " << type_to_enum(etype) << ",\n";
indent(out) << "'elem' => shape(\n";
indent_up();
generate_php_type_spec(out, etype);
indent_down();
indent(out) << "),\n";
if (arrays_) {
indent(out) << "'format' => 'harray',\n";
} else if (no_use_hack_collections_) {
indent(out) << "'format' => 'array',\n";
} else {
indent(out) << "'format' => 'collection',\n";
}
} else if (const auto* tset = dynamic_cast<const t_set*>(t)) {
const t_type* etype = tset->get_elem_type();
if (find_hack_adapter(etype)) {
throw std::runtime_error(
"using hack.adapter annotation with set keys is not supported yet");
}
indent(out) << "'etype' => " << type_to_enum(etype) << ",\n";
indent(out) << "'elem' => shape(\n";
indent_up();
generate_php_type_spec(out, etype);
indent_down();
indent(out) << "),\n";
if (arrays_) {
indent(out) << "'format' => 'harray',\n";
} else if (arraysets_) {
indent(out) << "'format' => 'array',\n";
} else {
indent(out) << "'format' => 'collection',\n";
}
} else {
throw std::runtime_error(
"compiler error: no type for php struct spec field");
}
}
/**
* Generates the struct specification structure, which fully qualifies enough
* type information to generalize serialization routines.
*/
void t_hack_generator::generate_php_struct_spec(
std::ofstream& out, const t_struct* tstruct) {
indent(out) << "const dict<int, this::TFieldSpec> SPEC = dict[\n";
indent_up();
const auto fields =
tstruct->find_structured_annotation_or_null(
"facebook.com/thrift/annotation/thrift/SerializeInFieldIdOrder")
? tstruct->fields_id_order()
: tstruct->fields().copy();
for (const auto* field_ptr : fields) {
const auto& field = *field_ptr;
const t_type& t = *field.type();
indent(out) << field.id() << " => shape(\n";
indent_up();
out << indent() << "'var' => '" << field.name() << "',\n";
if (tstruct->is_union()) {
// Optimally, we shouldn't set this per field but rather per struct.
// However, the tspec is a field_id => data array, and if we set it
// at the top level people might think the 'union' key is a field
// id, which isn't cool. It's safer and more bc to instead set this
// key on all fields.
out << indent() << "'union' => true,\n";
}
if (find_hack_wrapper(field)) {
indent(out) << "'is_wrapped' => true,\n";
}
generate_php_type_spec(out, &t);
indent_down();
indent(out) << "),\n";
}
indent_down();
indent(out) << "];\n";
indent(out) << "const dict<string, int> FIELDMAP = dict[\n";
indent_up();
for (const auto& field : fields) {
indent(out) << "'" << field->name() << "' => " << field->id() << ",\n";
}
indent_down();
indent(out) << "];\n";
}
void t_hack_generator::generate_php_struct_struct_trait(
std::ofstream& out, const t_struct* tstruct, const std::string& name) {
std::string traitName;
if (const auto* structtrait =
tstruct->find_annotation_or_null("php.structtrait")) {
if (structtrait->empty() || *structtrait == "1") {
traitName = hack_name(name, tstruct->program()) + "Trait";
} else {
traitName = hack_name(*structtrait, tstruct->program());
}
} else if (struct_trait_) {
traitName = hack_name(name, tstruct->program()) + "Trait";
}
if (!traitName.empty()) {
indent(out) << "use " << traitName << ";\n\n";
}
}
void t_hack_generator::generate_php_struct_shape_spec(
std::ofstream& out, const t_struct* tstruct, bool is_constructor_shape) {
indent(out) << "const type "
<< (is_constructor_shape ? "TConstructorShape" : "TShape")
<< " = shape(\n";
for (const auto& field : tstruct->fields()) {
const t_type* t = field.get_type();
// Compute typehint before resolving typedefs to avoid missing any adapter
// annotations.
std::string typehint = type_to_typehint(t, false, !is_constructor_shape);
std::string dval = "";
if (field.default_value() != nullptr &&
!(t->is_struct() || t->is_xception())) {
dval = render_const_value(t, field.default_value());
} else {
dval = render_default_value(t);
}
bool nullable =
nullable_everything_ || field_is_nullable(tstruct, &field, dval);
std::string prefix = nullable || is_constructor_shape ? "?" : "";
indent(out) << " " << prefix << "'" << field.name() << "' => " << prefix
<< typehint << ",\n";
}
if (!is_constructor_shape && shapes_allow_unknown_fields_) {
indent(out) << " ...\n";
}
indent(out) << ");\n";
}
/**
* Generate a Lambda on a Collection Value.
*
* For example, if our structure is:
*
* 1: map<string, list<i32>> map_of_string_to_list_of_i32;
*
* Then our __toShape() routine results in:
*
* 'map_of_string_to_list_of_i32' => $this->map_of_string_to_list_of_i32->map(
* $_val0 ==> $_val0->toVArray(),
* )
* |> ThriftUtil::toDArray($$),
*
* And this method here will get called with
*
* generate_php_struct_shape_collection_value_lambda(..., list<i32>)
*
* And returns the string:
*
* " $_val0 ==> $_val0->toVArray(),"
*
* This method operates via recursion on complex types.
*/
void t_hack_generator::generate_php_struct_shape_collection_value_lambda(
std::ostream& out, t_name_generator& namer, const t_type* t) {
std::string tmp = namer("_val");
indent(out);
out << "($" << tmp << ") ==> ";
if (t->is_struct()) {
out << "$" << tmp << "->__toShape(),\n";
} else if (t->is_set()) {
if (arraysets_ || no_use_hack_collections_) {
out << "dict($" << tmp << "),\n";
} else {
out << "ThriftUtil::toDArray(Dict\\fill_keys($" << tmp
<< ", true), static::class),\n";
}
} else if (t->is_map() || t->is_list()) {
const t_type* val_type;
if (t->is_map()) {
val_type = static_cast<const t_map*>(t)->get_val_type();
} else {
val_type = static_cast<const t_list*>(t)->get_elem_type();
}
val_type = val_type->get_true_type();
if (!val_type->is_container() && !val_type->is_struct()) {
out << generate_to_array_method(t, "$" + tmp) << ",\n";
return;
}
out << "$" << tmp << "->map(\n";
indent_up();
generate_php_struct_shape_collection_value_lambda(out, namer, val_type);
indent_down();
indent(out) << ")\n";
indent_up();
indent(out) << "|> " << generate_to_array_method(t, "$$") << ",\n";
indent_down();
}
}
void t_hack_generator::generate_hack_array_from_shape_lambda(
std::ostream& out, t_name_generator& namer, const t_type* t) {
if (t->is_map()) {
out << "Dict\\map(\n";
} else {
out << "Vec\\map(\n";
}
indent_up();
indent(out) << "$$,\n";
std::string tmp = namer("_val");
indent(out) << "$" << tmp << " ==> $" << tmp;
const t_type* val_type;
if (t->is_map()) {
val_type = static_cast<const t_map*>(t)->get_val_type();
} else {
val_type = static_cast<const t_list*>(t)->get_elem_type();
}
val_type = val_type->get_true_type();
if (val_type->is_map() || val_type->is_list() || val_type->is_struct()) {
indent_up();
out << "\n";
indent(out) << "|> ";
if (val_type->is_struct()) {
std::string type = hack_name(val_type);
out << type << "::__fromShape($$),\n";
} else {
generate_hack_array_from_shape_lambda(out, namer, val_type);
out << ",\n";
}
indent_down();
} else {
out << ",\n";
}
indent_down();
indent(out) << ")";
if (no_use_hack_collections_) {
if (t->is_map()) {
out << " |> dict($$)";
} else {
out << " |> vec($$)";
}
}
}
void t_hack_generator::generate_shape_from_hack_array_lambda(
std::ostream& out, t_name_generator& namer, const t_type* t) {
if (no_use_hack_collections_) {
out << "(\n";
indent_up();
indent(out);
}
if (t->is_map()) {
out << "Dict\\map(\n";
} else {
out << "Vec\\map(\n";
}
indent_up();
indent(out) << "$$,\n";
std::string tmp = namer("_val");
indent(out);
out << "($" << tmp << ") ==> $" << tmp;
const t_type* val_type;
if (t->is_map()) {
val_type = static_cast<const t_map*>(t)->get_val_type();
} else {
val_type = static_cast<const t_list*>(t)->get_elem_type();
}
val_type = val_type->get_true_type();
if (val_type->is_struct()) {
out << "->__toShape(),\n";
} else if (val_type->is_map() || val_type->is_list()) {
indent_up();
out << "\n";
indent(out) << "|> ";
generate_shape_from_hack_array_lambda(out, namer, val_type);
indent_down();
} else {
out << ",\n";
}
indent_down();
if (no_use_hack_collections_) {
if (t->is_map()) {
indent(out) << ") |> dict($$)\n";
} else {
indent(out) << ") |> vec($$)\n";
}
indent_down();
}
indent(out) << "),\n";
}
bool t_hack_generator::type_has_nested_struct(const t_type* t) {
bool has_struct = false;
const t_type* val_type = t;
while (true) {
if (val_type->is_map()) {
val_type = static_cast<const t_map*>(val_type)->get_val_type();
} else {
val_type = static_cast<const t_list*>(val_type)->get_elem_type();
}
val_type = val_type->get_true_type();
if (!(val_type->is_map() || val_type->is_list())) {
if (val_type->is_struct()) {
has_struct = true;
}
break;
}
}
return has_struct;
}
/**
* Determine whether a field should be marked nullable.
*/
bool t_hack_generator::field_is_nullable(
const t_struct* tstruct, const t_field* field, std::string dval) {
const t_type* t = field->type()->get_true_type();
return (dval == "null") || tstruct->is_union() ||
(field->get_req() == t_field::e_req::optional &&
field->default_value() == nullptr) ||
(t->is_enum() && field->get_req() != t_field::e_req::required);
}
void t_hack_generator::generate_php_struct_stringifyMapKeys_method(
std::ofstream& out) {
if (!shape_arraykeys_) {
return;
}
std::string arg_return_type;
if (arrays_) {
arg_return_type = "dict";
} else if (no_use_hack_collections_) {
arg_return_type = "darray";
} else if (const_collections_) {
arg_return_type = "\\ConstMap";
} else {
arg_return_type = "Map";
}
indent(out) << "public static function __stringifyMapKeys<T>("
<< arg_return_type << "<arraykey, T> $m)[]: " << arg_return_type
<< "<string, T> {\n";
indent_up();
if (arrays_ || no_use_hack_collections_) {
indent(out) << "return Dict\\map_keys($m, $key ==> (string)$key);\n";
} else {
indent(out) << "$new = dict[];\n";
indent(out) << "foreach ($m as $k => $v) {\n";
indent_up();
indent(out) << "$new[(string)$k] = $v;\n";
indent_down();
indent(out) << "}\n";
indent(out) << "return new Map($new);\n";
}
indent_down();
indent(out) << "}\n\n";
}
void t_hack_generator::generate_php_struct_shape_methods(
std::ofstream& out, const t_struct* tstruct) {
generate_php_struct_stringifyMapKeys_method(out);
indent(out)
<< "public static function __fromShape(self::TShape $shape)[]: this {\n";
indent_up();
indent(out) << "return new static(\n";
indent_up();
t_name_generator namer;
for (const auto& field : tstruct->fields()) {
const t_type* t = field.type()->get_true_type();
std::string dval = "";
if (field.default_value() != nullptr &&
!(t->is_struct() || t->is_xception())) {
dval = render_const_value(t, field.default_value());
} else {
dval = render_default_value(t);
}
bool nullable =
field_is_nullable(tstruct, &field, dval) || nullable_everything_;
std::stringstream source;
source << "$shape['" << field.name() << "']";
std::stringstream inner;
bool is_simple_shape_index = true;
if (t->is_set()) {
if (arraysets_ || arrays_ || no_use_hack_collections_) {
inner << source.str();
} else {
is_simple_shape_index = false;
inner << "new Set(Keyset\\keys(" << source.str() << "))";
}
} else if (t->is_map() || t->is_list()) {
bool stringify_map_keys = false;
if (t->is_map() && shape_arraykeys_) {
const t_type* key_type = static_cast<const t_map*>(t)->get_key_type();
if (key_type->is_base_type() && key_type->is_string_or_binary()) {
stringify_map_keys = true;
}
}
if (stringify_map_keys) {
is_simple_shape_index = false;
inner << "self::__stringifyMapKeys(";
}
if (arrays_ || no_use_hack_collections_) {
inner << source.str();
if (type_has_nested_struct(t)) {
is_simple_shape_index = false;
indent_up();
inner << std::endl;
indent(inner) << "|> ";
generate_hack_array_from_shape_lambda(inner, namer, t);
indent_down();
}
if (stringify_map_keys) {
inner << ")";
}
} else {
is_simple_shape_index = false;
inner << (stringify_map_keys ? "" : "(");
if (t->is_map()) {
inner << "new Map(";
} else {
inner << "new Vector(";
}
inner << source.str() << "))";
int nest = 0;
while (true) {
const t_type* val_type;
if (t->is_map()) {
val_type = static_cast<const t_map*>(t)->get_val_type();
} else {
val_type = static_cast<const t_list*>(t)->get_elem_type();
}
val_type = val_type->get_true_type();
if ((val_type->is_set() && !arraysets_) || val_type->is_map() ||
val_type->is_list() || val_type->is_struct()) {
indent_up();
nest++;
inner << "->map(\n";
if (val_type->is_set()) {
std::string tmp = namer("val");
indent(inner) << "$" << tmp << " ==> new Set(Keyset\\keys($"
<< tmp << ")),\n";
break;
} else if (val_type->is_map() || val_type->is_list()) {
std::string tmp = namer("val");
stringify_map_keys = false;
if (val_type->is_map() && shape_arraykeys_) {
const t_type* key_type =
static_cast<const t_map*>(val_type)->get_key_type();
if (key_type->is_base_type() &&
key_type->is_string_or_binary()) {
stringify_map_keys = true;
}
}
indent(inner)
<< "$" << tmp << " ==> "
<< (stringify_map_keys ? "self::__stringifyMapKeys" : "")
<< "(new ";
if (val_type->is_map()) {
inner << "Map";
} else {
inner << "Vector";
}
inner << "($" << tmp << "))";
t = val_type;
} else if (val_type->is_struct()) {
std::string tmp = namer("val");
std::string type = hack_name(val_type);
indent(inner) << "$" << tmp << " ==> " << type << "::__fromShape("
<< "$" << tmp << "),\n";
break;
}
} else {
if (nest > 0) {
inner << ",\n";
}
break;
}
}
while (nest-- > 0) {
indent_down();
indent(inner) << ")";
if (nest > 0) {
inner << ",\n";
}
}
}
} else if (t->is_struct()) {
is_simple_shape_index = false;
std::string type = hack_name(t);
inner << type << "::__fromShape(" << source.str() << ")";
} else {
inner << source.str();
}
std::stringstream val;
indent(val);
if (tstruct->is_union() || nullable) {
val << "Shapes::idx($shape, '" << field.name() << "')";
if (!is_simple_shape_index) {
val << " === null ? null : (" << inner.str() << ")";
}
} else {
val << inner.str();
}
val << ",\n";
out << val.str();
}
indent_down();
indent(out) << ");\n";
indent_down();
indent(out) << "}\n";
out << "\n";
indent(out) << "public function __toShape()[]: self::TShape {\n";
indent_up();
indent(out) << "return shape(\n";
indent_up();
for (const auto& field : tstruct->fields()) {
const t_type* t = field.type()->get_true_type();
t_name_generator ngen;
indent(out) << "'" << field.name() << "' => ";
std::stringstream val;
bool nullable =
field_is_nullable(tstruct, &field, render_default_value(t)) ||
nullable_everything_;
auto fieldRef = "$this->" + field.name();
if (t->is_container()) {
if (t->is_map() || t->is_list()) {
if (arrays_ || no_use_hack_collections_) {
val << fieldRef;
if (type_has_nested_struct(t)) {
val << "\n";
indent_up();
indent(val) << "|> ";
if (nullable) {
val << "$$ === null \n";
indent_up();
indent(val) << "? null \n";
indent(val) << ": ";
}
generate_shape_from_hack_array_lambda(val, ngen, t);
if (nullable) {
indent_down();
}
indent_down();
} else {
val << ",\n";
}
} else {
const t_type* val_type;
if (t->is_map()) {
val_type = static_cast<const t_map*>(t)->get_val_type();
} else {
val_type = static_cast<const t_list*>(t)->get_elem_type();
}
val_type = val_type->get_true_type();
if (val_type->is_container() || val_type->is_struct() || nullable) {
val << fieldRef;
if (val_type->is_container() || val_type->is_struct()) {
val << (nullable ? "?" : "") << "->map(\n";
indent_up();
generate_php_struct_shape_collection_value_lambda(
val, ngen, val_type);
indent_down();
indent(val) << ")";
}
val << std::endl;
indent_up();
indent(val) << "|> " << (nullable ? "$$ === null ? null : " : "")
<< generate_to_array_method(t, "$$") << ",\n";
indent_down();
} else {
val << generate_to_array_method(t, fieldRef) << ",\n";
}
}
} else if (arraysets_ || arrays_ || no_use_hack_collections_) {
val << fieldRef << ",\n";
} else {
if (nullable) {
val << fieldRef << "\n";
indent_up();
indent(val) << "|> $$ === null ? null : ";
}
val << "ThriftUtil::toDArray(Dict\\fill_keys(";
if (nullable) {
val << "$$";
} else {
val << fieldRef;
}
val << "->toValuesArray(), true), static::class),\n";
if (nullable) {
indent_down();
}
}
} else if (t->is_struct()) {
val << fieldRef;
val << (nullable ? "?" : "") << "->__toShape(),\n";
} else {
val << fieldRef << ",\n";
}
out << val.str();
}
indent_down();
indent(out) << ");\n";
indent_down();
indent(out) << "}\n";
}
bool t_hack_generator::generate_php_struct_async_fromShape_method_helper(
std::ostream& out,
const t_type* ttype,
t_name_generator& namer,
std::string val) {
if (const auto* tstruct = dynamic_cast<const t_struct*>(ttype)) {
bool is_async = is_async_shapish_struct(tstruct);
if (is_async) {
out << "await " << hack_name(tstruct) << "::__genFromShape(" << val
<< ")";
} else {
out << hack_name(tstruct) << "::__fromShape(" << val << ")";
}
return is_async;
} else if (ttype->is_container()) {
if (ttype->is_set()) {
if (arraysets_ || arrays_ || no_use_hack_collections_) {
out << val;
} else {
out << "new Set(Keyset\\keys(" << val << "))";
}
return false;
}
std::string prefix = "";
std::string suffix = "";
std::string container_type = "";
bool stringify_map_keys = false;
const t_type* val_type;
if (ttype->is_map()) {
container_type = "Dict\\";
if (shape_arraykeys_) {
const t_type* key_type =
static_cast<const t_map*>(ttype)->get_key_type();
if (key_type->is_base_type() && key_type->is_string_or_binary()) {
stringify_map_keys = true;
indent_up();
out << "self::__stringifyMapKeys(\n" << indent();
}
}
if (!(arrays_ || no_use_hack_collections_)) {
prefix = prefix + "new Map(";
suffix = ")" + suffix;
}
val_type = static_cast<const t_map*>(ttype)->get_val_type();
} else {
container_type = "Vec\\";
if (!(arrays_ || no_use_hack_collections_)) {
prefix = "new Vector(";
suffix = ")";
}
val_type = static_cast<const t_list*>(ttype)->get_elem_type();
}
val_type = val_type->get_true_type();
std::stringstream inner;
bool is_async = false;
std::string inner_val = namer("$val");
indent_up();
indent_up();
is_async = generate_php_struct_async_fromShape_method_helper(
inner, val_type, namer, inner_val);
indent_down();
auto val_map_method = inner.str();
if (inner_val == val_map_method) {
// Since value doesn't need mapping, 'inner_val' will be unused.
// Decrement the counter so that we don't skip namer values.
namer.decrement_counter();
indent_down();
out << prefix << val << suffix;
if (stringify_map_keys) {
indent_down();
out << "\n" << indent() << ")";
}
return false;
}
out << prefix << (is_async ? "await " : "") << container_type
<< (is_async ? "map_async" : "map") << "(\n";
out << indent() << val << ",\n"
<< indent() << (is_async ? "async " : "") << inner_val << " ==> \n";
indent_up();
out << indent() << inner.str() << "\n";
indent_down();
indent_down();
out << indent() << ")" << suffix;
if (stringify_map_keys) {
indent_down();
out << "\n" << indent() << ")";
}
return is_async;
} else {
out << val;
return false;
}
}
bool t_hack_generator::generate_php_struct_async_toShape_method_helper(
std::ostream& out,
const t_type* ttype,
t_name_generator& namer,
std::string val) {
if (const auto* tstruct = dynamic_cast<const t_struct*>(ttype)) {
if (is_async_shapish_struct(tstruct)) {
out << val << "->__genToShape()";
return true;
} else {
out << val << "->__toShape()";
return false;
}
} else if (ttype->is_container()) {
if (ttype->is_set()) {
if (arraysets_ || arrays_ || no_use_hack_collections_) {
out << val;
} else {
out << "ThriftUtil::toDArray(Dict\\fill_keys(";
out << val;
out << "->toValuesArray(), true), static::class)";
}
return false;
}
std::string container_prefix = "";
bool use_to_darray_conv = false;
const t_type* val_type;
if (ttype->is_map()) {
container_prefix = "Dict\\";
if (array_migration_ && !(arrays_ || no_use_hack_collections_)) {
use_to_darray_conv = true;
indent_up();
out << "ThriftUtil::toDArray(\n" << indent();
}
val_type = static_cast<const t_map*>(ttype)->get_val_type();
} else {
container_prefix = "Vec\\";
val_type = static_cast<const t_list*>(ttype)->get_elem_type();
}
val_type = val_type->get_true_type();
if (val_type->is_container() || val_type->is_struct()) {
std::stringstream inner;
bool is_async = false;
std::string inner_val = namer("$val");
indent_up();
indent_up();
is_async = generate_php_struct_async_toShape_method_helper(
inner, val_type, namer, inner_val);
indent_down();
out << container_prefix << (is_async ? "map_async" : "map") << "(\n";
out << indent() << val << ",\n"
<< indent() << (is_async ? "async " : "") << inner_val << " ==> \n";
indent_up();
out << indent() << (is_async ? "await " : "") << inner.str() << "\n";
indent_down();
indent_down();
out << indent() << ")";
if (use_to_darray_conv) {
indent_down();
out << "\n" << indent() << ")";
}
return is_async;
} else {
out << generate_to_array_method(ttype, val);
return false;
}
} else {
out << val;
return false;
}
}
void t_hack_generator::generate_php_struct_async_shape_methods(
std::ofstream& out,
const t_struct* tstruct,
const std::string& struct_hack_name) {
generate_php_struct_stringifyMapKeys_method(out);
generate_php_struct_async_struct_creation_method_header(
out, ThriftAsyncStructCreationMethod::FROM_SHAPE);
t_name_generator namer;
for (const auto& field : tstruct->fields()) {
const std::string& name = field.name();
const t_type* t = field.type()->get_true_type();
std::string dval = "";
if (field.default_value() != nullptr &&
!(t->is_struct() || t->is_xception())) {
dval = render_const_value(t, field.default_value());
} else {
dval = render_default_value(t);
}
bool nullable =
field_is_nullable(tstruct, &field, dval) || nullable_everything_;
std::string field_ref;
if (tstruct->is_union() || nullable) {
field_ref = "$" + name;
out << indent() << field_ref << " = Shapes::idx($shape, '" << name
<< "');\n";
out << indent() << "if (" << field_ref << " !== null) {\n";
indent_up();
} else {
field_ref = "$shape['" + name + "']";
}
std::stringstream source;
bool is_async = generate_php_struct_async_fromShape_method_helper(
source, t, namer, field_ref);
auto source_str = source.str();
if (const auto* field_wrapper = find_hack_wrapper(field)) {
// await statements need to be in separate line,
// so we need to assign the value to a temp variable
// and then pass it to the wrapper for assignment
if (is_async || source_str != field_ref) {
out << indent() << "$" << name << " = " << source_str << ";\n";
source_str = "$" + name;
}
}
generate_php_struct_async_struct_creation_method_field_assignment(
out, tstruct, field, source_str, struct_hack_name);
if (tstruct->is_union() || nullable) {
indent_down();
out << indent() << "}\n";
}
}
generate_php_struct_async_struct_creation_method_footer(out);
out << "\n";
indent(out)
<< "public async function __genToShape()[zoned]: Awaitable<self::TShape> {\n";
indent_up();
for (const auto& field : tstruct->fields()) {
if (find_hack_wrapper(field)) {
out << indent() << "$" << field.name() << " = await "
<< (tstruct->is_union() ? "" : "(") << "$this->" << field.name()
<< (tstruct->is_union() ? "?" : " as nonnull)") << "->genUnwrap();\n";
}
}
indent(out) << "return shape(\n";
indent_up();
for (const auto& field : tstruct->fields()) {
const t_type* t = field.type()->get_true_type();
t_name_generator ngen;
indent(out) << "'" << field.name() << "' => ";
std::stringstream val;
bool nullable =
field_is_nullable(tstruct, &field, render_default_value(t)) ||
nullable_everything_;
auto fieldRef = "$this->" + field.name();
if (find_hack_wrapper(field)) {
fieldRef = "$" + field.name();
}
if (!t->is_container() && !t->is_struct()) {
out << fieldRef << ",\n";
} else {
bool is_async = generate_php_struct_async_toShape_method_helper(
val, t, ngen, fieldRef);
out << (is_async ? "await " : "");
if (nullable) {
out << "(" << fieldRef << " === null \n";
indent_up();
indent(out) << "? null \n";
indent(out) << ": ";
out << "(\n" << indent() << val.str() << "\n" << indent() << ")\n";
indent_down();
indent(out) << "),\n";
} else {
out << val.str() << ",\n";
}
}
}
indent_down();
indent(out) << ");\n";
indent_down();
indent(out) << "}\n";
}
/**
* Generates the structural ID definition, see generate_structural_id()
* for information about the structural ID.
*/
void t_hack_generator::generate_php_structural_id(
std::ofstream& out, const t_struct* tstruct, bool asFunction) {
if (asFunction) {
indent(out) << "static function getStructuralID()[]: int {\n";
indent_up();
indent(out) << "return " << generate_structural_id(tstruct) << ";\n";
indent_down();
indent(out) << "}\n";
} else {
indent(out) << "const int STRUCTURAL_ID = "
<< generate_structural_id(tstruct) << ";\n";
}
}
bool t_hack_generator::is_async_struct(const t_struct* tstruct) {
for (const auto& field : tstruct->fields()) {
if (find_hack_wrapper(field)) {
return true;
}
}
return false;
}
bool t_hack_generator::is_async_shapish_struct(const t_struct* tstruct) {
std::string parent_struct_name = hack_name(tstruct);
switch (struct_async_type_[parent_struct_name]) {
case ThriftShapishStructType::ASYNC:
return true;
case ThriftShapishStructType::SYNC:
return false;
case ThriftShapishStructType::VISITED:
// 'shapes' option cannot be used with recursive structs
// We should ideally throw an error here.
// But there are certain files that are able to bypass,
// so simply returning false.
return false;
default:
struct_async_type_[parent_struct_name] = ThriftShapishStructType::VISITED;
}
for (const auto& field : tstruct->fields()) {
if (is_async_field(field)) {
struct_async_type_[parent_struct_name] = ThriftShapishStructType::ASYNC;
return true;
}
}
struct_async_type_[parent_struct_name] = ThriftShapishStructType::SYNC;
return false;
}
bool t_hack_generator::is_async_field(const t_field& field) {
return find_hack_wrapper(field) ||
is_async_type(field.get_type()->get_true_type());
}
bool t_hack_generator::is_async_type(const t_type* type) {
type = type->get_true_type();
if (type->is_base_type() || type->is_enum()) {
return false;
} else if (type->is_container()) {
if (const auto* tlist = dynamic_cast<const t_list*>(type)) {
return is_async_type(tlist->get_elem_type());
} else if (const auto* tset = dynamic_cast<const t_set*>(type)) {
return is_async_type(tset->get_elem_type());
} else if (const auto* tmap = dynamic_cast<const t_map*>(type)) {
return is_async_type(tmap->get_key_type()) ||
is_async_type(tmap->get_val_type());
}
} else if (const auto* tstruct = dynamic_cast<const t_struct*>(type)) {
return is_async_shapish_struct(tstruct);
}
return false;
}
void t_hack_generator::generate_php_struct_definition(
std::ofstream& out,
const t_struct* tstruct,
ThriftStructType type,
const std::string& name) {
const std::string& real_name = name.empty() ? tstruct->name() : name;
if (tstruct->is_union()) {
// Generate enum for union before the actual class
generate_php_union_enum(out, tstruct, real_name);
}
_generate_php_struct_definition(out, tstruct, type, real_name);
}
void t_hack_generator::generate_php_union_methods(
std::ofstream& out,
const t_struct* tstruct,
const std::string& struct_hack_name) {
auto enumName = union_enum_name(tstruct);
indent(out) << "public function getType()[]: " << enumName << " {\n";
indent(out) << indent() << "return $this->_type;\n";
indent(out) << "}\n\n";
out << indent() << "public function reset()[write_props]: void {\n";
indent_up();
out << indent() << "switch ($this->_type) {\n";
indent_up();
for (const auto& field : tstruct->fields()) {
const auto& fieldName = field.name();
out << indent() << "case " << enumName << "::" << fieldName << ":\n";
out << indent(get_indent() + 1) << "$this->" << fieldName << " = null;\n";
out << indent(get_indent() + 1) << "break;\n";
}
out << indent() << "case " << enumName << "::_EMPTY_:\n";
out << indent(get_indent() + 1) << "break;\n";
indent_down();
out << indent() << "}\n";
out << indent() << "$this->_type = " << enumName << "::_EMPTY_;\n";
indent_down();
out << indent() << "}\n\n";
for (const auto& field : tstruct->fields()) {
const auto& fieldName = field.name();
auto typehint = type_to_typehint(field.get_type());
// set_<fieldName>()
indent(out) << "public function set_" << fieldName << "(" << typehint
<< " $" << fieldName << ")[write_props]: this {\n";
indent_up();
indent(out) << "$this->reset();\n";
indent(out) << "$this->_type = " << enumName << "::" << fieldName << ";\n";
indent(out) << "$this->" << fieldName << " = ";
if (const auto* field_wrapper = find_hack_wrapper(field)) {
out << *field_wrapper << "::fromThrift_DO_NOT_USE_THRIFT_INTERNAL<"
<< typehint << ", "
<< hack_name(struct_hack_name, tstruct->program(), true) << ">($"
<< fieldName << ", " << field.get_key() << ", $this);\n";
} else {
out << "$" << fieldName << ";\n";
}
indent(out) << "return $this;\n";
indent_down();
indent(out) << "}\n\n";
typehint = field_to_typehint(
field,
hack_name(struct_hack_name, tstruct->program(), true),
/* is_field_nullable */ false);
// get_<fieldName>()
indent(out) << "public function get_" << fieldName << "()[]: ?" << typehint
<< " {\n";
indent_up();
indent(out) << "return $this->" << fieldName << ";\n";
indent_down();
indent(out) << "}\n\n";
// getx_<fieldName>()
indent(out) << "public function getx_" << fieldName << "()[]: " << typehint
<< " {\n";
indent_up();
indent(out) << "invariant(\n";
indent_up();
indent(out) << "$this->_type === " << enumName << "::" << fieldName
<< ",\n";
indent(out) << "'get_" << fieldName << " called on an instance of "
<< tstruct->name() << " whose current type is %s',\n";
indent(out) << "(string)$this->_type,\n";
indent_down();
indent(out) << ");\n";
indent(out) << "return $this->" << fieldName << " as nonnull;\n";
indent_down();
indent(out) << "}\n\n";
}
}
void t_hack_generator::generate_php_struct_fields(
std::ofstream& out,
const t_struct* tstruct,
const std::string& struct_hack_name,
ThriftStructType type) {
auto struct_class_name =
hack_name(struct_hack_name, tstruct->program(), true);
for (const auto& field : tstruct->fields()) {
bool is_base_exception_field = type == ThriftStructType::EXCEPTION &&
is_base_exception_property(&field);
const auto* field_wrapper = find_hack_wrapper(field);
if (is_base_exception_field && field_wrapper) {
throw std::runtime_error(
tstruct->name() + "::" + field.name() +
" has a wrapped type. FieldWrapper annotation is not allowed for base exception properties.");
}
const t_type* t = field.get_type();
t = t->get_true_type();
if (t->is_enum() && is_bitmask_enum(static_cast<const t_enum*>(t))) {
throw std::runtime_error(
"Enum " + t->name() +
" is actually a bitmask, cannot generate a field of this enum type");
}
std::string dval;
if (field.default_value() != nullptr &&
!(t->is_struct() || t->is_xception())) {
dval = render_const_value(t, field.default_value());
} else {
dval = render_default_value(t);
}
// result structs only contain fields: success and e.
// success is whatever type the method returns, but must be nullable
// regardless, since if there is an exception we expect it to be null
bool nullable =
(type == ThriftStructType::RESULT ||
field_is_nullable(tstruct, &field, dval) || nullable_everything_) &&
!is_base_exception_field;
// Compute typehint before resolving typedefs to avoid missing any adapter
// annotations.
std::string typehint = field_to_typehint(
field,
struct_class_name,
nullable && !tstruct->is_union() /* is_field_nullable */);
if (nullable || field_wrapper) {
typehint = "?" + typehint;
}
if (type != ThriftStructType::RESULT && type != ThriftStructType::ARGS) {
generate_php_docstring(out, &field);
}
if (std::string const* field_attributes =
field.find_annotation_or_null("hack.attributes")) {
indent(out) << "<<" << *field_attributes << ">>\n";
}
if (type == ThriftStructType::EXCEPTION && field.name() == "code") {
if (!(t->is_any_int() || t->is_enum())) {
throw std::runtime_error(
tstruct->name() + "::code defined to be a non-integral type. " +
"code fields for Exception classes must be integral");
} else if (
t->is_enum() &&
static_cast<const t_enum*>(t)->get_enum_values().empty()) {
throw std::runtime_error(
"Enum " + t->name() + " is the type for the code property of " +
tstruct->name() + ", but it has no values.");
}
if (t->is_enum()) {
typehint = "/* Originally defined as " + typehint + " */ int";
}
}
std::string visibility = field_wrapper
? "private"
: ((protected_unions_ && tstruct->is_union()) ? "protected" : "public");
indent(out) << visibility << " " << typehint << " $" << field.name()
<< ";\n";
generate_php_struct_field_methods(
out, &field, type == ThriftStructType::EXCEPTION);
if (field_wrapper) {
generate_php_field_wrapper_methods(
out, field, tstruct->is_union(), nullable, struct_class_name);
}
}
}
void t_hack_generator::generate_php_field_wrapper_methods(
std::ofstream& out,
const t_field& field,
bool is_union,
bool nullable,
const std::string& struct_class_name) {
if (is_union) {
return;
}
const auto& fieldName = field.name();
out << "\n";
// get_<fieldName>()
indent(out) << "public function get_" << fieldName << "()[]: "
<< field_to_typehint(
field,
struct_class_name,
/*is_field_nullable*/ nullable)
<< " {\n";
indent_up();
indent(out) << "return $this->" << fieldName << " as nonnull;\n";
indent_down();
indent(out) << "}\n\n";
}
void t_hack_generator::generate_php_struct_field_methods(
std::ofstream& out, const t_field* field, bool is_exception) {
const t_type* t = field->get_type();
t = t->get_true_type();
if (is_exception && field->name() == "code" && t->is_enum()) {
std::string enum_type = type_to_typehint(t);
out << "\n";
out << indent() << "public function setCodeAsEnum(" << enum_type
<< " $code)[write_props]: void {\n";
if (!enum_transparenttype_) {
out << indent() << " /* HH_FIXME[4110] nontransparent enum */\n";
}
out << indent() << " $this->code = $code;" << indent() << "\n"
<< indent() << "}\n\n";
out << indent() << "public function getCodeAsEnum()[]: " << enum_type
<< " {\n"
<< indent()
<< " /* HH_FIXME[4110] retain HHVM enforcement semantics */\n"
<< indent() << " return $this->code;" << indent() << "\n"
<< indent() << "}\n";
}
}
void t_hack_generator::generate_php_struct_methods(
std::ofstream& out,
const t_struct* tstruct,
ThriftStructType type,
const std::string& name,
bool is_async_struct,
bool is_async_shapish_struct) {
if (is_async_struct) {
generate_php_struct_default_constructor(out, tstruct, type, name);
generate_php_struct_withDefaultValues_method(out);
generate_php_struct_async_struct_creation_method(
out,
tstruct,
name,
ThriftAsyncStructCreationMethod::FROM_CONSTRUCTOR_SHAPE);
out << "\n";
if (from_map_construct_) {
generate_php_struct_async_struct_creation_method(
out, tstruct, name, ThriftAsyncStructCreationMethod::FROM_MAP);
out << "\n";
}
} else {
generate_php_struct_constructor(out, tstruct, type, name);
generate_php_struct_withDefaultValues_method(out);
generate_php_struct_from_shape(out, tstruct);
out << "\n";
if (from_map_construct_) {
generate_php_struct_from_map(out, tstruct);
out << "\n";
}
}
out << indent() << "public function getName()[]: string {\n"
<< indent() << " return '" << name << "';\n"
<< indent() << "}\n\n";
if (tstruct->is_union()) {
generate_php_union_methods(out, tstruct, name);
}
if (type == ThriftStructType::EXCEPTION) {
const auto& value = tstruct->get_annotation("message");
if (tstruct->has_annotation("message") && value != "message") {
const auto* message_field = tstruct->get_field_by_name(value);
if (const auto field_wrapper = find_hack_wrapper(*message_field)) {
throw std::runtime_error(
tstruct->name() + "::" + value +
" has a wrapped type. FieldWrapper annotation is not allowed for base exception properties.");
}
out << indent() << "<<__Override>>\n"
<< indent() << "public function getMessage()[]: string {\n";
out << indent() << " return $this->" << message_field->name();
if (message_field->get_req() != t_field::e_req::required) {
out << " ?? ''";
}
out << ";\n" << indent() << "}\n\n";
}
}
generate_php_struct_metadata_method(out, tstruct);
generate_php_struct_structured_annotations_method(out, tstruct);
if (shapes_ && type != ThriftStructType::EXCEPTION &&
type != ThriftStructType::RESULT) {
if (is_async_shapish_struct) {
generate_php_struct_async_shape_methods(out, tstruct, name);
} else {
generate_php_struct_shape_methods(out, tstruct);
}
}
generate_instance_key(out);
generate_json_reader(out, tstruct);
generate_adapter_type_checks(out, tstruct);
}
void t_hack_generator::generate_php_struct_constructor_field_assignment(
std::ofstream& out,
const t_field& field,
const t_struct* tstruct,
ThriftStructType type,
const std::string& name,
bool is_default_assignment) {
const t_type* t = field.type()->get_true_type();
const auto true_type = type_to_typehint(t, false, false, false, false);
std::string dval = "";
bool is_exception = tstruct->is_exception();
if (field.default_value() != nullptr &&
!(t->is_struct() || t->is_xception())) {
dval = render_const_value(t, field.default_value());
} else if (
tstruct->is_exception() &&
(field.name() == "code" || field.name() == "line")) {
if (t->is_any_int()) {
dval = "0";
} else {
// just use the lowest value
const t_enum* tenum = static_cast<const t_enum*>(t);
dval =
hack_name(tenum) + "::" + (*tenum->get_enum_values().begin())->name();
}
} else if (
is_exception && (field.name() == "message" || field.name() == "file")) {
dval = "''";
} else if (tstruct->is_union() || nullable_everything_) {
dval = "null";
} else {
dval = render_default_value(t);
}
if (dval != "null") {
if (const auto* adapter = find_hack_adapter(field.get_type())) {
dval = *adapter + "::fromThrift(" + dval + ")";
}
}
// result structs only contain fields: success and e.
// success is whatever type the method returns, but must be nullable
// regardless, since if there is an exception we expect it to be null
// TODO(ckwalsh) Extract this logic into a helper function
bool nullable = type == ThriftStructType::RESULT ||
(!(is_exception && is_base_exception_property(&field)) &&
(dval == "null" ||
(field.get_req() == t_field::e_req::optional &&
field.default_value() == nullptr)));
const std::string& field_name = field.name();
bool need_enum_code_fixme = is_exception && field_name == "code" &&
t->is_enum() && !enum_transparenttype_;
if (is_default_assignment) {
if (const auto* field_wrapper = find_hack_wrapper(field)) {
out << indent() << "$this->" << field_name << " = " << *field_wrapper
<< "::fromThrift_DO_NOT_USE_THRIFT_INTERNAL<" << (nullable ? "?" : "")
<< true_type << ", " << hack_name(name, tstruct->program(), true)
<< ">(" << (nullable ? "null" : dval) << ", " << field.get_key()
<< ", $this);\n";
} else if (!nullable) {
out << indent() << "$this->" << field_name << " = " << dval << ";\n";
}
} else {
if (tstruct->is_union()) {
// Capture value from constructor and update _type field
out << indent() << "if ($" << field_name << " !== null) {\n";
}
if (need_enum_code_fixme) {
out << indent() << " /* HH_FIXME[4110] nontransparent Enum */\n";
}
out << indent() << (tstruct->is_union() ? " " : "") << "$this->"
<< field_name << " = $" << field_name
<< (!nullable ? " ?? " + dval : "") << ";\n";
if (tstruct->is_union()) {
out << indent()
<< " $this->_type = " << union_field_to_enum(tstruct, &field, name)
<< ";\n"
<< indent() << "}\n";
}
}
}
void t_hack_generator::generate_php_struct_constructor(
std::ofstream& out,
const t_struct* tstruct,
ThriftStructType type,
const std::string& name) {
out << indent() << "public function __construct(";
auto delim = "";
for (const auto& field : tstruct->fields()) {
out << delim << "?" << type_to_typehint(field.get_type()) << " $"
<< field.name() << " = null";
delim = ", ";
}
out << indent() << ")[] {\n";
indent_up();
if (type == ThriftStructType::EXCEPTION) {
out << indent() << "parent::__construct();\n";
}
if (tstruct->is_union()) {
out << indent()
<< "$this->_type = " << union_field_to_enum(tstruct, nullptr, name)
<< ";\n";
}
for (const auto& field : tstruct->fields()) {
generate_php_struct_constructor_field_assignment(
out, field, tstruct, type, name);
}
scope_down(out);
out << "\n";
}
void t_hack_generator::generate_php_struct_withDefaultValues_method(
std::ofstream& out) {
indent(out) << "public static function withDefaultValues()[]: this {\n";
indent_up();
indent(out) << "return new static();\n";
scope_down(out);
out << "\n";
}
void t_hack_generator::generate_php_struct_default_constructor(
std::ofstream& out,
const t_struct* tstruct,
ThriftStructType type,
const std::string& name) {
out << indent() << "public function __construct()[] {\n";
indent_up();
if (!tstruct->is_union()) {
if (type == ThriftStructType::EXCEPTION) {
out << indent() << "parent::__construct();\n";
}
std::vector<const t_field*> wrapped_fields;
if (type != ThriftStructType::RESULT) {
for (const auto& field : tstruct->fields()) {
// Fields with FieldWrappr annotation are nullable and need to be set
// at the end after all non-nullable fields are set.
if (find_hack_wrapper(field)) {
wrapped_fields.push_back(&field);
} else {
generate_php_struct_constructor_field_assignment(
out, field, tstruct, type, name, true);
}
}
for (const auto& field : wrapped_fields) {
generate_php_struct_constructor_field_assignment(
out, *field, tstruct, type, name, true);
}
}
}
scope_down(out);
out << "\n";
}
void t_hack_generator::generate_php_struct_metadata_method(
std::ofstream& out, const t_struct* tstruct) {
bool is_exception = tstruct->is_exception();
// Metadata
out << indent() << "public static function get"
<< (is_exception ? "Exception" : "Struct") << "Metadata()[]: "
<< (is_exception ? "\\tmeta_ThriftException" : "\\tmeta_ThriftStruct")
<< " {\n";
indent_up();
bool saved_arrays_ = arrays_;
arrays_ = true;
out << indent() << "return "
<< render_const_value(
is_exception ? tmeta_ThriftException_type()
: tmeta_ThriftStruct_type(),
struct_to_tmeta(tstruct, is_exception).get())
<< ";\n";
arrays_ = saved_arrays_;
indent_down();
out << indent() << "}\n\n";
}
void t_hack_generator::generate_php_struct_structured_annotations_method(
std::ofstream& out, const t_struct* tstruct) {
indent(out) << "public static function getAllStructuredAnnotations()[]: "
"\\TStructAnnotations {\n";
indent_up();
std::stringstream annotations_out;
std::stringstream annotations_temp_var_initializations_out;
t_name_generator namer;
indent(annotations_out) << "return shape(\n";
indent_up();
indent(annotations_out) << "'struct' => "
<< render_structured_annotations(
tstruct->structured_annotations(),
annotations_temp_var_initializations_out,
namer)
<< ",\n";
indent(annotations_out) << "'fields' => dict[\n";
indent_up();
for (auto&& field : tstruct->fields()) {
if (field.structured_annotations().empty() &&
field.type()->structured_annotations().empty()) {
continue;
}
indent(annotations_out) << "'" << field.name() << "' => shape(\n";
indent_up();
indent(annotations_out) << "'field' => "
<< render_structured_annotations(
field.structured_annotations(),
annotations_temp_var_initializations_out,
namer)
<< ",\n";
indent(annotations_out) << "'type' => "
<< render_structured_annotations(
field.type()->structured_annotations(),
annotations_temp_var_initializations_out,
namer)
<< ",\n";
indent_down();
indent(annotations_out) << "),\n";
}
out << annotations_temp_var_initializations_out.str();
out << annotations_out.str();
indent_down();
indent(out) << "],\n";
indent_down();
indent(out) << ");\n";
indent_down();
indent(out) << "}\n\n";
}
void t_hack_generator::generate_php_union_enum(
std::ofstream& out, const t_struct* tstruct, const std::string& name) {
// Generate enum class with this pattern
// enum <UnionName>Enum: int {
// __EMPTY__ = 0;
// field1 = 1;
// }
if (std::string const* union_enum_attributes =
tstruct->find_annotation_or_null("hack.union_enum_attributes")) {
indent(out) << "<<" << *union_enum_attributes << ">>\n";
}
out << "enum " << union_enum_name(name, tstruct->program(), true)
<< ": int {\n";
indent_up();
// If no field is set
indent(out) << UNION_EMPTY << " = 0;\n";
for (const auto& field : tstruct->fields()) {
indent(out) << field.name() << " = " << field.id() << ";\n";
}
indent_down();
out << "}\n\n";
}
bool t_hack_generator::is_base_exception_property(const t_field* field) {
static const std::unordered_set<std::string> kBaseExceptionProperties{
"code", "message", "line", "file"};
return kBaseExceptionProperties.find(field->name()) !=
kBaseExceptionProperties.end();
}
std::string t_hack_generator::render_service_metadata_response(
const t_service* service, const bool mangle) {
std::vector<const t_enum*> enums;
std::vector<const t_struct*> structs;
std::vector<const t_exception*> exceptions;
std::vector<const t_service*> services;
std::queue<const t_type*> queue;
std::set<const t_type*> visited;
queue.push(service);
while (!queue.empty()) {
auto next = queue.front();
queue.pop();
if (next == nullptr || visited.find(next) != visited.end()) {
continue;
}
visited.emplace(next);
auto type = next->get_true_type();
if (const auto* tlist = dynamic_cast<const t_list*>(type)) {
queue.push(tlist->get_elem_type());
} else if (const auto* tset = dynamic_cast<const t_set*>(type)) {
queue.push(tset->get_elem_type());
} else if (const auto* tmap = dynamic_cast<const t_map*>(type)) {
queue.push(tmap->get_key_type());
queue.push(tmap->get_val_type());
} else if (
const auto* tinteraction = dynamic_cast<const t_interaction*>(type)) {
continue;
} else if (const auto* tservice = dynamic_cast<const t_service*>(type)) {
if (tservice != service) {
services.push_back(tservice);
}
queue.push(tservice->get_extends());
for (const auto& function : tservice->functions()) {
queue.push(function.get_returntype());
queue.push(&function.params());
queue.push(function.exceptions());
}
} else if (const auto* tenum = dynamic_cast<const t_enum*>(type)) {
enums.push_back(tenum);
} else if (const auto* tstruct = dynamic_cast<const t_struct*>(type)) {
for (const auto& field : tstruct->fields()) {
queue.push(field.get_type());
}
if (!type->program() || type->is_paramlist()) {
continue;
}
if (type->is_exception()) {
auto exception = static_cast<const t_exception*>(type);
exceptions.push_back(exception);
} else {
structs.push_back(tstruct);
}
} else if (const auto* ttypedef = dynamic_cast<const t_typedef*>(type)) {
queue.push(ttypedef->get_type());
} else if (const auto* tsink = dynamic_cast<const t_sink*>(type)) {
queue.push(tsink->get_sink_type());
queue.push(tsink->get_first_response_type());
queue.push(tsink->get_final_response_type());
} else if (
const auto* tstream = dynamic_cast<const t_stream_response*>(type)) {
queue.push(tstream->get_elem_type());
queue.push(tstream->get_first_response_type());
} else {
// Unsupported type
}
}
std::ostringstream out;
out << indent()
<< "return \\tmeta_ThriftServiceMetadataResponse::fromShape(\n";
indent_up();
out << indent() << "shape(\n";
indent_up();
out << indent() << "'context' => \\tmeta_ThriftServiceContext::fromShape(\n";
indent_up();
out << indent() << "shape(\n";
indent_up();
out << indent() << "'service_info' => self::getServiceMetadata(),\n"
<< indent() << "'module' => \\tmeta_ThriftModuleContext::fromShape(\n";
indent_up();
out << indent() << "shape(\n";
indent_up();
out << indent() << "'name' => '" << service->program()->name() << "',\n";
indent_down();
out << indent() << ")\n";
indent_down();
out << indent() << "),\n";
indent_down();
out << indent() << ")\n";
indent_down();
out << indent() << "),\n";
out << indent() << "'metadata' => \\tmeta_ThriftMetadata::fromShape(\n";
indent_up();
out << indent() << "shape(\n";
indent_up();
// Enums
out << indent() << "'enums' => dict[\n";
indent_up();
for (const auto* tenum : enums) {
out << indent() << "'" << tenum->get_scoped_name() << "' => "
<< hack_name(tenum) << "_TEnumStaticMetadata::getEnumMetadata(),\n";
}
indent_down();
out << indent() << "],\n";
// Structs
out << indent() << "'structs' => dict[\n";
indent_up();
for (const auto* tstruct : structs) {
out << indent() << "'" << tstruct->get_scoped_name() << "' => "
<< hack_name(tstruct) << "::getStructMetadata(),\n";
}
indent_down();
out << indent() << "],\n";
// Exceptions
out << indent() << "'exceptions' => dict[\n";
indent_up();
for (const auto* exception : exceptions) {
out << indent() << "'" << exception->get_scoped_name() << "' => "
<< hack_name(exception) << "::getExceptionMetadata(),\n";
}
indent_down();
out << indent() << "],\n";
// Services
out << indent() << "'services' => dict[\n";
indent_up();
for (const auto* tservice : services) {
out << indent() << "'" << tservice->get_scoped_name() << "' => "
<< php_servicename_mangle(mangle, tservice, true)
<< "StaticMetadata::getServiceMetadata(),\n";
}
indent_down();
out << indent() << "],\n";
indent_down();
out << indent() << ")\n";
indent_down();
out << indent() << "),\n";
indent_down();
out << indent() << ")\n";
indent_down();
out << indent() << ");\n";
return out.str();
}
std::string t_hack_generator::render_structured_annotations(
const std::vector<const t_const*>& annotations,
std::ostream& temp_var_initializations_out,
t_name_generator& namer) {
std::ostringstream out;
out << "dict[";
if (!annotations.empty()) {
out << "\n";
indent_up();
for (const auto& annotation : annotations) {
indent(out) << "'" << hack_name(annotation->get_type()) << "' => "
<< render_const_value_helper(
annotation->get_type(),
annotation->get_value(),
temp_var_initializations_out,
namer)
<< ",\n";
}
indent_down();
indent(out);
}
out << "]";
return out.str();
}
void t_hack_generator::generate_adapter_type_checks(
std::ofstream& out, const t_struct* tstruct) {
// Adapter name -> original type of the field that the adapter is for.
std::set<std::pair<std::string, std::string>> adapter_types_;
for (const auto* t : collect_types(tstruct)) {
if (const auto* adapter = find_hack_adapter(t)) {
adapter_types_.emplace(
*adapter,
type_to_typehint(t, false, false, false, /* ignore_adapter */ true));
}
}
if (adapter_types_.empty()) {
return;
}
indent(out)
<< "private static function __hackAdapterTypeChecks()[]: void {\n";
indent_up();
for (const auto& kv : adapter_types_) {
indent(out) << "\\ThriftUtil::requireSameType<" << kv.first
<< "::TThriftType, " << kv.second << ">();\n";
}
indent_down();
indent(out) << "}\n\n";
}
/**
* Generates a struct definition for a thrift data type. This is nothing in
* PHP where the objects are all just associative arrays (unless of course we
* decide to start using objects for them...)
*
* @param tstruct The struct definition
*/
void t_hack_generator::_generate_php_struct_definition(
std::ofstream& out,
const t_struct* tstruct,
ThriftStructType type,
const std::string& name) {
bool generateAsTrait = tstruct->has_annotation("php.trait");
if (type != ThriftStructType::ARGS && type != ThriftStructType::RESULT &&
(type == ThriftStructType::EXCEPTION || !generateAsTrait)) {
generate_php_docstring(out, tstruct, type == ThriftStructType::EXCEPTION);
}
if (std::string const* attributes =
tstruct->find_annotation_or_null("hack.attributes")) {
f_types_ << "<<" << *attributes << ">>\n";
}
out << (generateAsTrait ? "trait " : "class ")
<< hack_name(name, tstruct->program(), true);
if (generateAsTrait) {
out << "Trait";
} else if (tstruct->is_exception()) {
out << " extends \\TException";
}
bool is_async = is_async_struct(tstruct);
if (is_async) {
out << " implements \\IThriftAsyncStruct";
} else {
out << " implements \\IThriftSyncStruct";
}
if (tstruct->is_union()) {
out << ", \\IThriftUnion<" << union_enum_name(name, tstruct->program())
<< ">";
}
bool gen_shapes = shapes_ && type != ThriftStructType::EXCEPTION &&
type != ThriftStructType::RESULT;
bool is_async_shapish = false;
if (gen_shapes) {
is_async_shapish = is_async_shapish_struct(tstruct);
if (is_async_shapish) {
out << ", \\IThriftShapishAsyncStruct";
} else {
out << ", \\IThriftShapishSyncStruct";
}
}
out << " {\n";
indent_up();
if (tstruct->is_union()) {
indent(out) << "use \\ThriftUnionSerializationTrait;\n\n";
} else {
indent(out) << "use \\ThriftSerializationTrait;\n\n";
}
if (generateAsTrait && type == ThriftStructType::EXCEPTION) {
indent(out) << "require extends \\TException;\n";
}
generate_php_struct_struct_trait(out, tstruct, name);
generate_php_struct_spec(out, tstruct);
out << "\n";
generate_php_struct_shape_spec(out, tstruct, true);
out << "\n";
if (gen_shapes) {
generate_php_struct_shape_spec(out, tstruct);
}
generate_php_structural_id(out, tstruct, generateAsTrait);
generate_php_struct_fields(out, tstruct, name, type);
if (tstruct->is_union()) {
// Generate _type to store which field is set and initialize it to _EMPTY_
indent(out) << "protected " << union_enum_name(name, tstruct->program())
<< " $_type = " << union_field_to_enum(tstruct, nullptr, name)
<< ";\n";
}
out << "\n";
generate_php_struct_methods(
out, tstruct, type, name, is_async, is_async_shapish);
indent_down();
out << indent() << "}\n\n";
}
void t_hack_generator::generate_php_struct_from_shape(
std::ofstream& out, const t_struct* tstruct) {
out << indent() << "public static function fromShape"
<< "(self::TConstructorShape $shape)[]: this {\n";
indent_up();
out << indent() << "return new static(\n";
indent_up();
for (const auto& field : tstruct->fields()) {
const std::string& name = field.name();
out << indent() << "Shapes::idx($shape, '" << name << "'),\n";
}
indent_down();
out << indent() << ");\n";
indent_down();
out << indent() << "}\n";
}
void t_hack_generator::generate_php_struct_from_map(
std::ofstream& out, const t_struct* tstruct) {
out << indent() << "public static function fromMap_DEPRECATED(";
if (strict_types_) {
// Generate constructor from Map
out << (const_collections_ ? "\\Const" : "") << "Map<string, mixed> $map";
} else {
// Generate constructor from KeyedContainer
out << (soft_attribute_ ? "<<__Soft>> " : "@")
<< "KeyedContainer<string, mixed> $map";
}
out << ")[]: this {\n";
indent_up();
out << indent() << "return new static(\n";
indent_up();
for (const auto& field : tstruct->fields()) {
out << indent()
<< "/* HH_FIXME[4110] For backwards compatibility with map's mixed values. */\n";
out << indent() << "idx($map, '" << field.name() << "'),\n";
}
indent_down();
out << indent() << ");\n";
indent_down();
out << indent() << "}\n";
}
void t_hack_generator::generate_php_struct_async_struct_creation_method_header(
std::ofstream& out, ThriftAsyncStructCreationMethod method_type) {
indent(out) << "public static async function ";
switch (method_type) {
case ThriftAsyncStructCreationMethod::FROM_MAP:
out << "genFromMap_DEPRECATED(";
if (strict_types_) {
// Generate constructor from Map
out << (const_collections_ ? "\\Const" : "")
<< "Map<string, mixed> $map)";
} else {
// Generate constructor from KeyedContainer
out << (soft_attribute_ ? "<<__Soft>> " : "@")
<< "KeyedContainer<string, mixed> $map)";
}
break;
case ThriftAsyncStructCreationMethod::FROM_CONSTRUCTOR_SHAPE:
out << "genFromShape(self::TConstructorShape $shape)";
break;
case ThriftAsyncStructCreationMethod::FROM_SHAPE:
out << "__genFromShape(self::TShape $shape)";
break;
}
out << "[zoned]: Awaitable<this> {\n";
indent_up();
out << indent() << "$obj = new static();\n";
}
void t_hack_generator::generate_php_struct_async_struct_creation_method_footer(
std::ofstream& out) {
out << indent() << "return $obj;\n";
indent_down();
out << indent() << "}\n";
}
void t_hack_generator::
generate_php_struct_async_struct_creation_method_field_assignment(
std::ofstream& out,
const t_struct* tstruct,
const t_field& field,
const std::string& field_ref,
const std::string& struct_hack_name) {
auto name = field.name();
if (const auto* field_wrapper = find_hack_wrapper(field)) {
if (tstruct->is_union()) {
out << indent() << "$obj->" << name << " = await " << *field_wrapper
<< "::genFromThrift<" << type_to_typehint(field.get_type()) << ", "
<< hack_name(struct_hack_name, tstruct->program(), true) << ">("
<< field_ref << ", " << field.get_key() << ", $obj);\n";
} else {
out << indent() << "await $obj->get_" << name << "()->genWrap("
<< field_ref << ");\n";
}
} else {
out << indent() << "$obj->" << name << " = " << field_ref << ";\n";
}
if (tstruct->is_union()) {
out << indent() << "$obj->_type = "
<< union_field_to_enum(tstruct, &field, struct_hack_name) << ";\n";
}
}
void t_hack_generator::generate_php_struct_async_struct_creation_method(
std::ofstream& out,
const t_struct* tstruct,
const std::string& struct_hack_name,
ThriftAsyncStructCreationMethod method_type) {
generate_php_struct_async_struct_creation_method_header(out, method_type);
std::string idx_prefix = "Shapes::idx($shape, '";
if (method_type == ThriftAsyncStructCreationMethod::FROM_MAP) {
idx_prefix = "idx($map, '";
}
for (const auto& field : tstruct->fields()) {
const std::string& name = field.name();
std::string field_ref = "$" + name;
out << indent() << field_ref << " = " << idx_prefix << name << "');\n";
out << indent() << "if (" << field_ref << " !== null) {\n";
indent_up();
if (method_type == ThriftAsyncStructCreationMethod::FROM_MAP) {
out << indent()
<< "/* HH_FIXME[4110] For backwards compatibility with map's mixed values. */\n";
}
generate_php_struct_async_struct_creation_method_field_assignment(
out, tstruct, field, field_ref, struct_hack_name);
indent_down();
out << indent() << "}\n";
}
generate_php_struct_async_struct_creation_method_footer(out);
}
/**
* Generates a thrift service.
*
* @param tservice The service definition
*/
void t_hack_generator::generate_service(const t_service* tservice) {
if (mangled_services_) {
if (php_namespace(tservice).empty()) {
throw std::runtime_error(
"cannot generate mangled services for " + tservice->name() +
"; no php namespace found");
}
// Note: Because calling generate_service again "uses up" tmp variables,
// generating a mangled service has the effect of changing the files of
// unmangled services declared in the same thrift file (i.e., with
// different tmp variables). Thus we store/restore the tmp_ counter so
// that unmangled service files are not affected.
int orig_tmp = get_tmp_counter();
// generate new files for mangled services, if requested
generate_service(tservice, true);
set_tmp_counter(orig_tmp);
} else {
generate_service(tservice, false);
}
}
void t_hack_generator::generate_service(
const t_service* tservice, bool mangle) {
std::string f_base_name = php_servicename_mangle(mangle, tservice);
std::string f_service_name = get_out_dir() + f_base_name + ".php";
f_service_.open(f_service_name.c_str());
record_genfile(f_service_name);
f_service_ << "<?hh\n" << autogen_comment() << "\n";
std::string hack_ns = hack_namespace(program_);
if (!hack_ns.empty()) {
f_service_ << "namespace " << hack_ns << ";\n\n";
}
// Generate the main parts of the service
generate_service_interface(
tservice,
mangle,
/*async*/ true,
/*client*/ false);
generate_service_interface(
tservice,
mangle,
/*async*/ false,
/*client*/ false);
generate_service_interface(
tservice,
mangle,
/*async*/ true,
/*client*/ true);
generate_service_interface(
tservice,
mangle,
/*async*/ false,
/*client*/ true);
generate_service_client(tservice, mangle);
generate_service_interactions(tservice, mangle);
if (phps_) {
generate_service_processor(tservice, mangle, /*async*/ true);
generate_service_processor(tservice, mangle, /*async*/ false);
}
// Generate the structures passed around and helper functions
generate_service_helpers(tservice, mangle);
// Close service file
f_service_.close();
}
/**
* Generates event handler functions.
*/
void t_hack_generator::generate_processor_event_handler_functions(
std::ofstream& out) {
generate_event_handler_functions(out, "\\TProcessorEventHandler");
}
void t_hack_generator::generate_client_event_handler_functions(
std::ofstream& out) {
generate_event_handler_functions(out, "\\TClientEventHandler");
}
void t_hack_generator::generate_event_handler_functions(
std::ofstream& /*out*/, std::string cl) {
f_service_ << indent() << "public function setEventHandler(" << cl
<< " $event_handler)[write_props]: this {\n"
<< indent() << " $this->eventHandler_ = $event_handler;\n"
<< indent() << " return $this;\n"
<< indent() << "}\n\n";
indent(f_service_) << "public function getEventHandler()[]: " << cl << " {\n"
<< indent() << " return $this->eventHandler_;\n"
<< indent() << "}\n\n";
}
/**
* Generates a service server definition.
*
* @param tservice The service to generate a server for.
*/
void t_hack_generator::generate_service_processor(
const t_service* tservice, bool mangle, bool async) {
// Generate the dispatch methods
std::string suffix = async ? "Async" : "Sync";
std::string extends = "";
std::string extends_processor =
std::string("\\Thrift") + suffix + "Processor";
if (tservice->get_extends() != nullptr) {
extends = php_servicename_mangle(mangle, tservice->get_extends(), true);
extends_processor = extends + suffix + "ProcessorBase";
}
std::string long_name = php_servicename_mangle(mangle, tservice);
// I hate to make this abstract, but Hack doesn't support overriding const
// types. Thus, we will have an inheritance change that does not define the
// const type, then branch off at each service with the processor that does
// define the const type.
f_service_
<< indent() << "abstract class " << long_name << suffix
<< "ProcessorBase extends " << extends_processor << " {\n"
<< indent() << " abstract const type TThriftIf as " << long_name
<< (async ? "Async" : "") << "If;\n"
<< indent()
<< " const classname<\\IThriftServiceStaticMetadata> SERVICE_METADATA_CLASS = "
<< long_name << "StaticMetadata::class;\n\n";
indent_up();
// Generate the process subfunctions
for (const auto* function : get_supported_server_functions(tservice)) {
generate_process_function(tservice, function, async);
}
generate_process_metadata_function(tservice, mangle, async);
indent_down();
f_service_ << "}\n";
f_service_ << indent() << "class " << long_name << suffix
<< "Processor extends " << long_name << suffix
<< "ProcessorBase {\n"
<< indent() << " const type TThriftIf = " << long_name
<< (async ? "Async" : "") << "If;\n"
<< indent() << "}\n";
if (!async) {
f_service_ << indent() << "// For backwards compatibility\n"
<< indent() << "class " << long_name << "Processor extends "
<< long_name << "SyncProcessor {}\n";
}
f_service_ << "\n";
}
void t_hack_generator::generate_process_metadata_function(
const t_service* tservice, bool mangle, bool async) {
// Open function
indent(f_service_)
<< "protected" << (async ? " async" : "")
<< " function process_getThriftServiceMetadata(int $seqid, \\TProtocol $input, \\TProtocol $output): "
<< (async ? "Awaitable<void>" : "void") << " {\n";
indent_up();
std::string function_name = "getThriftServiceMetadata";
std::string argsname =
"\\tmeta_ThriftMetadataService_" + function_name + "_args";
std::string resultname =
"\\tmeta_ThriftMetadataService_" + function_name + "_result";
f_service_ << indent() << "$reply_type = \\TMessageType::REPLY;\n"
<< "\n"
<< indent() << "if ($input is \\TBinaryProtocolAccelerated) {\n"
<< indent() << " $args = \\thrift_protocol_read_binary_struct("
<< "$input, '" << argsname << "');\n"
<< indent()
<< "} else if ($input is \\TCompactProtocolAccelerated) {"
<< "\n"
<< indent()
<< " $args = \\thrift_protocol_read_compact_struct($input, '"
<< argsname << "');\n"
<< indent() << "} else {\n"
<< indent() << " $args = " << argsname
<< "::withDefaultValues();\n"
<< indent() << " $args->read($input);\n"
<< indent() << "}\n";
f_service_ << indent() << "$input->readMessageEnd();\n"
<< indent() << "$result = " << resultname
<< "::withDefaultValues();\n";
f_service_
<< indent() << "try {\n"
<< indent()
<< " $result->success = " << php_servicename_mangle(mangle, tservice)
<< "StaticMetadata::getServiceMetadataResponse();\n"
<< indent() << "} catch (\\Exception $ex) {\n"
<< indent() << " $reply_type = \\TMessageType::EXCEPTION;\n"
<< indent()
<< " $result = new \\TApplicationException($ex->getMessage().\"\\n\".$ex->getTraceAsString());\n"
<< indent() << "}\n";
f_service_ << indent() << "if ($output is \\TBinaryProtocolAccelerated)\n";
scope_up(f_service_);
f_service_ << indent() << "\\thrift_protocol_write_binary($output, '"
<< function_name
<< "', $reply_type, $result, $seqid, $output->isStrictWrite());\n";
scope_down(f_service_);
f_service_ << indent()
<< "else if ($output is \\TCompactProtocolAccelerated)\n";
scope_up(f_service_);
f_service_ << indent() << "\\thrift_protocol_write_compact($output, '"
<< function_name << "', $reply_type, $result, $seqid);\n";
scope_down(f_service_);
f_service_ << indent() << "else\n";
scope_up(f_service_);
// Serialize the request header
f_service_ << indent() << "$output->writeMessageBegin(\"" << function_name
<< "\", $reply_type, $seqid);\n"
<< indent() << "$result->write($output);\n"
<< indent() << "$output->writeMessageEnd();\n"
<< indent() << "$output->getTransport()->flush();\n";
scope_down(f_service_);
// Close function
indent_down();
f_service_ << indent() << "}\n";
}
/**
* Generates a process function definition.
*
* @param tfunction The function to write a dispatcher for
*/
void t_hack_generator::generate_process_function(
const t_service* tservice, const t_function* tfunction, bool async) {
// Open function
indent(f_service_)
<< "protected" << (async ? " async" : "") << " function process_"
<< tfunction->name()
<< "(int $seqid, \\TProtocol $input, \\TProtocol $output): "
<< (async ? "Awaitable<void>" : "void") << " {\n";
indent_up();
std::string service_name = hack_name(tservice);
std::string argsname = generate_function_helper_name(
tservice, tfunction, PhpFunctionNameSuffix::ARGS);
std::string resultname = generate_function_helper_name(
tservice, tfunction, PhpFunctionNameSuffix::RESULT);
const std::string& fn_name = tfunction->name();
f_service_ << indent()
<< "$handler_ctx = $this->eventHandler_->getHandlerContext('"
<< fn_name << "');\n"
<< indent() << "$reply_type = \\TMessageType::REPLY;\n"
<< "\n"
<< indent() << "$this->eventHandler_->preRead($handler_ctx, '"
<< fn_name << "', dict[]);\n"
<< "\n"
<< indent() << "if ($input is \\TBinaryProtocolAccelerated) {\n"
<< indent() << " $args = \\thrift_protocol_read_binary_struct("
<< "$input, '" << argsname << "');\n"
<< indent()
<< "} else if ($input is \\TCompactProtocolAccelerated) {"
<< "\n"
<< indent()
<< " $args = \\thrift_protocol_read_compact_struct($input, '"
<< argsname << "');\n"
<< indent() << "} else {\n"
<< indent() << " $args = " << argsname
<< "::withDefaultValues();\n"
<< indent() << " $args->read($input);\n"
<< indent() << "}\n";
f_service_ << indent() << "$input->readMessageEnd();\n";
f_service_ << indent() << "$this->eventHandler_->postRead($handler_ctx, '"
<< fn_name << "', $args);\n";
// Declare result for non oneway function
if (tfunction->qualifier() != t_function_qualifier::one_way) {
f_service_ << indent() << "$result = " << resultname
<< "::withDefaultValues();\n";
}
// Try block for a function with exceptions
f_service_ << indent() << "try {\n";
indent_up();
// Generate the function call
indent(f_service_) << "$this->eventHandler_->preExec($handler_ctx, '"
<< service_name << "', '" << fn_name << "', $args);\n";
f_service_ << indent();
if (tfunction->qualifier() != t_function_qualifier::one_way &&
!tfunction->get_returntype()->is_void()) {
f_service_ << "$result->success = ";
}
f_service_ << (async ? "await " : "") << "$this->handler->"
<< tfunction->name() << "(";
auto delim = "";
for (const auto& param : tfunction->get_paramlist()->fields()) {
f_service_ << delim << "$args->" << param.name();
delim = ", ";
}
f_service_ << ");\n";
if (tfunction->qualifier() != t_function_qualifier::one_way) {
indent(f_service_) << "$this->eventHandler_->postExec($handler_ctx, '"
<< fn_name << "', $result);\n";
}
indent_down();
int exc_num = 0;
for (const auto& x : t_throws::or_empty(tfunction->exceptions())->fields()) {
f_service_ << indent() << "} catch (" << hack_name(x.get_type()) << " $exc"
<< exc_num << ") {\n";
if (tfunction->qualifier() != t_function_qualifier::one_way) {
indent_up();
f_service_ << indent()
<< "$this->eventHandler_->handlerException($handler_ctx, '"
<< fn_name << "', $exc" << exc_num << ");\n"
<< indent() << "$result->" << x.name() << " = $exc" << exc_num
<< ";\n";
indent_down();
}
++exc_num;
}
f_service_
<< indent() << "} catch (\\Exception $ex) {\n"
<< indent() << " $reply_type = \\TMessageType::EXCEPTION;\n"
<< indent() << " $this->eventHandler_->handlerError($handler_ctx, '"
<< fn_name << "', $ex);\n"
<< indent()
<< " $result = new \\TApplicationException($ex->getMessage().\"\\n\".$ex->getTraceAsString());\n"
<< indent() << "}\n";
// Shortcut out here for oneway functions
if (tfunction->qualifier() == t_function_qualifier::one_way) {
f_service_ << indent() << "return;\n";
indent_down();
f_service_ << indent() << "}\n";
return;
}
f_service_ << indent() << "$this->eventHandler_->preWrite($handler_ctx, '"
<< fn_name << "', $result);\n";
f_service_ << indent() << "if ($output is \\TBinaryProtocolAccelerated)\n";
scope_up(f_service_);
f_service_ << indent() << "\\thrift_protocol_write_binary($output, '"
<< tfunction->name()
<< "', $reply_type, $result, $seqid, $output->isStrictWrite());\n";
scope_down(f_service_);
f_service_ << indent()
<< "else if ($output is \\TCompactProtocolAccelerated)\n";
scope_up(f_service_);
f_service_ << indent() << "\\thrift_protocol_write_compact($output, '"
<< tfunction->name() << "', $reply_type, $result, $seqid);\n";
scope_down(f_service_);
f_service_ << indent() << "else\n";
scope_up(f_service_);
// Serialize the request header
f_service_ << indent() << "$output->writeMessageBegin(\"" << tfunction->name()
<< "\", $reply_type, $seqid);\n"
<< indent() << "$result->write($output);\n"
<< indent() << "$output->writeMessageEnd();\n"
<< indent() << "$output->getTransport()->flush();\n";
scope_down(f_service_);
f_service_ << indent() << "$this->eventHandler_->postWrite($handler_ctx, '"
<< fn_name << "', $result);\n";
// Close function
indent_down();
f_service_ << indent() << "}\n";
}
/**
* Generates helper functions for a service.
*
* @param tservice The service to generate a header definition for
*/
void t_hack_generator::generate_service_helpers(
const t_service* tservice, bool mangle) {
f_service_ << "// HELPER FUNCTIONS AND STRUCTURES\n\n";
for (const auto* function : get_supported_client_functions(tservice)) {
generate_php_function_helpers(tservice, function);
}
for (const auto& interaction : get_interactions(tservice)) {
for (const auto& function : get_supported_client_functions(interaction)) {
generate_php_interaction_function_helpers(
tservice, interaction, function);
}
}
f_service_ << indent() << "class " << php_servicename_mangle(mangle, tservice)
<< "StaticMetadata implements \\IThriftServiceStaticMetadata {\n";
indent_up();
// Expose service metadata
f_service_ << indent() << "public static function getServiceMetadata()[]: "
<< "\\tmeta_ThriftService {\n";
indent_up();
bool saved_arrays_ = arrays_;
arrays_ = true;
f_service_ << indent() << "return "
<< render_const_value(
tmeta_ThriftService_type(),
service_to_tmeta(tservice).get())
<< ";\n";
indent_down();
f_service_ << indent() << "}\n\n";
// Expose all metadata
f_service_ << indent()
<< "public static function getServiceMetadataResponse()[]: "
<< "\\tmeta_ThriftServiceMetadataResponse {\n";
indent_up();
f_service_ << render_service_metadata_response(tservice, mangle);
arrays_ = saved_arrays_;
indent_down();
f_service_ << indent() << "}\n\n";
// Structured annotations
f_service_ << indent()
<< "public static function getAllStructuredAnnotations()[]: "
"\\TServiceAnnotations {\n";
indent_up();
std::stringstream annotations_out;
std::stringstream annotations_temp_var_initializations_out;
t_name_generator namer;
annotations_out << indent() << "return shape(\n";
indent_up();
annotations_out << indent() << "'service' => "
<< render_structured_annotations(
tservice->structured_annotations(),
annotations_temp_var_initializations_out,
namer)
<< ",\n";
annotations_out << indent() << "'functions' => dict[\n";
indent_up();
for (const auto& function : get_supported_client_functions(tservice)) {
if (function->structured_annotations().empty()) {
continue;
}
annotations_out << indent() << "'" << function->name() << "' => "
<< render_structured_annotations(
function->structured_annotations(),
annotations_temp_var_initializations_out,
namer)
<< ",\n";
}
f_service_ << annotations_temp_var_initializations_out.str();
f_service_ << annotations_out.str();
indent_down();
f_service_ << indent() << "],\n";
indent_down();
f_service_ << indent() << ");\n";
indent_down();
f_service_ << indent() << "}\n";
indent_down();
f_service_ << indent() << "}\n\n";
}
void t_hack_generator::generate_service_interactions(
const t_service* tservice, bool mangle) {
const std::vector<const t_service*>& interactions =
get_interactions(tservice);
if (interactions.empty()) {
return;
}
f_service_ << "// INTERACTION HANDLERS\n\n";
const std::string& service_name = tservice->name();
for (const auto* interaction : interactions) {
f_service_ << indent() << "class "
<< php_servicename_mangle(
mangle,
interaction,
service_name + "_" + interaction->name())
<< " extends \\ThriftClientBase {\n";
indent_up();
f_service_ << indent() << "private \\InteractionId $interactionId;\n\n";
f_service_
<< indent() << "public function __construct("
<< "\\TProtocol $input, "
<< "?\\TProtocol $output = null, "
<< "?\\IThriftMigrationAsyncChannel $channel = null)[leak_safe] {\n";
indent_up();
f_service_ << indent()
<< "parent::__construct($input, $output, $channel);\n";
f_service_ << indent() << "if ($this->channel_ is nonnull) {\n";
indent_up();
f_service_ << indent()
<< "$this->interactionId = $this->channel_->createInteraction("
<< render_string(interaction->name()) << ");\n";
indent_down();
f_service_ << indent() << "} else {\n";
indent_up();
f_service_ << indent() << "throw new \\Exception("
<< render_string(
"The channel must be nonnull to create interactions.")
<< ");\n";
indent_down();
f_service_ << indent() << "}\n";
indent_down();
f_service_ << indent() << "}\n\n";
// Generate interaction method implementations
for (const auto& function : get_supported_client_functions(interaction)) {
_generate_service_client_child_fn(f_service_, interaction, function);
_generate_sendImpl(f_service_, interaction, function);
if (function->qualifier() != t_function_qualifier::one_way) {
_generate_recvImpl(f_service_, interaction, function);
}
f_service_ << "\n";
}
indent_down();
f_service_ << indent() << "}\n\n";
}
}
/**
* Generates a struct and helpers for a function.
*
* @param tfunction The function
*/
void t_hack_generator::generate_php_function_helpers(
const t_service* tservice, const t_function* tfunction) {
const std::string& service_name = tservice->name();
if (tfunction->returns_stream()) {
generate_php_stream_function_helpers(tfunction, service_name);
return;
} else if (tfunction->returns_sink()) {
generate_php_sink_function_helpers(tfunction, service_name);
return;
}
generate_php_function_args_helpers(tfunction, service_name);
if (tfunction->qualifier() != t_function_qualifier::one_way) {
generate_php_function_result_helpers(
tfunction,
tfunction->get_returntype(),
tfunction->exceptions(),
service_name,
"_result",
tfunction->get_returntype()->is_void());
}
}
void t_hack_generator::generate_php_function_result_helpers(
const t_function* tfunction,
const t_type* ttype,
const t_throws* ex,
const std::string& prefix,
const std::string& suffix,
bool is_void) {
t_struct result(program_, prefix + "_" + tfunction->name() + suffix);
if (ttype) {
auto success = std::make_unique<t_field>(ttype, "success", 0);
if (!is_void) {
result.append(std::move(success));
}
}
if (ex != nullptr) {
for (const auto& x : ex->fields()) {
result.append(x.clone_DO_NOT_USE());
}
}
generate_php_struct_definition(f_service_, &result, ThriftStructType::RESULT);
}
void t_hack_generator::generate_php_function_args_helpers(
const t_function* tfunction, const std::string& prefix) {
const t_struct* params = tfunction->get_paramlist();
std::string params_name = prefix + "_" + params->name();
generate_php_struct_definition(
f_service_, params, ThriftStructType::ARGS, params_name);
}
/**
* Generates a struct and helpers for a stream function.
*
* @param tfunction The function
*/
void t_hack_generator::generate_php_stream_function_helpers(
const t_function* tfunction, const std::string& prefix) {
const auto* tstream =
dynamic_cast<const t_stream_response*>(tfunction->get_returntype());
generate_php_function_args_helpers(tfunction, prefix);
generate_php_function_result_helpers(
tfunction,
tstream->get_elem_type(),
tstream->exceptions(),
prefix,
"_StreamResponse",
false);
generate_php_function_result_helpers(
tfunction,
tstream->get_first_response_type(),
tfunction->exceptions(),
prefix,
"_FirstResponse",
!tstream->has_first_response());
}
/**
* Generates a struct and helpers for a sink function.
*
* @param tfunction The function
*/
void t_hack_generator::generate_php_sink_function_helpers(
const t_function* tfunction, const std::string& prefix) {
generate_php_function_args_helpers(tfunction, prefix);
const auto* tsink = dynamic_cast<const t_sink*>(tfunction->get_returntype());
generate_php_function_result_helpers(
tfunction,
tsink->get_first_response_type(),
tfunction->exceptions(),
prefix,
"_FirstResponse",
!tsink->sink_has_first_response());
generate_php_function_result_helpers(
tfunction,
tsink->get_sink_type(),
tfunction->get_sink_xceptions(),
prefix,
"_SinkPayload",
/* is_void */ false);
generate_php_function_result_helpers(
tfunction,
tsink->get_final_response_type(),
tfunction->get_sink_final_response_xceptions(),
prefix,
"_FinalResponse",
/* is_void */ false);
}
/**
* Generates a struct and helpers for an interaction function
*/
void t_hack_generator::generate_php_interaction_function_helpers(
const t_service* tservice,
const t_service* interaction,
const t_function* tfunction) {
const std::string& prefix = tservice->name() + "_" + interaction->name();
if (tfunction->returns_stream()) {
generate_php_stream_function_helpers(tfunction, prefix);
return;
} else if (tfunction->returns_sink()) {
generate_php_sink_function_helpers(tfunction, prefix);
return;
}
generate_php_function_args_helpers(tfunction, prefix);
if (tfunction->qualifier() != t_function_qualifier::one_way) {
generate_php_function_result_helpers(
tfunction,
tfunction->get_returntype(),
tfunction->exceptions(),
prefix,
"_result",
tfunction->get_returntype()->is_void());
}
}
/**
* Generates the docstring for a generic object.
*/
void t_hack_generator::generate_php_docstring(
std::ofstream& out, const t_node* tdoc) {
if (tdoc->has_doc()) {
generate_docstring_comment(
out, // out
"/**\n", // comment_start
" * ", // line_prefix
tdoc->get_doc(), // contents
" */\n"); // comment_end
}
}
/**
* Generates the docstring for a function.
*
* This is how the generated docstring looks like:-
*
* <Original docstring goes here>
*
* Original thrift definition:-
* return_type
* functionName(1: argType1 arg_name1,
* 2: argType2 arg_name2)
* throws (1: exceptionType1 ex_name1,
* 2: exceptionType2 ex_name2);
*/
void t_hack_generator::generate_php_docstring(
std::ofstream& out, const t_function* tfunction) {
indent(out) << "/**\n";
// Copy the doc.
if (tfunction->has_doc()) {
generate_docstring_comment(
out, // out
"", // comment_start
" * ", // line_prefix
tfunction->get_doc(), // contents
""); // comment_end
}
// Also write the original thrift function definition.
if (tfunction->has_doc()) {
indent(out) << " * \n";
}
indent(out) << " * "
<< "Original thrift definition:-\n";
// Return type.
indent(out) << " * ";
if (tfunction->qualifier() == t_function_qualifier::one_way) {
out << "oneway ";
}
if (const auto* tstream =
dynamic_cast<const t_stream_response*>(tfunction->get_returntype())) {
if (tstream->has_first_response()) {
out << thrift_type_name(tstream->get_first_response_type()) << ", ";
} else {
out << "void, ";
}
out << "stream<" << thrift_type_name(tstream->get_elem_type());
generate_php_docstring_stream_exceptions(out, tstream->exceptions());
out << ">\n";
} else if (
const auto* tsink =
dynamic_cast<const t_sink*>(tfunction->get_returntype())) {
if (tsink->sink_has_first_response()) {
out << thrift_type_name(tsink->get_first_response_type()) << ", ";
} else {
out << "void, ";
}
out << "sink<" << thrift_type_name(tsink->get_sink_type());
generate_php_docstring_stream_exceptions(out, tsink->sink_exceptions());
out << ", " << thrift_type_name(tsink->get_final_response_type());
generate_php_docstring_stream_exceptions(
out, tsink->final_response_exceptions());
out << ">\n";
} else {
out << thrift_type_name(tfunction->get_returntype()) << "\n";
}
// Function name.
indent(out) << " * " << indent(1) << tfunction->name() << "(";
// Find the position after the " * " from where the function arguments
// should be rendered.
int start_pos = get_indent_size() + tfunction->name().size() + 1;
// Parameters.
generate_php_docstring_args(out, start_pos, tfunction->get_paramlist());
out << ")";
// Exceptions.
if (tfunction->get_xceptions()->has_fields()) {
out << "\n" << indent() << " * " << indent(1) << "throws (";
// Find the position after the " * " from where the exceptions should be
// rendered.
start_pos = get_indent_size() + strlen("throws (");
generate_php_docstring_args(out, start_pos, tfunction->get_xceptions());
out << ")";
}
out << ";\n";
indent(out) << " */\n";
}
/**
* Generates the docstring for a field.
*
* This is how the generated docstring looks like:-
*
* <Original docstring goes here>
*
* Original thrift field:-
* argNumber: argType argName
*/
void t_hack_generator::generate_php_docstring(
std::ofstream& out, const t_field* tfield) {
indent(out) << "/**\n";
// Copy the doc.
if (tfield->has_doc()) {
generate_docstring_comment(
out, // out
"", // comment_start
" * ", // line_prefix
tfield->get_doc(), // contents
""); // comment_end
indent(out) << " * \n";
}
indent(out) << " * "
<< "Original thrift field:-\n";
indent(out) << " * " << tfield->get_key() << ": "
<< tfield->get_type()->get_full_name() << " " << tfield->name()
<< "\n";
indent(out) << " */\n";
}
/**
* Generates the docstring for a struct.
*
* This is how the generated docstring looks like:-
*
* <Original docstring goes here>
*
* Original thrift struct/exception:-
* Name
*/
void t_hack_generator::generate_php_docstring(
std::ofstream& out, const t_struct* tstruct, bool is_exception) {
indent(out) << "/**\n";
// Copy the doc.
if (tstruct->has_doc()) {
generate_docstring_comment(
out, // out
"", // comment_start
" * ", // line_prefix
tstruct->get_doc(), // contents
""); // comment_end
indent(out) << " * \n";
}
indent(out) << " * "
<< "Original thrift " << (is_exception ? "exception" : "struct")
<< ":-\n";
indent(out) << " * " << tstruct->name() << "\n";
indent(out) << " */\n";
}
/**
* Generates the docstring for an enum.
*
* This is how the generated docstring looks like:-
*
* <Original docstring goes here>
*
* Original thrift enum:-
* Name
*/
void t_hack_generator::generate_php_docstring(
std::ofstream& out, const t_enum* tenum) {
indent(out) << "/**\n";
// Copy the doc.
if (tenum->has_doc()) {
generate_docstring_comment(
out, // out
"", // comment_start
" * ", // line_prefix
tenum->get_doc(), // contents
""); // comment_end
indent(out) << " * \n";
}
indent(out) << " * "
<< "Original thrift enum:-\n";
indent(out) << " * " << tenum->name() << "\n";
indent(out) << " */\n";
}
/**
* Generates the docstring for a service.
*
* This is how the generated docstring looks like:-
*
* <Original docstring goes here>
*
* Original thrift service:-
* Name
*/
void t_hack_generator::generate_php_docstring(
std::ofstream& out, const t_service* tservice) {
indent(out) << "/**\n";
// Copy the doc.
if (tservice->has_doc()) {
generate_docstring_comment(
out, // out
"", // comment_start
" * ", // line_prefix
tservice->get_doc(), // contents
""); // comment_end
indent(out) << " * \n";
}
indent(out) << " * "
<< "Original thrift service:-\n";
indent(out) << " * " << tservice->name() << "\n";
indent(out) << " */\n";
}
/**
* Generates the docstring for a constant.
*
* This is how the generated docstring looks like:-
*
* <Original docstring goes here>
*
* Original thrift constant:-
* TYPE NAME
*/
void t_hack_generator::generate_php_docstring(
std::ofstream& out, const t_const* tconst) {
indent(out) << "/**\n";
// Copy the doc.
if (tconst->has_doc()) {
generate_docstring_comment(
out, // out
"", // comment_start
" * ", // line_prefix
tconst->get_doc(), // contents
""); // comment_end
indent(out) << " * \n";
}
indent(out) << " * "
<< "Original thrift constant:-\n";
indent(out) << " * " << tconst->get_type()->get_full_name() << " "
<< tconst->name() << "\n";
// no value because it could have characters that mess up the comment
indent(out) << " */\n";
}
/**
* Generates the docstring for function arguments and exceptions.
*
* @param int start_pos the position (after " * ") from which the rendering of
* arguments should start. In other words, we put that many space after " * "
* and then render the argument.
*/
void t_hack_generator::generate_php_docstring_args(
std::ofstream& out, int start_pos, const t_struct* arg_list) {
if (arg_list) {
bool first = true;
for (const auto& param : arg_list->fields()) {
if (first) {
first = false;
} else {
out << ",\n" << indent() << " * " << std::string(start_pos, ' ');
}
out << param.id() << ": " << thrift_type_name(param.get_type()) << " "
<< param.name();
}
}
}
void t_hack_generator::generate_php_docstring_stream_exceptions(
std::ofstream& out, const t_throws* ex) {
// Exceptions.
if (t_throws::or_empty(ex)->has_fields()) {
out << ", throws (";
auto first = true;
for (const auto& param : ex->fields()) {
if (first) {
first = false;
} else {
out << ", ";
}
out << param.id() << ": " << thrift_type_name(param.get_type()) << " "
<< param.name();
}
out << ")";
}
}
/**
* Generate an appropriate string for a php typehint
*/
std::string t_hack_generator::type_to_typehint(
const t_type* ttype,
bool nullable,
bool shape,
bool immutable_collections,
bool ignore_adapter) {
if (!ignore_adapter) {
// Check the adapter before resolving typedefs.
if (const auto* adapter = find_hack_adapter(ttype)) {
return *adapter + "::THackType";
}
}
ttype = ttype->get_true_type();
immutable_collections = immutable_collections || const_collections_;
if (ttype->is_base_type()) {
switch (((t_base_type*)ttype)->get_base()) {
case t_base_type::TYPE_VOID:
return "void";
case t_base_type::TYPE_STRING:
case t_base_type::TYPE_BINARY:
return "string";
case t_base_type::TYPE_BOOL:
return "bool";
case t_base_type::TYPE_BYTE:
case t_base_type::TYPE_I16:
case t_base_type::TYPE_I32:
case t_base_type::TYPE_I64:
return "int";
case t_base_type::TYPE_DOUBLE:
case t_base_type::TYPE_FLOAT:
return "float";
default:
return "mixed";
}
} else if (const auto* tenum = dynamic_cast<const t_enum*>(ttype)) {
if (is_bitmask_enum(tenum)) {
return "int";
} else {
return (nullable ? "?" : "") + hack_name(ttype);
}
} else if (ttype->is_struct() || ttype->is_xception()) {
return (nullable ? "?" : "") + hack_name(ttype) + (shape ? "::TShape" : "");
} else if (const auto* tlist = dynamic_cast<const t_list*>(ttype)) {
std::string prefix;
if (arrays_) {
prefix = "vec";
} else if (no_use_hack_collections_) {
prefix = "varray";
} else if (shape) {
prefix = array_migration_ ? "varray" : "vec";
} else {
prefix = immutable_collections ? "\\ConstVector" : "Vector";
}
return prefix + "<" +
type_to_typehint(
tlist->get_elem_type(), false, shape, immutable_collections) +
">";
} else if (const auto* tmap = dynamic_cast<const t_map*>(ttype)) {
std::string prefix;
if (arrays_) {
prefix = "dict";
} else if (no_use_hack_collections_) {
prefix = "darray";
} else if (shape) {
prefix = array_keyword_;
} else {
prefix = immutable_collections ? "\\ConstMap" : "Map";
}
std::string key_type = type_to_typehint(
tmap->get_key_type(), false, shape, immutable_collections);
if (shape && shape_arraykeys_ && key_type == "string") {
key_type = "arraykey";
} else if (!is_type_arraykey(tmap->get_key_type())) {
key_type = "arraykey";
}
return prefix + "<" + key_type + ", " +
type_to_typehint(
tmap->get_val_type(), false, shape, immutable_collections) +
">";
} else if (const auto* tset = dynamic_cast<const t_set*>(ttype)) {
std::string prefix;
if (arraysets_) {
prefix = array_keyword_;
} else if (arrays_) {
prefix = "keyset";
} else if (shape) {
prefix = array_keyword_;
} else {
prefix = immutable_collections ? "\\ConstSet" : "Set";
}
std::string suffix = (arraysets_ || (shape && !arrays_)) ? ", bool>" : ">";
std::string key_type = !is_type_arraykey(tset->get_elem_type())
? "arraykey"
: type_to_typehint(
tset->get_elem_type(), false, shape, immutable_collections);
return prefix + "<" + key_type + suffix;
} else {
return "mixed";
}
}
std::string t_hack_generator::field_to_typehint(
const t_field& tfield,
const std::string& struct_class_name,
bool is_field_nullable,
bool is_type_nullable,
bool shape,
bool immutable_collections,
bool ignore_adapter) {
std::string typehint = type_to_typehint(
tfield.get_type(),
is_type_nullable,
shape,
immutable_collections,
ignore_adapter);
if (const auto* field_wrapper = find_hack_wrapper(tfield)) {
typehint = *field_wrapper + "<" + (is_field_nullable ? "?" : "") +
typehint + ", " + struct_class_name + ">";
}
return typehint;
}
std::string t_hack_generator::get_stream_function_return_typehint(
const t_stream_response* tstream) {
// Finally, the function declaration.
std::string return_typehint;
auto stream_response_type_hint =
type_to_typehint(tstream->get_elem_type()) + ">";
if (tstream->has_first_response()) {
auto first_response_type_hint =
type_to_typehint(tstream->get_first_response_type());
return_typehint = "\\ResponseAndClientStream<" + first_response_type_hint +
", " + stream_response_type_hint;
} else {
return_typehint =
"\\ResponseAndClientStream<void, " + stream_response_type_hint;
}
return return_typehint;
}
std::string t_hack_generator::get_sink_function_return_typehint(
const t_sink* tsink) {
// Finally, the function declaration.
std::string return_typehint = type_to_typehint(tsink->get_sink_type()) +
", " + type_to_typehint(tsink->get_final_response_type()) + ">";
if (tsink->sink_has_first_response()) {
auto first_response_type_hint =
type_to_typehint(tsink->get_first_response_type());
return "\\ResponseAndClientSink<" + first_response_type_hint + ", " +
return_typehint;
} else {
return "\\ResponseAndClientSink<void, " + return_typehint;
}
}
/**
* Generate an appropriate string for a parameter typehint.
* The difference from type_to_typehint() is for parameters we should accept
* an array or a collection type, so we return KeyedContainer
*/
std::string t_hack_generator::type_to_param_typehint(
const t_type* ttype, bool nullable) {
if (const auto* tlist = dynamic_cast<const t_list*>(ttype)) {
if (strict_types_) {
return type_to_typehint(ttype, nullable);
} else {
return "KeyedContainer<int, " +
type_to_param_typehint(tlist->get_elem_type()) + ">";
}
} else if (const auto* tmap = dynamic_cast<const t_map*>(ttype)) {
if (strict_types_) {
return type_to_typehint(ttype, nullable);
} else {
const auto* key_type = tmap->get_key_type();
return "KeyedContainer<" +
(!is_type_arraykey(key_type) ? "arraykey"
: type_to_param_typehint(key_type)) +
", " + type_to_param_typehint(tmap->get_val_type()) + ">";
}
} else {
return type_to_typehint(ttype, nullable);
}
}
bool t_hack_generator::is_type_arraykey(const t_type* type) {
type = type->get_true_type();
return type->is_string_or_binary() || type->is_any_int() || type->is_byte() ||
type->is_enum();
}
/**
* Generates a service interface definition.
*
* @param tservice The service to generate a header definition for
* @param mangle Generate mangled service classes
*/
void t_hack_generator::generate_service_interface(
const t_service* tservice, bool mangle, bool async, bool client) {
generate_php_docstring(f_service_, tservice);
std::string suffix =
std::string(async ? "Async" : "") + (client ? "Client" : "");
std::string extends_if =
std::string("\\IThrift") + (async ? "Async" : "Sync") + "If";
std::string long_name = php_servicename_mangle(mangle, tservice);
if (async && client) {
extends_if = long_name + "Async" + "If";
if (tservice->get_extends() != nullptr) {
std::string ext_prefix =
php_servicename_mangle(mangle, tservice->get_extends(), true);
extends_if = extends_if + ", " + ext_prefix + suffix + "If";
}
} else if (tservice->get_extends() != nullptr) {
std::string ext_prefix =
php_servicename_mangle(mangle, tservice->get_extends(), true);
extends_if = ext_prefix + suffix + "If";
}
f_service_ << "interface " << long_name << suffix << "If extends "
<< extends_if << " {\n";
indent_up();
auto delim = "";
auto functions = client ? get_supported_client_functions(tservice)
: get_supported_server_functions(tservice);
for (const auto* function : functions) {
if (async && client && !is_client_only_function(function)) {
continue;
}
// Add a blank line before the start of a new function definition
f_service_ << delim;
delim = "\n";
// Add the doxygen style comments.
generate_php_docstring(f_service_, function);
// Finally, the function declaration.
std::string return_typehint;
if (const auto* tstream = dynamic_cast<const t_stream_response*>(
function->get_returntype())) {
return_typehint = get_stream_function_return_typehint(tstream);
} else if (
const auto* tsink =
dynamic_cast<const t_sink*>(function->get_returntype())) {
return_typehint = get_sink_function_return_typehint(tsink);
} else {
return_typehint = type_to_typehint(function->get_returntype());
}
if (async || client) {
return_typehint = "Awaitable<" + return_typehint + ">";
}
if (nullable_everything_) {
const std::string& funname = function->name();
indent(f_service_) << "public function " << funname << "("
<< argument_list(
function->get_paramlist(), "", true, true)
<< "): " << return_typehint << ";\n";
} else {
indent(f_service_) << "public function "
<< function_signature(function, "", return_typehint)
<< ";\n";
}
}
indent_down();
f_service_ << "}\n\n";
}
void t_hack_generator::generate_service_client(
const t_service* tservice, bool mangle) {
_generate_service_client(f_service_, tservice, mangle);
}
/**
* Generates a service client definition.
*
* @param tservice The service to generate a server for.
*/
void t_hack_generator::_generate_service_client(
std::ofstream& out, const t_service* tservice, bool mangle) {
generate_php_docstring(out, tservice);
std::string long_name = php_servicename_mangle(mangle, tservice);
out << "trait " << long_name << "ClientBase {\n"
<< " require extends \\ThriftClientBase;\n\n";
indent_up();
// Generate client method implementations
for (const auto* function : get_supported_client_functions(tservice)) {
_generate_sendImpl(out, tservice, function);
if (function->qualifier() != t_function_qualifier::one_way) {
_generate_recvImpl(out, tservice, function);
}
out << "\n";
}
// Generate factory method for interactions
const std::vector<const t_service*>& interactions =
get_interactions(tservice);
if (!interactions.empty()) {
out << indent() << "/* interaction handlers factory methods */\n";
const std::string& service_name = tservice->name();
for (const auto& interaction : interactions) {
const std::string& handle_name = php_servicename_mangle(
mangle, interaction, service_name + "_" + interaction->name());
out << indent() << "public function create" << interaction->name()
<< "(): ";
out << handle_name << " {\n";
indent_up();
out << indent() << "$interaction = new " << handle_name
<< "($this->input_, $this->output_, $this->channel_);\n";
out << indent() << "$interaction->setAsyncHandler($this->asyncHandler_)"
<< "->setEventHandler($this->eventHandler_);\n";
out << indent() << "return $interaction;\n";
indent_down();
out << indent() << "}\n\n";
}
}
scope_down(out);
out << "\n";
_generate_service_client_children(out, tservice, mangle, /*async*/ true);
_generate_service_client_children(out, tservice, mangle, /*async*/ false);
}
void t_hack_generator::_generate_recvImpl(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction) {
auto ttype = tfunction->get_returntype();
std::string return_typehint;
std::string resultname;
std::string recvImpl_method_name;
bool is_void = false;
if (const auto* tstream = dynamic_cast<const t_stream_response*>(ttype)) {
_generate_stream_decode_recvImpl(out, tservice, tfunction);
resultname = generate_function_helper_name(
tservice, tfunction, PhpFunctionNameSuffix::FIRST_RESPONSE);
recvImpl_method_name =
std::string("recvImpl_") + tfunction->name() + "_FirstResponse";
if (tstream->has_first_response()) {
ttype = tstream->get_first_response_type();
return_typehint = type_to_typehint(ttype);
} else {
return_typehint = "void";
is_void = true;
}
} else if (const auto* tsink = dynamic_cast<const t_sink*>(ttype)) {
_generate_sink_final_response_decode_recvImpl(out, tservice, tfunction);
resultname = generate_function_helper_name(
tservice, tfunction, PhpFunctionNameSuffix::FIRST_RESPONSE);
recvImpl_method_name =
std::string("recvImpl_") + tfunction->name() + "_FirstResponse";
if (tsink->sink_has_first_response()) {
ttype = tsink->get_first_response_type();
return_typehint = type_to_typehint(ttype);
} else {
return_typehint = "void";
is_void = true;
}
} else {
resultname = generate_function_helper_name(
tservice, tfunction, PhpFunctionNameSuffix::RESULT);
recvImpl_method_name = std::string("recvImpl_") + tfunction->name();
return_typehint = type_to_typehint(ttype);
is_void = ttype->is_void();
}
const std::string& rpc_function_name =
generate_rpc_function_name(tservice, tfunction);
t_function recv_function(
tfunction->get_returntype(),
recvImpl_method_name,
std::make_unique<t_paramlist>(program_));
// Open function
out << "\n";
out << indent() << "protected function "
<< function_signature(
&recv_function,
"?int $expectedsequenceid = null"
", shape(?'read_options' => int) $options = shape()",
return_typehint)
<< " {\n";
indent_up();
out << indent() << "try {\n";
indent_up();
out << indent() << "$this->eventHandler_->preRecv('" << rpc_function_name
<< "', $expectedsequenceid);\n";
out << indent() << "if ($this->input_ is \\TBinaryProtocolAccelerated) {\n";
indent_up();
out << indent() << "$result = \\thrift_protocol_read_binary("
<< "$this->input_"
<< ", '" << resultname << "'"
<< ", $this->input_->isStrictRead()"
<< ", Shapes::idx($options, 'read_options', 0)"
<< ");\n";
indent_down();
out << indent()
<< "} else if ($this->input_ is \\TCompactProtocolAccelerated)\n";
scope_up(out);
out << indent() << "$result = \\thrift_protocol_read_compact("
<< "$this->input_"
<< ", '" << resultname << "'"
<< ", Shapes::idx($options, 'read_options', 0)"
<< ");\n";
scope_down(out);
out << indent() << "else\n";
scope_up(out);
out << indent() << "$rseqid = 0;\n"
<< indent() << "$fname = '';\n"
<< indent() << "$mtype = 0;\n\n";
out << indent() << "$this->input_->readMessageBegin(\n"
<< indent() << " inout $fname,\n"
<< indent() << " inout $mtype,\n"
<< indent() << " inout $rseqid,\n"
<< indent() << ");\n"
<< indent() << "if ($mtype === \\TMessageType::EXCEPTION) {\n"
<< indent() << " $x = new \\TApplicationException();\n"
<< indent() << " $x->read($this->input_);\n"
<< indent() << " $this->input_->readMessageEnd();\n"
<< indent() << " throw $x;\n"
<< indent() << "}\n";
out << indent() << "$result = " << resultname << "::withDefaultValues();\n"
<< indent() << "$result->read($this->input_);\n";
out << indent() << "$this->input_->readMessageEnd();\n";
out << indent()
<< "if ($expectedsequenceid !== null && ($rseqid !== $expectedsequenceid)) {\n"
<< indent() << " throw new \\TProtocolException(\"" << tfunction->name()
<< " failed: sequence id is out of order\");\n"
<< indent() << "}\n";
scope_down(out);
indent_down();
indent(out) << "} catch (\\THandlerShortCircuitException $ex) {\n";
indent_up();
out << indent() << "switch ($ex->resultType) {\n"
<< indent() << " case \\THandlerShortCircuitException::R_EXPECTED_EX:\n"
<< indent() << " $this->eventHandler_->recvException('"
<< rpc_function_name << "', $expectedsequenceid, $ex->result);\n"
<< indent() << " throw $ex->result;\n"
<< indent()
<< " case \\THandlerShortCircuitException::R_UNEXPECTED_EX:\n"
<< indent() << " $this->eventHandler_->recvError('"
<< rpc_function_name << "', $expectedsequenceid, $ex->result);\n"
<< indent() << " throw $ex->result;\n"
<< indent() << " case \\THandlerShortCircuitException::R_SUCCESS:\n"
<< indent() << " default:\n"
<< indent() << " $this->eventHandler_->postRecv('" << rpc_function_name
<< "', $expectedsequenceid, $ex->result);\n"
<< indent() << " return";
if (!is_void) {
out << " $ex->result";
}
out << ";\n" << indent() << "}\n";
indent_down();
out << indent() << "} catch (\\Exception $ex) {\n";
indent_up();
out << indent() << "$this->eventHandler_->recvError('" << rpc_function_name
<< "', $expectedsequenceid, $ex);\n"
<< indent() << "throw $ex;\n";
indent_down();
out << indent() << "}\n";
// Careful, only return result if not a void function
if (!is_void) {
out << indent() << "if ($result->success !== null) {\n"
<< indent() << " $success = $result->success;\n"
<< indent() << " $this->eventHandler_->postRecv('" << rpc_function_name
<< "', $expectedsequenceid, $success);"
<< "\n"
<< indent() << " return $success;\n"
<< indent() << "}\n";
}
for (const auto& x : tfunction->get_xceptions()->fields()) {
out << indent() << "if ($result->" << x.name() << " !== null) {\n"
<< indent() << " $x = $result->" << x.name() << ";"
<< "\n"
<< indent() << " $this->eventHandler_->recvException('"
<< rpc_function_name << "', $expectedsequenceid, $x);\n"
<< indent() << " throw $x;\n"
<< indent() << "}\n";
}
// Careful, only return _result if not a void function
if (is_void) {
out << indent() << "$this->eventHandler_->postRecv('" << rpc_function_name
<< "', $expectedsequenceid, null);\n"
<< indent() << "return;\n";
} else {
out << indent() << "$x = new \\TApplicationException(\""
<< tfunction->name() << " failed: unknown result\""
<< ", \\TApplicationException::MISSING_RESULT"
<< ");\n"
<< indent() << "$this->eventHandler_->recvError('" << rpc_function_name
<< "', $expectedsequenceid, $x);\n"
<< indent() << "throw $x;\n";
}
// Close function
scope_down(out);
}
void t_hack_generator::_generate_stream_decode_recvImpl(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction) {
const std::string& resultname = generate_function_helper_name(
tservice, tfunction, PhpFunctionNameSuffix::STREAM_RESPONSE);
t_function recv_function(
tfunction->get_returntype(),
std::string("recvImpl_") + tfunction->name() +
std::string("_StreamDecode"),
std::make_unique<t_paramlist>(program_));
const auto* tstream =
dynamic_cast<const t_stream_response*>(tfunction->get_returntype());
auto ttype = tstream->get_elem_type();
std::string return_typehint =
"(function(?string, ?\\Exception) : " + type_to_typehint(ttype) + ")";
// Open function
out << "\n";
out << indent() << "protected function "
<< function_signature(
&recv_function,
"shape(?'read_options' => int) $options = shape()",
return_typehint)
<< " {\n";
indent_up();
out << indent() << "$protocol = $this->input_;\n";
out << indent() << "return function(\n";
indent_up();
out << indent() << "?string $stream_payload, ?\\Exception $ex\n";
indent_down();
out << indent() << ") use (\n";
indent_up();
out << indent() << "$protocol,\n";
indent_down();
out << indent() << ") {\n";
indent_up();
out << indent() << "try {\n";
indent_up();
out << indent() << "if ($ex !== null) {\n";
indent_up();
out << indent() << "throw $ex;\n";
indent_down();
out << indent() << "}\n";
out << indent() << "$transport = $protocol->getTransport();\n";
out << indent() << "invariant(\n";
indent_up();
out << indent() << "$transport is \\TMemoryBuffer,\n"
<< indent() << "\"Stream methods require TMemoryBuffer transport\"\n";
indent_down();
out << indent() << ");\n\n";
out << indent() << "$transport->resetBuffer();\n"
<< indent() << "$transport->write($stream_payload as nonnull);\n";
out << indent() << "$result = " << resultname << "::withDefaultValues();\n"
<< indent() << "$result->read($protocol);\n";
out << indent() << "$protocol->readMessageEnd();\n";
indent_down();
indent(out) << "} catch (\\THandlerShortCircuitException $ex) {\n";
indent_up();
out << indent() << "throw $ex->result;\n";
indent_down();
out << indent() << "}\n";
out << indent() << "if ($result->success !== null) {\n"
<< indent() << " return $result->success;\n"
<< indent() << "}\n";
for (const auto& x : t_throws::or_empty(tstream->exceptions())->fields()) {
out << indent() << "if ($result->" << x.name() << " !== null) {\n"
<< indent() << " throw $result->" << x.name() << ";"
<< "\n"
<< indent() << "}\n";
}
out << indent() << "throw new \\TApplicationException(\"" << tfunction->name()
<< " failed: unknown result\""
<< ", \\TApplicationException::MISSING_RESULT"
<< ");\n";
// close decode function
indent_down();
out << indent() << "};\n";
// Close function
scope_down(out);
}
void t_hack_generator::_generate_sink_encode_sendImpl(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction) {
const std::string& resultname = generate_function_helper_name(
tservice, tfunction, PhpFunctionNameSuffix::SINK_PAYLOAD);
t_function recv_function(
tfunction->get_returntype(),
std::string("sendImpl_") + tfunction->name() + std::string("_SinkEncode"),
std::make_unique<t_paramlist>(program_));
const auto* tsink = dynamic_cast<const t_sink*>(tfunction->get_returntype());
auto ttype = tsink->get_sink_type();
auto typehint = type_to_typehint(ttype);
std::string return_typehint =
"(function(?" + typehint + ", ?\\Exception) : (string, bool))";
// Open function
out << "\n";
out << indent() << "protected function "
<< function_signature(&recv_function, "", return_typehint) << " {\n";
indent_up();
out << indent() << "$protocol = $this->output_;\n";
out << indent() << "return function(\n";
indent_up();
out << indent() << "?" << typehint << " $sink_payload, ?\\Exception $ex\n";
indent_down();
out << indent() << ") use (\n";
indent_up();
out << indent() << "$protocol,\n";
indent_down();
out << indent() << ") {\n\n";
indent_up();
out << indent() << "$transport = $protocol->getTransport();\n";
out << indent() << "invariant(\n";
indent_up();
out << indent() << "$transport is \\TMemoryBuffer,\n"
<< indent() << "\"Sink methods require TMemoryBuffer transport\"\n";
indent_down();
out << indent() << ");\n\n";
out << indent() << "$is_application_ex = false;\n\n";
out << indent() << "if ($ex !== null) {\n";
indent_up();
auto exceptions = tfunction->get_sink_xceptions();
indent(out);
if (exceptions != nullptr) {
for (const auto& ex : exceptions->fields()) {
out << "if ($ex is " << hack_name(ex.get_type()->get_true_type())
<< ") {\n";
indent_up();
out << indent() << "$result = " << resultname;
out << "::fromShape(shape(\n";
indent_up();
out << indent() << "'" << ex.name() << "' => $ex,\n";
indent_down();
out << indent() << "));\n";
indent_down();
out << indent() << "} else ";
}
}
out << "if ($ex is \\TApplicationException) {\n";
indent_up();
out << indent() << "$is_application_ex = true;\n"
<< indent() << "$result = $ex;\n";
indent_down();
out << indent() << "} else {\n";
indent_up();
out << indent()
<< "$result = new \\TApplicationException($ex->getMessage().\"\\n\".$ex->getTraceAsString());\n";
indent_down();
out << indent() << "}\n";
// close exception if
indent_down();
out << indent() << "} else {\n";
indent_up();
out << indent() << "$result = " << resultname << "::fromShape(shape(\n";
indent_up();
out << indent() << "'success'"
<< " => $sink_payload,\n";
indent_down();
out << indent() << "));\n";
// close result if-else
indent_down();
out << indent() << "}\n\n";
out << indent() << "$result->write($protocol);\n"
<< indent() << "$protocol->writeMessageEnd();\n"
<< indent() << "$transport->flush();\n"
<< indent() << "$msg = $transport->getBuffer();\n"
<< indent() << "$transport->resetBuffer();\n"
<< indent() << "return tuple($msg, $is_application_ex);\n";
// close encode function
indent_down();
out << indent() << "};\n";
// Close function
scope_down(out);
}
void t_hack_generator::_generate_sink_final_response_decode_recvImpl(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction) {
const std::string& resultname = generate_function_helper_name(
tservice, tfunction, PhpFunctionNameSuffix::SINK_FINAL_RESPONSE);
t_function recv_function(
tfunction->get_returntype(),
std::string("recvImpl_") + tfunction->name() +
std::string("_FinalResponse"),
std::make_unique<t_paramlist>(program_));
const auto* tsink = dynamic_cast<const t_sink*>(tfunction->get_returntype());
auto ttype = tsink->get_final_response_type();
std::string return_typehint =
"(function(?string, ?\\Exception) : " + type_to_typehint(ttype) + ")";
// Open function
out << "\n";
out << indent() << "protected function "
<< function_signature(&recv_function, "", return_typehint) << " {\n";
indent_up();
out << indent() << "$protocol = $this->input_;\n";
out << indent() << "return function(\n";
indent_up();
out << indent() << "?string $sink_final_response, ?\\Exception $ex\n";
indent_down();
out << indent() << ") use (\n";
indent_up();
out << indent() << "$protocol,\n";
indent_down();
out << indent() << ") {\n";
indent_up();
out << indent() << "try {\n";
indent_up();
out << indent() << "if ($ex !== null) {\n";
indent_up();
out << indent() << "throw $ex;\n";
indent_down();
out << indent() << "}\n";
out << indent() << "$transport = $protocol->getTransport();\n";
out << indent() << "invariant(\n";
indent_up();
out << indent() << "$transport is \\TMemoryBuffer,\n"
<< indent() << "\"Stream methods require TMemoryBuffer transport\"\n";
indent_down();
out << indent() << ");\n\n";
out << indent() << "$transport->resetBuffer();\n"
<< indent() << "$transport->write($sink_final_response as nonnull);\n";
out << indent() << "$result = " << resultname << "::withDefaultValues();\n"
<< indent() << "$result->read($protocol);\n";
out << indent() << "$protocol->readMessageEnd();\n";
indent_down();
indent(out) << "} catch (\\THandlerShortCircuitException $ex) {\n";
indent_up();
out << indent() << "throw $ex->result;\n";
indent_down();
out << indent() << "}\n";
out << indent() << "if ($result->success !== null) {\n"
<< indent() << " return $result->success;\n"
<< indent() << "}\n";
for (const auto& x :
t_throws::or_empty(tsink->get_final_response_xceptions())->fields()) {
out << indent() << "if ($result->" << x.name() << " !== null) {\n"
<< indent() << " throw $result->" << x.name() << ";"
<< "\n"
<< indent() << "}\n";
}
out << indent() << "throw new \\TApplicationException(\"" << tfunction->name()
<< " failed: unknown result\""
<< ", \\TApplicationException::MISSING_RESULT"
<< ");\n";
// close decode function
indent_down();
out << indent() << "};\n";
// Close function
scope_down(out);
}
void t_hack_generator::_generate_sendImpl(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction) {
const t_struct* arg_struct = tfunction->get_paramlist();
const auto& fields = arg_struct->fields();
const std::string& funname = tfunction->name();
const std::string& rpc_function_name =
generate_rpc_function_name(tservice, tfunction);
if (nullable_everything_) {
indent(out) << "protected function sendImpl_" << funname << "("
<< argument_list(tfunction->get_paramlist(), "", true, true)
<< "): int {\n";
} else {
indent(out) << "protected function sendImpl_"
<< function_signature(tfunction, "", "int") << " {\n";
}
indent_up();
const std::string& argsname = generate_function_helper_name(
tservice, tfunction, PhpFunctionNameSuffix::ARGS);
out << indent() << "$currentseqid = $this->getNextSequenceID();\n"
<< indent() << "$args = " << argsname;
if (!fields.empty()) {
out << "::fromShape(shape(\n";
indent_up();
// Loop through the fields and assign to the args struct
for (auto&& field : fields) {
indent(out);
std::string name = "$" + field.name();
out << "'" << field.name() << "' => ";
if (nullable_everything_) {
// just passthrough null
out << name << " === null ? null : ";
}
t_name_generator namer;
this->_generate_sendImpl_arg(out, namer, name, field.get_type());
out << ",\n";
}
indent_down();
indent(out) << "));\n";
} else {
out << "::withDefaultValues();\n";
}
out << indent() << "try {\n";
indent_up();
out << indent() << "$this->eventHandler_->preSend('" << rpc_function_name
<< "', $args, $currentseqid);\n";
out << indent() << "if ($this->output_ is \\TBinaryProtocolAccelerated)\n";
scope_up(out);
out << indent() << "\\thrift_protocol_write_binary($this->output_, '"
<< rpc_function_name << "', "
<< "\\TMessageType::CALL, $args, $currentseqid, "
<< "$this->output_->isStrictWrite(), "
<< (tfunction->qualifier() == t_function_qualifier::one_way ? "true"
: "false")
<< ");\n";
scope_down(out);
out << indent()
<< "else if ($this->output_ is \\TCompactProtocolAccelerated)\n";
scope_up(out);
out << indent() << "\\thrift_protocol_write_compact($this->output_, '"
<< rpc_function_name << "', "
<< "\\TMessageType::CALL, $args, $currentseqid, "
<< (tfunction->qualifier() == t_function_qualifier::one_way ? "true"
: "false")
<< ");\n";
scope_down(out);
out << indent() << "else\n";
scope_up(out);
// Serialize the request header
out << indent() << "$this->output_->writeMessageBegin('" << rpc_function_name
<< "', \\TMessageType::CALL, $currentseqid);\n";
// Write to the stream
out << indent() << "$args->write($this->output_);\n"
<< indent() << "$this->output_->writeMessageEnd();\n";
if (tfunction->qualifier() == t_function_qualifier::one_way) {
out << indent() << "$this->output_->getTransport()->onewayFlush();\n";
} else {
out << indent() << "$this->output_->getTransport()->flush();\n";
}
scope_down(out);
indent_down();
indent(out) << "} catch (\\THandlerShortCircuitException $ex) {\n";
indent_up();
out << indent() << "switch ($ex->resultType) {\n"
<< indent() << " case \\THandlerShortCircuitException::R_EXPECTED_EX:\n"
<< indent()
<< " case \\THandlerShortCircuitException::R_UNEXPECTED_EX:\n"
<< indent() << " $this->eventHandler_->sendError('"
<< rpc_function_name << "', $args, $currentseqid, $ex->result);"
<< "\n"
<< indent() << " throw $ex->result;\n"
<< indent() << " case \\THandlerShortCircuitException::R_SUCCESS:\n"
<< indent() << " default:\n"
<< indent() << " $this->eventHandler_->postSend('" << rpc_function_name
<< "', $args, $currentseqid);\n"
<< indent() << " return $currentseqid;\n"
<< indent() << "}\n";
indent_down();
indent(out) << "} catch (\\Exception $ex) {\n";
indent_up();
out << indent() << "$this->eventHandler_->sendError('" << rpc_function_name
<< "', $args, $currentseqid, $ex);\n"
<< indent() << "throw $ex;\n";
indent_down();
indent(out) << "}\n";
out << indent() << "$this->eventHandler_->postSend('" << rpc_function_name
<< "', $args, $currentseqid);\n";
indent(out) << "return $currentseqid;\n";
scope_down(out);
if (tfunction->returns_sink()) {
_generate_sink_encode_sendImpl(out, tservice, tfunction);
}
}
// If !strict_types, containers are typehinted as KeyedContainer<Key, Value>
// to better support passing in arrays/dicts/maps/vecs/vectors and
// handle backwards compatibility. However, structs are typehinted as
// the actual container (ex: Map<Key, Val>), and we need to safely
// convert the typehints.
//
// This isn't as simple as dict($param) or new Map($param). If there is
// a nested container, that also needs to have its typehints converted.
// This iterates through the type object and generates the appropriate
// code to convert all the nested typehints.
void t_hack_generator::_generate_sendImpl_arg(
std::ofstream& out,
t_name_generator& namer,
const std::string& var,
const t_type* t) {
const t_type* val_type;
if (strict_types_ || !t->is_container()) {
out << var;
return;
}
if (const auto* tmap = dynamic_cast<const t_map*>(t)) {
val_type = tmap->get_val_type();
} else if (const auto* tlist = dynamic_cast<const t_list*>(t)) {
val_type = tlist->get_elem_type();
} else if (const auto* tset = dynamic_cast<const t_set*>(t)) {
val_type = tset->get_elem_type();
} else {
throw std::runtime_error("Unknown container type");
}
val_type = val_type->get_true_type();
if (val_type->is_container() && !val_type->is_set()) {
if (t->is_map()) {
if (arrays_ || no_use_hack_collections_) {
out << "Dict\\map(" << var << ", ";
} else {
out << "(new Map(" << var << "))->map(";
}
} else if (t->is_list()) {
if (arrays_ || no_use_hack_collections_) {
out << "Vec\\map(" << var << ", ";
} else {
out << "(new Vector(" << var << "))->map(";
}
} else if (t->is_set()) {
throw std::runtime_error("Sets can't have nested containers");
} else {
throw std::runtime_error("Unknown container type");
}
indent_up();
out << "\n" << indent();
// Update var to what it will be next, since we no longer need the old
// value.
std::string new_var = "$" + namer("_val");
out << new_var << " ==> ";
this->_generate_sendImpl_arg(out, namer, new_var, val_type);
indent_down();
out << "\n" << indent();
out << ")";
} else {
// the parens around the collections are unnecessary but I'm leaving them
// so that I don't end up changing literally all files
if (t->is_map()) {
if (arrays_ || no_use_hack_collections_) {
out << "dict(" << var << ")";
} else {
out << "new Map(" << var << ")";
}
} else if (t->is_list()) {
if (arrays_ || no_use_hack_collections_) {
out << "vec(" << var << ")";
} else {
out << "new Vector(" << var << ")";
}
} else if (t->is_set()) {
out << var;
} else {
throw std::runtime_error("Unknown container type");
}
}
}
void t_hack_generator::_generate_service_client_children(
std::ofstream& out, const t_service* tservice, bool mangle, bool async) {
std::string long_name = php_servicename_mangle(mangle, tservice);
std::string class_suffix = std::string(async ? "Async" : "");
std::string interface_suffix = std::string(async ? "AsyncClient" : "Client");
std::string extends = "\\ThriftClientBase";
bool root = tservice->get_extends() == nullptr;
if (!root) {
extends = php_servicename_mangle(mangle, tservice->get_extends(), true) +
class_suffix + "Client";
}
out << "class " << long_name << class_suffix << "Client extends " << extends
<< " implements " << long_name << interface_suffix << "If {\n"
<< " use " << long_name << "ClientBase;\n\n";
indent_up();
// Generate functions as necessary.
for (const auto* function : get_supported_client_functions(tservice)) {
_generate_service_client_child_fn(out, tservice, function);
if (no_use_hack_collections_) {
_generate_service_client_child_fn(
out, tservice, function, /*legacy_arrays*/ true);
}
}
if (!async) {
out << indent() << "/* send and recv functions */\n";
for (const auto* function : get_supported_server_functions(tservice)) {
const std::string& funname = function->name();
std::string return_typehint =
type_to_typehint(function->get_returntype());
out << indent() << "public function send_"
<< function_signature(function, "", "int") << " {\n"
<< indent() << " return $this->sendImpl_" << funname << "(";
auto delim = "";
for (const auto& param : function->get_paramlist()->fields()) {
out << delim << "$" << param.name();
delim = ", ";
}
out << ");\n" << indent() << "}\n";
if (function->qualifier() != t_function_qualifier::one_way) {
t_function recv_function(
function->get_returntype(),
std::string("recv_") + function->name(),
std::make_unique<t_paramlist>(program_));
// Open function
bool is_void = function->get_returntype()->is_void();
out << indent() << "public function "
<< function_signature(
&recv_function,
"?int $expectedsequenceid = null",
return_typehint)
<< " {\n"
<< indent() << " " << (is_void ? "" : "return ")
<< "$this->recvImpl_" << funname << "($expectedsequenceid);\n"
<< indent() << "}\n";
}
}
}
indent_down();
out << "}\n\n";
}
void t_hack_generator::_generate_service_client_child_fn(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction,
bool legacy_arrays) {
if (tfunction->returns_stream()) {
_generate_service_client_stream_child_fn(
out, tservice, tfunction, legacy_arrays);
return;
}
if (tfunction->returns_sink()) {
_generate_service_client_sink_child_fn(
out, tservice, tfunction, legacy_arrays);
return;
}
std::string funname =
tfunction->name() + (legacy_arrays ? "__LEGACY_ARRAYS" : "");
const std::string& tservice_name =
(tservice->is_interaction() ? service_name_ : tservice->name());
std::string return_typehint = type_to_typehint(tfunction->get_returntype());
generate_php_docstring(out, tfunction);
indent(out) << "public async function " << funname << "("
<< argument_list(
tfunction->get_paramlist(), "", true, nullable_everything_)
<< "): Awaitable<" + return_typehint + "> {\n";
indent_up();
indent(out) << "$hh_frame_metadata = $this->getHHFrameMetadata();\n";
indent(out) << "if ($hh_frame_metadata !== null) {\n";
indent_up();
indent(out) << "\\HH\\set_frame_metadata($hh_frame_metadata);\n";
indent_down();
indent(out) << "}\n";
indent(out) << "$rpc_options = $this->getAndResetOptions() ?? "
<< (tservice->is_interaction()
? "new RpcOptions()"
: "\\ThriftClientBase::defaultOptions()")
<< ";\n";
if (tservice->is_interaction()) {
indent(out)
<< "$rpc_options = $rpc_options->setInteractionId($this->interactionId);\n";
}
indent(out) << "await $this->asyncHandler_->genBefore(\"" << tservice_name
<< "\", \"" << generate_rpc_function_name(tservice, tfunction)
<< "\");\n";
indent(out) << "$currentseqid = $this->sendImpl_" << tfunction->name() << "(";
auto delim = "";
for (const auto& param : tfunction->get_paramlist()->fields()) {
out << delim << "$" << param.name();
delim = ", ";
}
out << ");\n";
if (tfunction->qualifier() != t_function_qualifier::one_way) {
out << indent() << "$channel = $this->channel_;\n"
<< indent() << "$out_transport = $this->output_->getTransport();\n"
<< indent() << "$in_transport = $this->input_->getTransport();\n"
<< indent()
<< "if ($channel !== null && $out_transport is \\TMemoryBuffer && $in_transport is \\TMemoryBuffer) {\n";
indent_up();
out << indent() << "$msg = $out_transport->getBuffer();\n"
<< indent() << "$out_transport->resetBuffer();\n"
<< indent() << "list($result_msg, $_read_headers) = "
<< "await $channel->genSendRequestResponse($rpc_options, $msg);\n"
<< indent() << "$in_transport->resetBuffer();\n"
<< indent() << "$in_transport->write($result_msg);\n";
indent_down();
indent(out) << "} else {\n";
indent_up();
indent(out) << "await $this->asyncHandler_->genWait($currentseqid);\n";
scope_down(out);
out << indent();
if (!tfunction->get_returntype()->is_void()) {
out << "$response = ";
}
out << "$this->recvImpl_" << tfunction->name() << "("
<< "$currentseqid";
if (legacy_arrays) {
out << ", shape('read_options' => THRIFT_MARK_LEGACY_ARRAYS)";
}
out << ");\n";
indent(out) << "await $this->asyncHandler_->genAfter();\n";
if (!tfunction->get_returntype()->is_void()) {
indent(out) << "return $response;\n";
}
} else {
out << indent() << "$channel = $this->channel_;\n"
<< indent() << "$out_transport = $this->output_->getTransport();\n"
<< indent()
<< "if ($channel !== null && $out_transport is \\TMemoryBuffer) {\n";
indent_up();
out << indent() << "$msg = $out_transport->getBuffer();\n"
<< indent() << "$out_transport->resetBuffer();\n"
<< indent()
<< "await $channel->genSendRequestNoResponse($rpc_options, $msg);\n";
scope_down(out);
}
scope_down(out);
out << "\n";
}
void t_hack_generator::_generate_service_client_stream_child_fn(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction,
bool legacy_arrays) {
std::string funname =
tfunction->name() + (legacy_arrays ? "__LEGACY_ARRAYS" : "");
const std::string& tservice_name =
(tservice->is_interaction() ? service_name_ : tservice->name());
std::string return_typehint = get_stream_function_return_typehint(
dynamic_cast<const t_stream_response*>(tfunction->get_returntype()));
generate_php_docstring(out, tfunction);
indent(out) << "public async function " << funname << "("
<< argument_list(
tfunction->get_paramlist(), "", true, nullable_everything_)
<< "): Awaitable<" + return_typehint + "> {\n";
indent_up();
indent(out) << "$hh_frame_metadata = $this->getHHFrameMetadata();\n";
indent(out) << "if ($hh_frame_metadata !== null) {\n";
indent_up();
indent(out) << "\\HH\\set_frame_metadata($hh_frame_metadata);\n";
indent_down();
indent(out) << "}\n";
indent(out) << "$rpc_options = $this->getAndResetOptions() ?? "
<< (tservice->is_interaction()
? "new RpcOptions()"
: "\\ThriftClientBase::defaultOptions()")
<< ";\n";
if (tservice->is_interaction()) {
indent(out)
<< "$rpc_options = $rpc_options->setInteractionId($this->interactionId);\n";
}
indent(out) << "$channel = $this->channel_;\n";
indent(out) << "$out_transport = $this->output_->getTransport();\n";
indent(out) << "$in_transport = $this->input_->getTransport();\n";
indent(out) << "invariant(\n";
indent_up();
indent(out)
<< "$channel !== null && $out_transport is \\TMemoryBuffer && $in_transport is \\TMemoryBuffer,\n";
indent(out)
<< "\"Stream methods require nonnull channel and TMemoryBuffer transport\"\n";
indent_down();
indent(out) << ");\n\n";
indent(out) << "await $this->asyncHandler_->genBefore(\"" << tservice_name
<< "\", \"" << generate_rpc_function_name(tservice, tfunction)
<< "\");\n";
indent(out) << "$currentseqid = $this->sendImpl_" << tfunction->name() << "(";
auto delim = "";
for (const auto& param : tfunction->get_paramlist()->fields()) {
out << delim << "$" << param.name();
delim = ", ";
}
out << ");\n";
out << indent() << "$msg = $out_transport->getBuffer();\n"
<< indent() << "$out_transport->resetBuffer();\n"
<< indent() << "list($result_msg, $_read_headers, $stream) = "
<< "await $channel->genSendRequestStreamResponse($rpc_options, $msg);\n\n";
const auto* tstream =
dynamic_cast<const t_stream_response*>(tfunction->get_returntype());
out << indent() << "$stream_gen = $stream->gen<"
<< type_to_typehint(tstream->get_elem_type()) << ">($this->recvImpl_"
<< tfunction->name() << "_StreamDecode(";
if (legacy_arrays) {
out << "shape('read_options' => THRIFT_MARK_LEGACY_ARRAYS)";
}
out << "));\n";
out << indent() << "$in_transport->resetBuffer();\n"
<< indent() << "$in_transport->write($result_msg);\n"
<< indent();
if (tstream->has_first_response()) {
out << "$first_response = ";
}
out << "$this->recvImpl_" << tfunction->name() << "_FirstResponse"
<< "("
<< "$currentseqid";
if (legacy_arrays) {
out << ", shape('read_options' => THRIFT_MARK_LEGACY_ARRAYS)";
}
out << ");\n";
indent(out) << "await $this->asyncHandler_->genAfter();\n";
if (tstream->has_first_response()) {
auto first_response_typehint =
type_to_typehint(tstream->get_first_response_type());
out << indent() << "return new " << return_typehint
<< "($first_response, $stream_gen);\n";
} else {
out << indent() << "return new " << return_typehint
<< "(null, $stream_gen);\n";
}
scope_down(out);
out << "\n";
}
void t_hack_generator::_generate_service_client_sink_child_fn(
std::ofstream& out,
const t_service* tservice,
const t_function* tfunction,
bool legacy_arrays) {
std::string funname =
tfunction->name() + (legacy_arrays ? "__LEGACY_ARRAYS" : "");
const std::string& tservice_name =
(tservice->is_interaction() ? service_name_ : tservice->name());
const auto* tsink = dynamic_cast<const t_sink*>(tfunction->get_returntype());
std::string return_typehint = get_sink_function_return_typehint(tsink);
generate_php_docstring(out, tfunction);
indent(out) << "public async function " << funname << "("
<< argument_list(
tfunction->get_paramlist(), "", true, nullable_everything_)
<< "): Awaitable<" + return_typehint + "> {\n";
indent_up();
indent(out) << "$hh_frame_metadata = $this->getHHFrameMetadata();\n";
indent(out) << "if ($hh_frame_metadata !== null) {\n";
indent_up();
indent(out) << "\\HH\\set_frame_metadata($hh_frame_metadata);\n";
indent_down();
indent(out) << "}\n";
indent(out) << "$rpc_options = $this->getAndResetOptions() ?? "
<< (tservice->is_interaction()
? "new RpcOptions()"
: "\\ThriftClientBase::defaultOptions()")
<< ";\n";
if (tservice->is_interaction()) {
out << "$rpc_options->setInteractionId($this->interactionId);\n";
}
indent(out) << "$channel = $this->channel_;\n";
indent(out) << "$out_transport = $this->output_->getTransport();\n";
indent(out) << "$in_transport = $this->input_->getTransport();\n";
indent(out) << "invariant(\n";
indent_up();
indent(out)
<< "$channel !== null && $out_transport is \\TMemoryBuffer && $in_transport is \\TMemoryBuffer,\n";
indent(out)
<< "\"Sink methods require nonnull channel and TMemoryBuffer transport\"\n";
indent_down();
indent(out) << ");\n\n";
indent(out) << "await $this->asyncHandler_->genBefore(\"" << tservice_name
<< "\", \"" << generate_rpc_function_name(tservice, tfunction)
<< "\");\n";
indent(out) << "$currentseqid = $this->sendImpl_" << tfunction->name() << "(";
auto delim = "";
for (const auto& param : tfunction->get_paramlist()->fields()) {
out << delim << "$" << param.name();
delim = ", ";
}
out << ");\n";
out << indent() << "$msg = $out_transport->getBuffer();\n"
<< indent() << "$out_transport->resetBuffer();\n"
<< indent() << "list($result_msg, $_read_headers, $sink) = "
<< "await $channel->genSendRequestSink($rpc_options, $msg);\n\n";
out << indent() << "$payload_serializer = $this->sendImpl_"
<< tfunction->name() << "_SinkEncode();\n";
out << indent() << "$final_response_deserializer = $this->recvImpl_"
<< tfunction->name() << "_FinalResponse();\n";
auto sink_type_hint = type_to_typehint(tsink->get_sink_type());
auto final_fesponse_type_hint =
type_to_typehint(tsink->get_final_response_type());
out << indent() << "$client_sink_func = async function(\n"
<< indent() << " AsyncGenerator<null, " << sink_type_hint
<< ", void> $pld_generator\n"
<< indent()
<< ") use ($sink, $payload_serializer, $final_response_deserializer) {\n";
indent_up();
out << indent() << "return await $sink->genSink<" << sink_type_hint << ", "
<< final_fesponse_type_hint << ">(\n";
indent_up();
out << indent() << "$pld_generator, \n"
<< indent() << "$payload_serializer, \n"
<< indent() << "$final_response_deserializer, \n";
indent_down();
out << indent() << ");\n";
indent_down();
out << indent() << "};\n\n";
out << indent() << "$in_transport->resetBuffer();\n"
<< indent() << "$in_transport->write($result_msg);\n"
<< indent();
if (tsink->sink_has_first_response()) {
out << "$first_response = ";
}
out << "$this->recvImpl_" << tfunction->name() << "_FirstResponse"
<< "("
<< "$currentseqid";
if (legacy_arrays) {
out << ", shape('read_options' => THRIFT_MARK_LEGACY_ARRAYS)";
}
out << ");\n\n";
indent(out) << "await $this->asyncHandler_->genAfter();\n";
if (tsink->sink_has_first_response()) {
auto first_response_typehint =
type_to_typehint(tsink->get_first_response_type());
out << indent() << "return new " << return_typehint
<< "($first_response, $client_sink_func);\n";
} else {
out << indent() << "return new " << return_typehint
<< "(null, $client_sink_func);\n";
}
scope_down(out);
out << "\n";
}
/**
* Declares a field, which may include initialization as necessary.
*
* @param ttype The type
* @param init iff initialize the field
* @param obj iff the field is an object
* @param thrift iff the object is a thrift object
*/
std::string t_hack_generator::declare_field(
const t_field* tfield, bool init, bool obj, bool /*thrift*/) {
std::string result = "$" + tfield->name();
if (init) {
const t_type* type = tfield->get_type()->get_true_type();
if (const auto* tbase_type = dynamic_cast<const t_base_type*>(type)) {
switch (tbase_type->get_base()) {
case t_base_type::TYPE_VOID:
break;
case t_base_type::TYPE_STRING:
case t_base_type::TYPE_BINARY:
result += " = ''";
break;
case t_base_type::TYPE_BOOL:
result += " = false";
break;
case t_base_type::TYPE_BYTE:
case t_base_type::TYPE_I16:
case t_base_type::TYPE_I32:
case t_base_type::TYPE_I64:
result += " = 0";
break;
case t_base_type::TYPE_DOUBLE:
case t_base_type::TYPE_FLOAT:
result += " = 0.0";
break;
default:
throw std::runtime_error(
"compiler error: no Hack initializer for base type " +
t_base_type::t_base_name(tbase_type->get_base()));
}
} else if (type->is_enum()) {
result += " = null";
} else if (type->is_map()) {
if (arrays_ || no_use_hack_collections_) {
result += " = dict[]";
} else {
result += " = Map {}";
}
} else if (type->is_list()) {
if (arrays_ || no_use_hack_collections_) {
result += " = vec[]";
} else {
result += " = Vector {}";
}
} else if (type->is_set()) {
if (arrays_) {
result += " = keyset[]";
} else if (arraysets_) {
result += " = dict[]";
} else {
result += " = Set {}";
}
} else if (type->is_struct() || type->is_xception()) {
if (obj) {
result += " = " + hack_name(type) + "::withDefaultValues()";
} else {
result += " = null";
}
}
}
return result + ";";
}
/**
* Renders a function signature of the form 'type name(args)'
*
* @param tfunction Function definition
* @return String of rendered function definition
*/
std::string t_hack_generator::function_signature(
const t_function* tfunction,
std::string more_tail_parameters,
std::string typehint) {
if (typehint.empty()) {
typehint = type_to_typehint(tfunction->get_returntype());
}
return tfunction->name() + "(" +
argument_list(tfunction->get_paramlist(), more_tail_parameters) +
"): " + typehint;
}
/**
* Renders a field list
*/
std::string t_hack_generator::argument_list(
const t_struct* tstruct,
std::string more_tail_parameters,
bool typehints,
bool force_nullable) {
std::string result = "";
auto delim = "";
for (const auto& field : tstruct->fields()) {
result += delim;
delim = ", ";
if (typehints) {
// If a field is not sent to a thrift server, the value is null :(
const t_type* ftype = field.type()->get_true_type();
bool nullable = !no_nullables_ ||
(ftype->is_enum() &&
(field.default_value() == nullptr ||
field.get_req() != t_field::e_req::required));
if (force_nullable &&
!field_is_nullable(tstruct, &field, render_default_value(ftype))) {
result += "?";
}
result += type_to_param_typehint(field.get_type(), nullable) + " ";
}
result += "$" + field.name();
}
if (more_tail_parameters.length() > 0) {
result += delim;
result += more_tail_parameters;
}
return result;
}
/**
* Renders the function name to be used in RPC
*/
std::string t_hack_generator::generate_rpc_function_name(
const t_service* tservice, const t_function* tfunction) const {
const std::string& prefix =
tservice->is_interaction() ? tservice->name() + "." : "";
return prefix + tfunction->name();
}
/**
* Generate function's helper structures name
* @param suffix defines the suffix
*/
std::string t_hack_generator::generate_function_helper_name(
const t_service* tservice,
const t_function* tfunction,
PhpFunctionNameSuffix suffix) {
std::string prefix = "";
if (tservice->is_interaction()) {
prefix = hack_name(service_name_, program_) + "_" + tservice->name();
} else {
prefix = hack_name(tservice);
}
switch (suffix) {
case PhpFunctionNameSuffix::ARGS:
return prefix + "_" + tfunction->name() + "_args";
case PhpFunctionNameSuffix::RESULT:
return prefix + "_" + tfunction->name() + "_result";
case PhpFunctionNameSuffix::STREAM_RESPONSE:
return prefix + "_" + tfunction->name() + "_StreamResponse";
case PhpFunctionNameSuffix::FIRST_RESPONSE:
return prefix + "_" + tfunction->name() + "_FirstResponse";
case PhpFunctionNameSuffix::SINK_PAYLOAD:
return prefix + "_" + tfunction->name() + "_SinkPayload";
case PhpFunctionNameSuffix::SINK_FINAL_RESPONSE:
return prefix + "_" + tfunction->name() + "_FinalResponse";
default:
throw std::runtime_error("Invalid php function name suffix");
}
}
/**
* Gets a typecast string for a particular type.
*/
std::string t_hack_generator::type_to_cast(const t_type* type) {
if (const auto* tbase_type = dynamic_cast<const t_base_type*>(type)) {
switch (tbase_type->get_base()) {
case t_base_type::TYPE_BOOL:
return "(bool)";
case t_base_type::TYPE_BYTE:
case t_base_type::TYPE_I16:
case t_base_type::TYPE_I32:
case t_base_type::TYPE_I64:
return "(int)";
case t_base_type::TYPE_DOUBLE:
case t_base_type::TYPE_FLOAT:
return "(float)";
case t_base_type::TYPE_STRING:
case t_base_type::TYPE_BINARY:
return "(string)";
default:
return "";
}
} else if (type->is_enum()) {
return "";
}
return "";
}
/**
* Converts the parse type to a C++ enum string for the given type.
*/
std::string t_hack_generator::type_to_enum(const t_type* type) {
type = type->get_true_type();
if (const auto* tbase_type = dynamic_cast<const t_base_type*>(type)) {
switch (tbase_type->get_base()) {
case t_base_type::TYPE_VOID:
throw std::runtime_error("NO T_VOID CONSTRUCT");
case t_base_type::TYPE_STRING:
case t_base_type::TYPE_BINARY:
return "\\TType::STRING";
case t_base_type::TYPE_BOOL:
return "\\TType::BOOL";
case t_base_type::TYPE_BYTE:
return "\\TType::BYTE";
case t_base_type::TYPE_I16:
return "\\TType::I16";
case t_base_type::TYPE_I32:
return "\\TType::I32";
case t_base_type::TYPE_I64:
return "\\TType::I64";
case t_base_type::TYPE_DOUBLE:
return "\\TType::DOUBLE";
case t_base_type::TYPE_FLOAT:
return "\\TType::FLOAT";
}
} else if (type->is_enum()) {
return "\\TType::I32";
} else if (type->is_struct() || type->is_xception()) {
return "\\TType::STRUCT";
} else if (type->is_map()) {
return "\\TType::MAP";
} else if (type->is_set()) {
return "\\TType::SET";
} else if (type->is_list()) {
return "\\TType::LST";
}
throw std::runtime_error("INVALID TYPE IN type_to_enum: " + type->name());
}
THRIFT_REGISTER_GENERATOR(
hack,
"HACK",
" server: Generate Hack server stubs.\n"
" rest: Generate Hack REST processors.\n"
" json: Generate functions to parse JSON into thrift struct.\n"
" mangledsvcs Generate services with namespace mangling.\n"
" stricttypes Use Collection classes everywhere rather than KeyedContainer.\n"
" arraysets Use legacy arrays for sets rather than objects.\n"
" nonullables Instantiate struct fields within structs, rather than nullable\n"
" structtrait Add 'use [StructName]Trait;' to generated classes\n"
" shapes Generate Shape definitions for structs\n"
" protected_unions Generate protected members for thrift unions\n"
" shape_arraykeys When generating Shape definition for structs:\n"
" replace array<string, TValue> with array<arraykey, TValue>\n"
" shapes_allow_unknown_fields Allow unknown fields and implicit subtyping for shapes \n"
" frommap_construct Generate fromMap_DEPRECATED method.\n"
" arrays Use Hack arrays for maps/lists/sets instead of objects.\n"
" const_collections Use ConstCollection objects rather than their mutable counterparts.\n"
" typedef Generate type aliases for all the types defined\n"
" enum_transparenttype Use transparent typing for Hack enums: 'enum FooBar: int as int'.\n");
} // namespace compiler
} // namespace thrift
} // namespace apache