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