Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

THRIFT-2429 #93

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
100 changes: 100 additions & 0 deletions compiler/cpp/src/generate/t_cpp_generator.cc
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,20 @@ using std::vector;

static const string endl = "\n"; // avoid ostream << std::endl flushes

namespace {


enum TerseWrites { TW_DISABLED = 0, TW_SAFE = 1, TW_ALL = 2 };

template <class Options>
TerseWrites parseTerseWrites(const Options& options) {
auto iter = options.find("terse_writes");
return (iter == options.end()) ? TW_DISABLED :
(iter->second == "safe") ? TW_SAFE : TW_ALL;
}

} // namespace

/**
* C++ code generator. This is legitimacy incarnate.
*
Expand Down Expand Up @@ -81,6 +95,8 @@ class t_cpp_generator : public t_oop_generator {
gen_templates_only_ =
(iter != parsed_options.end() && iter->second == "only");

terse_writes_ = parseTerseWrites(parsed_options);

out_dir_base_ = "gen-cpp";
}

Expand Down Expand Up @@ -217,6 +233,8 @@ class t_cpp_generator : public t_oop_generator {
std::string argument_list(t_struct* tstruct, bool name_params=true, bool start_comma=false);
std::string type_to_enum(t_type* ttype);
std::string local_reflection_name(const char*, t_type* ttype, bool external=false);
bool try_terse_write_predicate(ofstream& out, t_field* t, bool pointers, TerseWrites terse_writes,
string& predicate);

void generate_enum_constant_list(std::ofstream& f,
const vector<t_enum_value*>& constants,
Expand Down Expand Up @@ -291,6 +309,15 @@ class t_cpp_generator : public t_oop_generator {
*/
bool gen_no_default_operators_;

/*
* Should write function avoid emitting values that are unchanged
* from default, if not explicitly optional/required?
* Caveats, when whitelisted:
* - don't change default values when turned on.
* - read() should be done into fresh or __clear()ed objects.
*/
TerseWrites terse_writes_;

/**
* Strings for namespace, computed once up front then used directly
*/
Expand Down Expand Up @@ -1379,6 +1406,70 @@ void t_cpp_generator::generate_struct_reader(ofstream& out,
"}" << endl << endl;
}

/**
* Generates a terse write predicate - checks if the value
* has changed from its initial value.
*/
bool t_cpp_generator::try_terse_write_predicate(
ofstream& out, t_field* tfield, bool pointers, TerseWrites terse_writes,
string& predicate) {
if (terse_writes == TW_DISABLED) {
return false;
}

// Only do terse writes for fields where required/optional isn't specified.
if (tfield->get_req() == t_field::T_REQUIRED ||
tfield->get_req() == t_field::T_OPTIONAL) {
return false;
}
t_type* type = get_true_type(tfield->get_type());
t_const_value* tval = tfield->get_value();

// Terse write is unsafe to use without explicitly setting default value,
// as in PHP / Python that would change result of deserialization (comparing
// with the case when terse_writes is not used): field set in C++ to default
// value would be deserialized as null / None.
if (terse_writes == TW_SAFE && tval == nullptr) {
return false;
}

if (type->is_struct() || type->is_xception() ||
// no support for void.
(type->is_base_type() && ((t_base_type*)type)->is_void()) ||
// only support string, if default empty.
(type->is_base_type() && ((t_base_type*)type)->is_string() &&
tval != nullptr && !tval->get_string().empty()) ||
// only support container, if default empty.
(type->is_container() && tval != nullptr &&
((tval->get_type() == t_const_value::CV_LIST &&
!tval->get_list().empty()) ||
(tval->get_type() == t_const_value::CV_MAP &&
!tval->get_map().empty())))
) {
return false;
}

// Containers -> "if (!x.empty())"
if (type->is_container() ||
(type->is_base_type() && ((t_base_type*)type)->is_string())) {
predicate = "!this->" + tfield->get_name() +
(pointers ? "->empty()" : ".empty()");
return true;
}
// ints, enum -> "if (x != default value)
if (type->is_base_type() || type->is_enum()) {
std::string const_value = "0";
if (tval != NULL) {
const_value = render_const_value(out, "", type, tval);
}
predicate = (pointers ? "*(this->" : "this->") +
tfield->get_name() + (pointers ? ") != " : " != ") +
const_value;
return true;
}
return false;
}

/**
* Generates the write function.
*
Expand All @@ -1391,6 +1482,9 @@ void t_cpp_generator::generate_struct_writer(ofstream& out,
string name = tstruct->get_name();
const vector<t_field*>& fields = tstruct->get_sorted_members();
vector<t_field*>::const_iterator f_iter;
string predicate;
const TerseWrites terse_writes =
std::max(terse_writes_, parseTerseWrites(tstruct->annotations_));

if (gen_templates_) {
out <<
Expand All @@ -1416,6 +1510,11 @@ void t_cpp_generator::generate_struct_writer(ofstream& out,
if (check_if_set) {
out << endl << indent() << "if (this->__isset." << (*f_iter)->get_name() << ") {" << endl;
indent_up();
} else if (try_terse_write_predicate(out, *f_iter, pointers, terse_writes,
predicate)) {
indent(out) << "if (" << predicate << ") {" << endl;
indent_up();
check_if_set = true;
} else {
out << endl;
}
Expand Down Expand Up @@ -4682,5 +4781,6 @@ THRIFT_REGISTER_GENERATOR(cpp, "C++",
" pure_enums: Generate pure enums instead of wrapper classes.\n"
" dense: Generate type specifications for the dense protocol.\n"
" include_prefix: Use full include paths in generated files.\n"
" terse_writes: Suppress writes for fields holding default values.\n"
)

Loading