diff options
author | Jens Geyer <jensg@apache.org> | 2022-01-31 18:04:35 +0100 |
---|---|---|
committer | Jens Geyer <jensg@apache.org> | 2022-02-08 17:45:10 +0100 |
commit | 3cac3204519bbdfe02beb9d863e9b873cdaf9d07 (patch) | |
tree | 384c20a68441e257f07b69d3ee26a019ec43196f | |
parent | 2d667f34767b32d242687b5e31f65284f82ce16d (diff) | |
download | thrift-3cac3204519bbdfe02beb9d863e9b873cdaf9d07.tar.gz |
THRIFT-5511 Full support for the new net6 "nullability" semantics
Client: netstd
Patch: Jens Geyer
This closes #2516
16 files changed, 713 insertions, 403 deletions
diff --git a/compiler/cpp/src/thrift/generate/t_netstd_generator.cc b/compiler/cpp/src/thrift/generate/t_netstd_generator.cc index bfe3c0727..d3f3a1b2f 100644 --- a/compiler/cpp/src/thrift/generate/t_netstd_generator.cc +++ b/compiler/cpp/src/thrift/generate/t_netstd_generator.cc @@ -51,6 +51,7 @@ t_netstd_generator::t_netstd_generator(t_program* program, const map<string, str : t_oop_generator(program) { (void)option_string; + use_net6_features = false; suppress_deepcopy = false; add_async_postfix = false; use_pascal_case_properties = false; @@ -81,6 +82,9 @@ t_netstd_generator::t_netstd_generator(t_program* program, const map<string, str else if (iter->first.compare("no_deepcopy") == 0) { suppress_deepcopy = true; } + else if (iter->first.compare("net6") == 0) { + use_net6_features = true; + } else if (iter->first.compare("async_postfix") == 0) { add_async_postfix = true; } @@ -159,6 +163,7 @@ void t_netstd_generator::init_generator() pverbose("- serialize ....... %s\n", (is_serialize_enabled() ? "ON" : "off")); pverbose("- wcf ............. %s\n", (is_wcf_enabled() ? "ON" : "off")); pverbose("- pascal .......... %s\n", (use_pascal_case_properties ? "ON" : "off")); + pverbose("- net6 ............ %s\n", (use_net6_features ? "ON" : "off")); pverbose("- no_deepcopy ..... %s\n", (suppress_deepcopy ? "ON" : "off")); pverbose("- async_postfix ... %s\n", (add_async_postfix ? "ON" : "off")); } @@ -179,7 +184,7 @@ string t_netstd_generator::normalize_name(string name, bool is_arg_name) { return "@" + name; } - + // no changes necessary return name; } @@ -302,14 +307,31 @@ void t_netstd_generator::reset_indent() { } -void t_netstd_generator::start_netstd_namespace(ostream& out) +void t_netstd_generator::pragmas_and_directives(ostream& out) { - out << "#nullable disable // suppress C# 8.0 nullable contexts (we still support earlier versions)" << endl - << "#pragma warning disable IDE0079 // remove unnecessary pragmas" << endl + if( use_net6_features) { + out << "#nullable enable // requires C# 8.0" << endl; + } else { + out << "#nullable disable // suppress C# 8.0 nullable contexts (we still support earlier versions)" << endl; + } + + // this one must be first + out << "#pragma warning disable IDE0079 // remove unnecessary pragmas" << endl; + + out << "#pragma warning disable IDE0017 // object init can be simplified" << endl + << "#pragma warning disable IDE0028 // collection init can be simplified" << endl << "#pragma warning disable IDE1006 // parts of the code use IDL spelling" << endl - << "#pragma warning disable IDE0083 // pattern matching \"that is not SomeType\" requires net5.0 but we still support earlier versions" << endl - << endl; + << "#pragma warning disable CA1822 // empty " << DEEP_COPY_METHOD_NAME << "() methods still non-static" << endl; + + if( ! use_net6_features) { + out << "#pragma warning disable IDE0083 // pattern matching \"that is not SomeType\" requires net5.0 but we still support earlier versions" << endl; + } + out << endl; +} + +void t_netstd_generator::start_netstd_namespace(ostream& out) +{ if (!namespace_name_.empty()) { out << "namespace " << namespace_name_ << endl; @@ -346,7 +368,7 @@ string t_netstd_generator::netstd_type_usings() const namespaces += "using System.Runtime.Serialization;\n"; } - return namespaces + endl; + return namespaces; } string t_netstd_generator::netstd_thrift_usings() const @@ -360,7 +382,7 @@ string t_netstd_generator::netstd_thrift_usings() const "using Thrift.Transport.Server;\n" "using Thrift.Processor;\n"; - return namespaces + endl; + return namespaces; } void t_netstd_generator::close_generator() @@ -393,7 +415,8 @@ void t_netstd_generator::generate_enum(ostream& out, t_enum* tenum) reset_indent(); out << autogen_comment() << endl; - start_netstd_namespace(out); + pragmas_and_directives(out); + start_netstd_namespace(out); generate_netstd_doc(out, tenum); out << indent() << "public enum " << type_name(tenum,false) << endl; @@ -437,8 +460,9 @@ void t_netstd_generator::generate_consts(ostream& out, vector<t_const*> consts) } reset_indent(); - out << autogen_comment() << netstd_type_usings() << endl; + out << autogen_comment() << netstd_type_usings() << endl << endl; + pragmas_and_directives(out); start_netstd_namespace(out); out << indent() << "public static class " << make_valid_csharp_identifier(program_name_) << "Constants" << endl; @@ -680,7 +704,7 @@ void t_netstd_generator::collect_extensions_types(t_type* ttype) return; } - if (ttype->is_map() || ttype->is_set() || ttype->is_list()) + if (ttype->is_container()) { if( collected_extension_types.find(key) == collected_extension_types.end()) { @@ -702,6 +726,10 @@ void t_netstd_generator::collect_extensions_types(t_type* ttype) t_list* tlist = static_cast<t_list*>(ttype); collect_extensions_types(tlist->get_elem_type()); } + else + { + throw "compiler error: unhandled container type " + ttype->get_name(); + } } return; @@ -732,8 +760,11 @@ void t_netstd_generator::generate_extensions(ostream& out, map<string, t_type*> } reset_indent(); - out << autogen_comment() << netstd_type_usings() << endl; + out << autogen_comment() << netstd_type_usings() + << "using Thrift.Protocol;" << endl + << endl << endl; + pragmas_and_directives(out); start_netstd_namespace(out); out << indent() << "public static class " << make_valid_csharp_identifier(program_name_) << "Extensions" << endl; @@ -745,7 +776,11 @@ void t_netstd_generator::generate_extensions(ostream& out, map<string, t_type*> { out << indent() << "public static bool Equals(this " << iter->first << " instance, object that)" << endl; scope_up(out); - out << indent() << "if (!(that is " << iter->first << " other)) return false;" << endl; + if( use_net6_features) { + out << indent() << "if (that is not " << iter->first << " other) return false;" << endl; + } else { + out << indent() << "if (!(that is " << iter->first << " other)) return false;" << endl; + } out << indent() << "if (ReferenceEquals(instance, other)) return true;" << endl; out << endl; out << indent() << "return TCollections.Equals(instance, other);" << endl; @@ -759,38 +794,42 @@ void t_netstd_generator::generate_extensions(ostream& out, map<string, t_type*> out << endl << endl; if(! suppress_deepcopy) { - out << indent() << "public static " << iter->first << " " << DEEP_COPY_METHOD_NAME << "(this " << iter->first << " source)" << endl; + out << indent() << "public static " << iter->first << nullable_field_suffix(iter->second) << " " << DEEP_COPY_METHOD_NAME << "(this " << iter->first << nullable_field_suffix(iter->second) << " source)" << endl; scope_up(out); out << indent() << "if (source == null)" << endl; indent_up(); out << indent() << "return null;" << endl << endl; indent_down(); + string suffix(""); string tmp_instance = tmp("tmp"); out << indent() << "var " << tmp_instance << " = new " << iter->first << "(source.Count);" << endl; if( iter->second->is_map()) { t_map* tmap = static_cast<t_map*>(iter->second); - string copy_key = get_deep_copy_method_call(tmap->get_key_type(), needs_typecast); - string copy_val = get_deep_copy_method_call(tmap->get_val_type(), needs_typecast); + string copy_key = get_deep_copy_method_call(tmap->get_key_type(), true, needs_typecast, suffix); + string copy_val = get_deep_copy_method_call(tmap->get_val_type(), true, needs_typecast, suffix); bool null_key = type_can_be_null(tmap->get_key_type()); bool null_val = type_can_be_null(tmap->get_val_type()); out << indent() << "foreach (var pair in source)" << endl; indent_up(); - out << indent() << tmp_instance << ".Add("; - if( null_key) - { - out << "(pair.Key != null) ? pair.Key" << copy_key << " : null"; + if( use_net6_features) { + out << indent() << tmp_instance << ".Add(pair.Key" << copy_key; + out << ", pair.Value" << copy_val; } else { - out << "pair.Key" << copy_key; - } - out << ", "; - if( null_val) - { - out << "(pair.Value != null) ? pair.Value" << copy_val << " : null"; - } else { - out << "pair.Value" << copy_val; + out << indent() << tmp_instance << ".Add("; + if( null_key) { + out << "(pair.Key != null) ? pair.Key" << copy_key << " : null"; + } else { + out << "pair.Key" << copy_key; + } + out << ", "; + if( null_val) { + out << "(pair.Value != null) ? pair.Value" << copy_val << " : null"; + } else { + out << "pair.Value" << copy_val; + } } out << ");" << endl; indent_down(); @@ -801,24 +840,28 @@ void t_netstd_generator::generate_extensions(ostream& out, map<string, t_type*> if (iter->second->is_set()) { t_set* tset = static_cast<t_set*>(iter->second); - copy_elm = get_deep_copy_method_call(tset->get_elem_type(), needs_typecast); + copy_elm = get_deep_copy_method_call(tset->get_elem_type(), true, needs_typecast, suffix); null_elm = type_can_be_null(tset->get_elem_type()); } else // list { t_list* tlist = static_cast<t_list*>(iter->second); - copy_elm = get_deep_copy_method_call(tlist->get_elem_type(), needs_typecast); + copy_elm = get_deep_copy_method_call(tlist->get_elem_type(), true, needs_typecast, suffix); null_elm = type_can_be_null(tlist->get_elem_type()); } out << indent() << "foreach (var elem in source)" << endl; indent_up(); - out << indent() << tmp_instance << ".Add("; - if( null_elm) - { - out << "(elem != null) ? elem" << copy_elm << " : null"; + if( use_net6_features) { + out << indent() << tmp_instance << ".Add(elem" << copy_elm; } else { - out << "elem" << copy_elm; + out << indent() << tmp_instance << ".Add("; + if( null_elm) + { + out << "(elem != null) ? elem" << copy_elm << " : null"; + } else { + out << "elem" << copy_elm; + } } out << ");" << endl; indent_down(); @@ -864,8 +907,9 @@ void t_netstd_generator::generate_netstd_struct(t_struct* tstruct, bool is_excep f_struct.open(f_struct_name.c_str()); reset_indent(); - f_struct << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl; + f_struct << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl << endl; + pragmas_and_directives(f_struct); generate_netstd_struct_definition(f_struct, tstruct, is_exception); f_struct.close(); @@ -915,7 +959,7 @@ void t_netstd_generator::generate_netstd_struct_definition(ostream& out, t_struc // if the field is required, then we use auto-properties if (!field_is_required((*m_iter))) { - out << indent() << "private " << declare_field(*m_iter, false, "_") << endl; + out << indent() << "private " << declare_field(*m_iter, false, true, "_") << endl; } } out << endl; @@ -930,7 +974,7 @@ void t_netstd_generator::generate_netstd_struct_definition(ostream& out, t_struc if (is_required) { has_required_fields = true; - } + } else { has_non_required_fields = true; @@ -1041,7 +1085,7 @@ void t_netstd_generator::generate_netstd_struct_definition(ostream& out, t_struc { out << ", "; } - out << type_name((*m_iter)->get_type()) << " " << normalize_name((*m_iter)->get_name()); + out << type_name((*m_iter)->get_type()) << nullable_field_suffix(*m_iter) << " " << normalize_name((*m_iter)->get_name()); } } out << ") : this()" << endl @@ -1113,7 +1157,7 @@ void t_netstd_generator::generate_netstd_wcffault(ostream& out, t_struct* tstruc // if the field is required, then we use auto-properties if (!field_is_required((*m_iter))) { - out << indent() << "private " << declare_field(*m_iter, false, "_") << endl; + out << indent() << "private " << declare_field(*m_iter, false, true, "_") << endl; } } out << endl; @@ -1146,8 +1190,9 @@ void t_netstd_generator::generate_netstd_deepcopy_method(ostream& out, t_struct* for (m_iter = members.begin(); m_iter != members.end(); ++m_iter) { bool needs_typecast = false; + string suffix(""); t_type* ttype = (*m_iter)->get_type(); - string copy_op = get_deep_copy_method_call(ttype, needs_typecast); + string copy_op = get_deep_copy_method_call(ttype, true, needs_typecast, suffix); bool is_required = field_is_required(*m_iter); generate_null_check_begin( out, *m_iter); @@ -1511,8 +1556,9 @@ void t_netstd_generator::generate_netstd_union(t_struct* tunion) f_union.open(f_union_name.c_str()); reset_indent(); - f_union << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl; + f_union << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl << endl; + pragmas_and_directives(f_union); generate_netstd_union_definition(f_union, tunion); f_union.close(); @@ -1531,7 +1577,7 @@ void t_netstd_generator::generate_netstd_union_definition(ostream& out, t_struct out << indent() << "public abstract global::System.Threading.Tasks.Task WriteAsync(TProtocol tProtocol, CancellationToken " << CANCELLATION_TOKEN_NAME << ");" << endl << indent() << "public readonly int Isset;" << endl - << indent() << "public abstract object Data { get; }" << endl + << indent() << "public abstract object" << nullable_suffix() <<" Data { get; }" << endl << indent() << "protected " << tunion->get_name() << "(int isset)" << endl << indent() << "{" << endl; indent_up(); @@ -1542,78 +1588,136 @@ void t_netstd_generator::generate_netstd_union_definition(ostream& out, t_struct const vector<t_field*>& fields = tunion->get_members(); vector<t_field*>::const_iterator f_iter; - out << indent() << "public override bool Equals(object that)" << endl; + out << indent() << "public override bool Equals(object" << nullable_suffix() << " that)" << endl; scope_up(out); - out << indent() << "if (!(that is " << tunion->get_name() << " other)) return false;" << endl; + if( use_net6_features) { + out << indent() << "if (that is not " << tunion->get_name() << " other) return false;" << endl; + } else { + out << indent() << "if (!(that is " << tunion->get_name() << " other)) return false;" << endl; + } out << indent() << "if (ReferenceEquals(this, other)) return true;" << endl; out << endl; out << indent() << "if(this.Isset != other.Isset) return false;" << endl; out << endl; - out << indent() << "switch (Isset)" << endl; - scope_up(out); - for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) - { - bool needs_typecast = false; - string copy_op = get_deep_copy_method_call((*f_iter)->get_type(), needs_typecast); - out << indent() << "case " << (*f_iter)->get_key() << ":" << endl; + if(use_net6_features) { + out << indent() << "return Isset switch" << endl; + scope_up(out); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + bool needs_typecast = false; + string suffix(""); + get_deep_copy_method_call((*f_iter)->get_type(), false, needs_typecast, suffix); + out << indent() << (*f_iter)->get_key() << " => Equals(As_" << (*f_iter)->get_name() << ", other.As_" << (*f_iter)->get_name() << ")," << endl; + } + out << indent() << "_ => true," << endl; + indent_down(); + out << indent() << "};" << endl; + } else { + out << indent() << "switch (Isset)" << endl; + scope_up(out); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + bool needs_typecast = false; + string suffix(""); + get_deep_copy_method_call((*f_iter)->get_type(), false, needs_typecast, suffix); + out << indent() << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + out << indent() << "return Equals(As_" << (*f_iter)->get_name() << ", other.As_" << (*f_iter)->get_name() << ");" << endl; + indent_down(); + } + out << indent() << "default:" << endl; indent_up(); - out << indent() << "return Equals(As_" << (*f_iter)->get_name() << ", other.As_" << (*f_iter)->get_name() << ");" << endl; + out << indent() << "return true;" << endl; indent_down(); + scope_down(out); } - out << indent() << "default:" << endl; - indent_up(); - out << indent() << "return true;" << endl; - indent_down(); - scope_down(out); scope_down(out); out << endl; out << indent() << "public override int GetHashCode()" << endl; out << indent() << "{" << endl; indent_up(); - out << indent() << "switch (Isset)" << endl; - out << indent() << "{" << endl; - indent_up(); - for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) - { - bool needs_typecast = false; - string copy_op = get_deep_copy_method_call((*f_iter)->get_type(), needs_typecast); - out << indent() << "case " << (*f_iter)->get_key() << ":" << endl; - indent_up(); - out << indent() << "return As_" << (*f_iter)->get_name() << ".GetHashCode();" << endl; - indent_down(); - } - out << indent() << "default:" << endl; - indent_up(); - out << indent() << "return (new ___undefined()).GetHashCode();" << endl; - indent_down(); - indent_down(); - out << indent() << "}" << endl; - indent_down(); - out << indent() << "}" << endl << endl; - - if( ! suppress_deepcopy) { - out << indent() << "public " << tunion->get_name() << " DeepCopy()" << endl; + if(use_net6_features) { + out << indent() << "return Isset switch" << endl; out << indent() << "{" << endl; indent_up(); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + string null_coalesce(is_nullable_type((*f_iter)->get_type()) ? "?" : ""); + out << indent() << (*f_iter)->get_key() << " => As_" << (*f_iter)->get_name() << null_coalesce << ".GetHashCode()"; + if( null_coalesce.size() > 0) { + out << " ?? 0"; + } + out << "," << endl; + } + out << indent() << "_ => (new ___undefined()).GetHashCode()" << endl; + indent_down(); + out << indent() << "};" << endl; + } else { out << indent() << "switch (Isset)" << endl; out << indent() << "{" << endl; indent_up(); for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) { - bool needs_typecast = false; - string copy_op = get_deep_copy_method_call((*f_iter)->get_type(), needs_typecast); + string null_coalesce(is_nullable_type((*f_iter)->get_type()) ? "?" : ""); out << indent() << "case " << (*f_iter)->get_key() << ":" << endl; indent_up(); - out << indent() << "return new " << (*f_iter)->get_name() << "(As_" << (*f_iter)->get_name() << copy_op << ");" << endl; + out << indent() << "return As_" << (*f_iter)->get_name() << null_coalesce << ".GetHashCode()"; + if( null_coalesce.size() > 0) { + out << " ?? 0"; + } + out << ";" << endl; indent_down(); } out << indent() << "default:" << endl; indent_up(); - out << indent() << "return new ___undefined();" << endl; + out << indent() << "return (new ___undefined()).GetHashCode();" << endl; indent_down(); indent_down(); out << indent() << "}" << endl; + } + indent_down(); + out << indent() << "}" << endl << endl; + + if( ! suppress_deepcopy) { + out << indent() << "public " << tunion->get_name() << " " << DEEP_COPY_METHOD_NAME << "()" << endl; + out << indent() << "{" << endl; + indent_up(); + if(use_net6_features) { + out << indent() << "return Isset switch" << endl; + out << indent() << "{" << endl; + indent_up(); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + bool needs_typecast = false; + string suffix(""); + string copy_op = get_deep_copy_method_call((*f_iter)->get_type(), false, needs_typecast, suffix); + out << indent() << (*f_iter)->get_key() << " => new " << (*f_iter)->get_name() << "(As_" << (*f_iter)->get_name() << suffix << copy_op << ")," << endl; + } + out << indent() << "_ => new ___undefined()" << endl; + indent_down(); + out << indent() << "};" << endl; + } else { + out << indent() << "switch (Isset)" << endl; + out << indent() << "{" << endl; + indent_up(); + for (f_iter = fields.begin(); f_iter != fields.end(); ++f_iter) + { + bool needs_typecast = false; + string suffix(""); + string copy_op = get_deep_copy_method_call((*f_iter)->get_type(), false, needs_typecast, suffix); + out << indent() << "case " << (*f_iter)->get_key() << ":" << endl; + indent_up(); + out << indent() << "return new " << (*f_iter)->get_name() << "(As_" << (*f_iter)->get_name() << suffix << copy_op << ");" << endl; + indent_down(); + } + out << indent() << "default:" << endl; + indent_up(); + out << indent() << "return new ___undefined();" << endl; + indent_down(); + indent_down(); + out << indent() << "}" << endl; + } indent_down(); out << indent() << "}" << endl << endl; } @@ -1622,11 +1726,11 @@ void t_netstd_generator::generate_netstd_union_definition(ostream& out, t_struct out << indent() << "{" << endl; indent_up(); - out << indent() << "public override object Data { get { return null; } }" << endl + out << indent() << "public override object" << nullable_suffix() <<" Data { get { return null; } }" << endl << indent() << "public ___undefined() : base(0) {}" << endl << endl; if( ! suppress_deepcopy) { - out << indent() << "public new ___undefined DeepCopy()" << endl; + out << indent() << "public new ___undefined " << DEEP_COPY_METHOD_NAME << "()" << endl; out << indent() << "{" << endl; indent_up(); out << indent() << "return new ___undefined();" << endl; @@ -1662,13 +1766,17 @@ void t_netstd_generator::generate_netstd_union_definition(ostream& out, t_struct void t_netstd_generator::generate_netstd_union_class(ostream& out, t_struct* tunion, t_field* tfield) { - out << indent() << "public " << type_name(tfield->get_type()) << " As_" << tfield->get_name() << endl; + out << indent() << "public " << type_name(tfield->get_type()) << nullable_field_suffix(tfield) << " As_" << tfield->get_name() << endl; out << indent() << "{" << endl; indent_up(); out << indent() << "get" << endl; out << indent() << "{" << endl; indent_up(); - out << indent() << "return (" << tfield->get_key() << " == Isset) ? (" << type_name(tfield->get_type()) << ")Data : default(" << type_name(tfield->get_type()) << ");" << endl; + out << indent() << "return (" << tfield->get_key() << " == Isset) && (Data != null)" + << " ? (" << type_name(tfield->get_type()) << nullable_field_suffix(tfield) << ")Data" + << " : default" + << (use_net6_features ? "" : ("(" + type_name(tfield->get_type()) + ")")) + << ";" << endl; indent_down(); out << indent() << "}" << endl; indent_down(); @@ -1680,8 +1788,8 @@ void t_netstd_generator::generate_netstd_union_class(ostream& out, t_struct* tun out << indent() << "{" << endl; indent_up(); - out << indent() << "private " << type_name(tfield->get_type()) << " _data;" << endl - << indent() << "public override object Data { get { return _data; } }" << endl + out << indent() << "private readonly " << type_name(tfield->get_type()) << " _data;" << endl + << indent() << "public override object" << nullable_suffix() <<" Data { get { return _data; } }" << endl << indent() << "public " << tfield->get_name() << "(" << type_name(tfield->get_type()) << " data) : base("<< tfield->get_key() <<")" << endl << indent() << "{" << endl; indent_up(); @@ -1690,20 +1798,25 @@ void t_netstd_generator::generate_netstd_union_class(ostream& out, t_struct* tun out << indent() << "}" << endl; if( ! suppress_deepcopy) { - out << indent() << "public new " << tfield->get_name() << " DeepCopy()" << endl; + out << indent() << "public new " << tfield->get_name() << " " << DEEP_COPY_METHOD_NAME << "()" << endl; out << indent() << "{" << endl; indent_up(); bool needs_typecast = false; - string copy_op = get_deep_copy_method_call(tfield->get_type(), needs_typecast); + string suffix(""); + string copy_op = get_deep_copy_method_call(tfield->get_type(), true, needs_typecast, suffix); out << indent() << "return new " << tfield->get_name() << "(_data" << copy_op << ");" << endl; indent_down(); out << indent() << "}" << endl << endl; } - out << indent() << "public override bool Equals(object that)" << endl; + out << indent() << "public override bool Equals(object" << nullable_suffix() << " that)" << endl; out << indent() << "{" << endl; indent_up(); - out << indent() << "if (!(that is " << tunion->get_name() << " other)) return false;" << endl; + if(use_net6_features) { + out << indent() << "if (that is not " << tunion->get_name() << " other) return false;" << endl; + } else { + out << indent() << "if (!(that is " << tunion->get_name() << " other)) return false;" << endl; + } out << indent() << "if (ReferenceEquals(this, other)) return true;" << endl; out << endl; out << indent() << "return Equals( _data, other.As_" << tfield->get_name() << ");" << endl; @@ -1734,7 +1847,7 @@ void t_netstd_generator::generate_netstd_union_class(ostream& out, t_struct* tun << indent() << "field.ID = " << tfield->get_key() << ";" << endl << indent() << "await oprot.WriteFieldBeginAsync(field, " << CANCELLATION_TOKEN_NAME << ");" << endl; - generate_serialize_field(out, tfield, "_data", true); + generate_serialize_field(out, tfield, "_data", true, false); out << indent() << "await oprot.WriteFieldEndAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl << indent() << "await oprot.WriteFieldStopAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl @@ -1755,11 +1868,15 @@ void t_netstd_generator::generate_netstd_union_class(ostream& out, t_struct* tun void t_netstd_generator::generate_netstd_struct_equals(ostream& out, t_struct* tstruct) { - out << indent() << "public override bool Equals(object that)" << endl + out << indent() << "public override bool Equals(object" << nullable_suffix() << " that)" << endl << indent() << "{" << endl; indent_up(); - out << indent() << "if (!(that is " << type_name(tstruct,false) << " other)) return false;" << endl - << indent() << "if (ReferenceEquals(this, other)) return true;" << endl; + if(use_net6_features) { + out << indent() << "if (that is not " << type_name(tstruct,false) << " other) return false;" << endl; + } else { + out << indent() << "if (!(that is " << type_name(tstruct,false) << " other)) return false;" << endl; + } + out << indent() << "if (ReferenceEquals(this, other)) return true;" << endl; const vector<t_field*>& fields = tstruct->get_members(); @@ -1837,7 +1954,7 @@ void t_netstd_generator::generate_netstd_struct_hashcode(ostream& out, t_struct* out << "TCollections.GetHashCode(" << prop_name((*f_iter)) << ")"; } else { - out << prop_name((*f_iter)) << ".GetHashCode()"; + out << prop_name(*f_iter) << ".GetHashCode()"; } out << ";" << endl; @@ -1861,8 +1978,9 @@ void t_netstd_generator::generate_service(t_service* tservice) f_service.open(f_service_name.c_str()); reset_indent(); - f_service << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl; + f_service << autogen_comment() << netstd_type_usings() << netstd_thrift_usings() << endl << endl; + pragmas_and_directives(f_service); start_netstd_namespace(f_service); f_service << indent() << "public partial class " << normalize_name(service_name_) << endl @@ -2105,7 +2223,8 @@ void t_netstd_generator::generate_service_client(ostream& out, t_service* tservi out << indent() << "if (" << tmpvar << ".__isset.success)" << endl << indent() << "{" << endl; indent_up(); - out << indent() << "return " << tmpvar << ".Success;" << endl; + string nullable_value = nullable_value_access((*functions_iterator)->get_returntype()); + out << indent() << "return " << tmpvar << ".Success" << nullable_value << ";" << endl; indent_down(); out << indent() << "}" << endl; } @@ -2117,7 +2236,7 @@ void t_netstd_generator::generate_service_client(ostream& out, t_service* tservi out << indent() << "if (" << tmpvar << ".__isset." << get_isset_name(normalize_name((*x_iter)->get_name())) << ")" << endl << indent() << "{" << endl; indent_up(); - out << indent() << "throw " << tmpvar << "." << prop_name(*x_iter) << ";" << endl; + out << indent() << "throw " << tmpvar << "." << prop_name(*x_iter) << nullable_value_access((*x_iter)->get_type()) << ";" << endl; indent_down(); out << indent() << "}" << endl; } @@ -2161,9 +2280,9 @@ void t_netstd_generator::generate_service_server(ostream& out, t_service* tservi indent_up(); out << indent() << "private readonly IAsync _iAsync;" << endl - << indent() << "private readonly ILogger<AsyncProcessor> _logger;" << endl + << indent() << "private readonly ILogger<AsyncProcessor>" << nullable_suffix() << " _logger;" << endl << endl - << indent() << "public AsyncProcessor(IAsync iAsync, ILogger<AsyncProcessor> logger = default)"; + << indent() << "public AsyncProcessor(IAsync iAsync, ILogger<AsyncProcessor>" << nullable_suffix() << " logger = default)"; if (!extends.empty()) { @@ -2193,7 +2312,9 @@ void t_netstd_generator::generate_service_server(ostream& out, t_service* tservi if (extends.empty()) { - out << indent() << "protected Dictionary<string, ProcessFunction> processMap_ = new Dictionary<string, ProcessFunction>();" << endl; + out << indent() << "protected Dictionary<string, ProcessFunction> processMap_ = new" + << (use_net6_features ? "" : " Dictionary<string, ProcessFunction>") // Simplify new expression (IDE0090) + << "();" << endl; } out << endl; @@ -2228,7 +2349,7 @@ void t_netstd_generator::generate_service_server(ostream& out, t_service* tservi indent_up(); out << indent() << "var msg = await iprot.ReadMessageBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl << endl - << indent() << "processMap_.TryGetValue(msg.Name, out ProcessFunction fn);" << endl + << indent() << "processMap_.TryGetValue(msg.Name, out var fn);" << endl << endl << indent() << "if (fn == null)" << endl << indent() << "{" << endl; @@ -2653,15 +2774,15 @@ void t_netstd_generator::generate_deserialize_container(ostream& out, t_type* tt if (ttype->is_map()) { - out << indent() << "TMap " << obj << " = await iprot.ReadMapBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl; + out << indent() << "var " << obj << " = await iprot.ReadMapBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl; } else if (ttype->is_set()) { - out << indent() << "TSet " << obj << " = await iprot.ReadSetBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl; + out << indent() << "var " << obj << " = await iprot.ReadSetBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl; } else if (ttype->is_list()) { - out << indent() << "TList " << obj << " = await iprot.ReadListBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl; + out << indent() << "var " << obj << " = await iprot.ReadListBeginAsync(" << CANCELLATION_TOKEN_NAME << ");" << endl; } out << indent() << prefix << " = new " << type_name(ttype) << "(" << obj << ".Count);" << endl; @@ -2711,8 +2832,8 @@ void t_netstd_generator::generate_deserialize_map_element(ostream& out, t_map* t t_field fkey(tmap->get_key_type(), key); t_field fval(tmap->get_val_type(), val); - out << indent() << declare_field(&fkey) << endl; - out << indent() << declare_field(&fval) << endl; + out << indent() << declare_field(&fkey, false, false) << endl; + out << indent() << declare_field(&fval, false, false) << endl; generate_deserialize_field(out, &fkey); generate_deserialize_field(out, &fval); @@ -2725,7 +2846,7 @@ void t_netstd_generator::generate_deserialize_set_element(ostream& out, t_set* t string elem = tmp("_elem"); t_field felem(tset->get_elem_type(), elem); - out << indent() << declare_field(&felem) << endl; + out << indent() << declare_field(&felem, false, false) << endl; generate_deserialize_field(out, &felem); @@ -2737,19 +2858,20 @@ void t_netstd_generator::generate_deserialize_list_element(ostream& out, t_list* string elem = tmp("_elem"); t_field felem(tlist->get_elem_type(), elem); - out << indent() << declare_field(&felem) << endl; + out << indent() << declare_field(&felem, false, false) << endl; generate_deserialize_field(out, &felem); out << indent() << prefix << ".Add(" << elem << ");" << endl; } -void t_netstd_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix, bool is_propertyless) +void t_netstd_generator::generate_serialize_field(ostream& out, t_field* tfield, string prefix, bool is_propertyless, bool allow_nullable) { t_type* type = tfield->get_type(); type = resolve_typedef( type); string name = prefix + (is_propertyless ? "" : prop_name(tfield)); + string nullable_name = name + (allow_nullable ? nullable_value_access(type) : ""); if (type->is_void()) { @@ -2768,8 +2890,6 @@ void t_netstd_generator::generate_serialize_field(ostream& out, t_field* tfield, { out << indent() << "await oprot."; - string nullable_name = name; - if (type->is_base_type()) { t_base_type::t_base tbase = static_cast<t_base_type*>(type)->get_base(); @@ -2812,7 +2932,7 @@ void t_netstd_generator::generate_serialize_field(ostream& out, t_field* tfield, } else if (type->is_enum()) { - out << "WriteI32Async((int)" << nullable_name << ", " << CANCELLATION_TOKEN_NAME << ");"; + out << "WriteI32Async((int)" << name << ", " << CANCELLATION_TOKEN_NAME << ");"; } out << endl; } @@ -2902,21 +3022,21 @@ void t_netstd_generator::generate_serialize_container(ostream& out, t_type* ttyp void t_netstd_generator::generate_serialize_map_element(ostream& out, t_map* tmap, string iter, string map) { t_field kfield(tmap->get_key_type(), iter); - generate_serialize_field(out, &kfield, ""); + generate_serialize_field(out, &kfield, "", false, false); t_field vfield(tmap->get_val_type(), map + "[" + iter + "]"); - generate_serialize_field(out, &vfield, ""); + generate_serialize_field(out, &vfield, "", false, false); } void t_netstd_generator::generate_serialize_set_element(ostream& out, t_set* tset, string iter) { t_field efield(tset->get_elem_type(), iter); - generate_serialize_field(out, &efield, ""); + generate_serialize_field(out, &efield, "", false, false); } void t_netstd_generator::generate_serialize_list_element(ostream& out, t_list* tlist, string iter) { t_field efield(tlist->get_elem_type(), iter); - generate_serialize_field(out, &efield, ""); + generate_serialize_field(out, &efield, "", false, false); } void t_netstd_generator::generate_property(ostream& out, t_field* tfield, bool isPublic, bool generateIsset) @@ -2930,12 +3050,23 @@ void t_netstd_generator::generate_netstd_property(ostream& out, t_field* tfield, { out << indent() << "[DataMember(Order = 0)]" << endl; } - out << indent() << (isPublic ? "public " : "private ") << type_name(tfield->get_type()) << " " << prop_name(tfield); + + out << indent() + << (isPublic ? "public " : "private ") + << type_name(tfield->get_type()) + << nullable_field_suffix(tfield) + << " " + << prop_name(tfield) + ; bool is_required = field_is_required(tfield); if (is_required) { - out << " { get; set; }" << endl; + out << " { get; set; }"; + if( use_net6_features && (!force_member_nullable(tfield))) { + out << initialize_field(tfield) << ";"; + } + out << endl; } else { @@ -3207,6 +3338,105 @@ string t_netstd_generator::func_name(std::string fname, bool suppress_mapping) { return get_mapped_member_name(fname); } +bool t_netstd_generator::is_nullable_type(t_type* ttype) { + ttype = resolve_typedef(ttype); + + if (ttype->is_enum()) { + return false; + } + + if (ttype->is_base_type()) { + t_base_type::t_base tbase = static_cast<t_base_type*>(ttype)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_STRING: + return true; // both binary and string + default: + return false; + } + } + + return true; +} + + +string t_netstd_generator::nullable_suffix() { + if(use_net6_features) { + return "?"; + } else { + return ""; + } +} + + +string t_netstd_generator::nullable_field_suffix(t_field* tfield) { + if(field_is_required(tfield) && (!force_member_nullable(tfield))) + return ""; + else + return nullable_field_suffix(tfield->get_type()); +} + + +string t_netstd_generator::nullable_field_suffix(t_type* ttype) { + if( ! use_net6_features) { + return ""; + } + + ttype = resolve_typedef(ttype); + + if (ttype->is_enum()) { + return ""; + } + + if (ttype->is_base_type()) { + t_base_type::t_base tbase = static_cast<t_base_type*>(ttype)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_STRING: + return nullable_suffix(); + default: + return ""; + } + } + + return nullable_suffix(); +} + +string t_netstd_generator::nullable_value_access(t_type* ttype) { + if( ! use_net6_features) + return ""; + + ttype = resolve_typedef(ttype); + + // this code uses the null-forgiving operator and therefore assumes that the variable + // has been properly checked against an isset guard or null + if (ttype->is_base_type()) { + t_base_type::t_base tbase = static_cast<t_base_type*>(ttype)->get_base(); + switch (tbase) + { + case t_base_type::TYPE_STRING: + return "!"; + default: + return ""; + } + } + + if (ttype->is_container() || ttype->is_struct() || ttype->is_xception()) { + return "!"; + } + + return ""; +} + +bool t_netstd_generator::force_member_nullable(t_field* tfield) { + // IMPORTANT: + // If tfield is a struct that contains a required field of the same type (directly or indirectly), + // auto-initializing such a member field would immediately produce an OOM, or at least unexpectedly + // allocate potentially large amounts of memory -> ALWAYS leave containers and struct members nullable + t_type* ttype = resolve_typedef(tfield->get_type()); + return ttype->is_struct() || ttype->is_container(); +} + string t_netstd_generator::type_name(t_type* ttype, bool with_namespace) { ttype = resolve_typedef(ttype); @@ -3225,7 +3455,7 @@ string t_netstd_generator::type_name(t_type* ttype, bool with_namespace) if (ttype->is_set()) { t_set* tset = static_cast<t_set*>(ttype); - return "THashSet<" + type_name(tset->get_elem_type()) + ">"; + return "HashSet<" + type_name(tset->get_elem_type()) + ">"; } if (ttype->is_list()) @@ -3281,10 +3511,14 @@ string t_netstd_generator::base_type_name(t_base_type* tbase) } } -string t_netstd_generator::get_deep_copy_method_call(t_type* ttype, bool& needs_typecast) +string t_netstd_generator::get_deep_copy_method_call(t_type* ttype, bool is_not_null, bool& needs_typecast, string& suffix) { ttype = resolve_typedef(ttype); + // if is_not_null is set, then the surrounding code already explicitly tests against != null + string null_check(""); + + suffix = ""; needs_typecast = false; if (ttype->is_base_type()) { @@ -3293,9 +3527,16 @@ string t_netstd_generator::get_deep_copy_method_call(t_type* ttype, bool& needs_ { case t_base_type::TYPE_STRING: if (ttype->is_binary()) { - return ".ToArray()"; + suffix = nullable_suffix(); + if( use_net6_features) { + null_check = is_not_null ? "!" : " ?? Array.Empty<byte>()"; + } + return ".ToArray()" + null_check; } else { - return ""; // simple assignment will do, strings are immutable in C# + if( use_net6_features) { + null_check = is_not_null ? "!" : " ?? string.Empty"; + } + return null_check; // simple assignment will do, strings are immutable in C# } break; default: @@ -3306,63 +3547,116 @@ string t_netstd_generator::get_deep_copy_method_call(t_type* ttype, bool& needs_ { return ""; // simple assignment will do } + else if (is_union_enabled() && ttype->is_struct() && static_cast<t_struct*>(ttype)->is_union()) + { + needs_typecast = (! ttype->is_container()); + suffix = nullable_suffix(); + if( use_net6_features) { + null_check = is_not_null ? "!" : " ?? new "+ttype->get_name() +".___undefined()"; + } + return "." + DEEP_COPY_METHOD_NAME + "()" + null_check; + } else { - needs_typecast = (! ttype->is_container()); - return "." + DEEP_COPY_METHOD_NAME + "()"; + needs_typecast = (! ttype->is_container()); + suffix = nullable_suffix(); + if( use_net6_features) { + null_check = is_not_null ? "!" : " ?? new()"; + } + return "." + DEEP_COPY_METHOD_NAME + "()" + null_check; } + + throw "UNEXPECTED TYPE IN get_deep_copy_method_call: " + ttype->get_name(); } -string t_netstd_generator::declare_field(t_field* tfield, bool init, string prefix) +string t_netstd_generator::declare_field(t_field* tfield, bool init, bool allow_nullable, string prefix) { - string result = type_name(tfield->get_type()) + " " + prefix + tfield->get_name(); + string result = type_name(tfield->get_type()) + + (allow_nullable ? nullable_field_suffix(tfield) : "") + + " " + + prefix + tfield->get_name() + ; if (init) { - t_type* ttype = tfield->get_type(); - ttype = resolve_typedef(ttype); - if (ttype->is_base_type() && field_has_default(tfield)) - { - std::ofstream dummy; - result += " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value()); - } - else if (ttype->is_base_type()) + result += initialize_field(tfield); + } + + return result + ";"; +} + +string t_netstd_generator::initialize_field(t_field* tfield) +{ + t_type* ttype = tfield->get_type(); + ttype = resolve_typedef(ttype); + + if (ttype->is_base_type() && field_has_default(tfield)) + { + std::ofstream dummy; + return " = " + render_const_value(dummy, tfield->get_name(), ttype, tfield->get_value()); + } + else if (force_member_nullable(tfield)) + { + return ""; // see force_member_nullable() why this is necessary + } + else if (ttype->is_base_type()) + { + t_base_type::t_base tbase = static_cast<t_base_type*>(ttype)->get_base(); + switch (tbase) { - t_base_type::t_base tbase = static_cast<t_base_type*>(ttype)->get_base(); - switch (tbase) - { - case t_base_type::TYPE_VOID: - throw "NO T_VOID CONSTRUCT"; - case t_base_type::TYPE_STRING: - result += " = null"; - break; - case t_base_type::TYPE_BOOL: - result += " = false"; - break; - case t_base_type::TYPE_I8: - 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: - result += " = (double)0"; - break; + case t_base_type::TYPE_VOID: + throw "NO T_VOID CONSTRUCT"; + case t_base_type::TYPE_STRING: + if(use_net6_features && field_is_required(tfield)) { + if (ttype->is_binary()) { + return " = Array.Empty<byte>()"; + } else { + return " = string.Empty"; + } + } else { + return " = null"; } + break; + case t_base_type::TYPE_BOOL: + return " = false"; + break; + case t_base_type::TYPE_I8: + case t_base_type::TYPE_I16: + case t_base_type::TYPE_I32: + case t_base_type::TYPE_I64: + return " = 0"; + break; + case t_base_type::TYPE_DOUBLE: + return " = 0.0"; + break; } - else if (ttype->is_enum()) - { - result += " = (" + type_name(ttype) + ")0"; - } - else if (ttype->is_container()) - { - result += " = new " + type_name(ttype) + "()"; + } + else if (ttype->is_enum()) + { + return " = default"; + } + else if (ttype->is_container()) + { + if(use_net6_features) { + return " = new()"; + } else { + return " = new " + type_name(ttype) + "()"; } - else - { - result += " = new " + type_name(ttype) + "()"; + } + else if (ttype->is_struct()) + { + t_struct* tstruct = static_cast<t_struct*>(ttype); + if(use_net6_features) { + if(tstruct->is_union()) { + return " = new " + type_name(ttype) + ".___undefined()"; + } else { + return " = new()"; + } + } else { + return " = new " + type_name(ttype) + "()"; } } - return result + ";"; + + throw "UNEXPECTED TYPE IN initialize_field: " + ttype->get_name(); } string t_netstd_generator::function_signature(t_function* tfunction, string prefix) @@ -3412,7 +3706,7 @@ string t_netstd_generator::argument_list(t_struct* tstruct, bool with_types) } if( with_types) { - result += type_name((*f_iter)->get_type()) + " "; + result += type_name((*f_iter)->get_type()) + nullable_field_suffix(*f_iter) + " "; } result += normalize_name((*f_iter)->get_name(),true); @@ -3573,6 +3867,7 @@ THRIFT_REGISTER_GENERATOR( " serial: Add serialization support to generated classes.\n" " union: Use new union typing, which includes a static read function for union types.\n" " pascal: Generate Pascal Case property names according to Microsoft naming convention.\n" - " no_deepcopy: Suppress generation of DeepCopy() method.\n" + " net6: Enable features that require net6 and C# 8 or higher.\n" + " no_deepcopy: Suppress generation of " + DEEP_COPY_METHOD_NAME + "() method.\n" " async_postfix: Append \"Async\" to all service methods (maintains compatibility with existing code).\n" ) diff --git a/compiler/cpp/src/thrift/generate/t_netstd_generator.h b/compiler/cpp/src/thrift/generate/t_netstd_generator.h index 8896075b2..982cd26ef 100644 --- a/compiler/cpp/src/thrift/generate/t_netstd_generator.h +++ b/compiler/cpp/src/thrift/generate/t_netstd_generator.h @@ -116,7 +116,7 @@ public: void generate_deserialize_set_element(ostream& out, t_set* tset, string prefix = ""); void generate_deserialize_map_element(ostream& out, t_map* tmap, string prefix = ""); void generate_deserialize_list_element(ostream& out, t_list* list, string prefix = ""); - void generate_serialize_field(ostream& out, t_field* tfield, string prefix = "", bool is_propertyless = false); + void generate_serialize_field(ostream& out, t_field* tfield, string prefix = "", bool is_propertyless = false, bool allow_nullable = true); void generate_serialize_struct(ostream& out, t_struct* tstruct, string prefix = ""); void generate_serialize_container(ostream& out, t_type* ttype, string prefix = ""); void generate_serialize_map_element(ostream& out, t_map* tmap, string iter, string map); @@ -139,7 +139,7 @@ public: string type_name(t_type* ttype, bool with_namespace = true); string base_type_name(t_base_type* tbase); - string declare_field(t_field* tfield, bool init = false, string prefix = ""); + string declare_field(t_field* tfield, bool init = false, bool allow_nullable = true, string prefix = ""); string function_signature_async(t_function* tfunction, string prefix = "", int mode = MODE_FULL_DECL); string function_signature(t_function* tfunction, string prefix = ""); string argument_list(t_struct* tstruct, bool with_types = true); @@ -152,15 +152,19 @@ public: protected: std::string autogen_comment() override { - return std::string("/**\n") - + " * <auto-generated>\n" - + " * " + autogen_summary() + "\n" - + " * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n" - + " * </auto-generated>\n" - " */\n" - ; + string comment = "/**\n"; + if( ! use_net6_features) { + comment += " * <auto-generated>\n"; + } + comment += " * " + autogen_summary() + "\n"; + comment += " * DO NOT EDIT UNLESS YOU ARE SURE THAT YOU KNOW WHAT YOU ARE DOING\n"; + if( ! use_net6_features) { + comment += " * </auto-generated>\n"; + } + comment += " */\n"; + return comment; } - + private: string namespace_name_; @@ -172,6 +176,7 @@ private: bool wcf_; bool use_pascal_case_properties; bool suppress_deepcopy; + bool use_net6_features; bool add_async_postfix; string wcf_namespace_; @@ -191,11 +196,20 @@ private: void cleanup_member_name_mapping(void* scope); string get_mapped_member_name(string oldname); string get_isset_name(const string& str); - string get_deep_copy_method_call(t_type* ttype, bool& needs_typecast); + string get_deep_copy_method_call(t_type* ttype, bool is_not_null, bool& needs_typecast, string& suffix); void collect_extensions_types(t_struct* tstruct); void collect_extensions_types(t_type* ttype); void generate_extensions(ostream& out, map<string, t_type*> types); void reset_indent(); void generate_null_check_begin(ostream& out, t_field* tfield); void generate_null_check_end(ostream& out, t_field* tfield); + string initialize_field(t_field* tfield); + + void pragmas_and_directives(ostream& out); + bool is_nullable_type(t_type* ttype); + bool force_member_nullable(t_field* tfield); // see there + string nullable_suffix(); // unconditionally + string nullable_field_suffix(t_field* tfield); // depends on field type + string nullable_field_suffix(t_type* ttype); // depends on field type + string nullable_value_access(t_type* ttype); // depends on field type }; diff --git a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs index 660b2b7a1..f42337630 100644 --- a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs +++ b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Impl/Thrift5253/MyService.cs @@ -26,30 +26,30 @@ namespace Thrift.PublicInterfaces.Compile.Tests.Impl.Thrift5253 { class MyServiceImpl : MyService.IAsync { - public Task<AsyncProcessor> AsyncProcessor_(AsyncProcessor input, CancellationToken cancellationToken = default) + public Task<AsyncProcessor> AsyncProcessor_(AsyncProcessor? input, CancellationToken cancellationToken = default) { - return Task.FromResult(new AsyncProcessor() { Foo = input.Foo }); + return Task.FromResult(new AsyncProcessor() { Foo = input?.Foo ?? 0 }); } - public Task<BrokenResult> Broken(BrokenArgs input, CancellationToken cancellationToken = default) + public Task<BrokenResult> Broken(BrokenArgs? input, CancellationToken cancellationToken = default) { - return Task.FromResult(new BrokenResult() { Foo = input.Foo }); + return Task.FromResult(new BrokenResult() { Foo = input?.Foo ?? 0 }); } - public Task<Client> Client_(Client input, CancellationToken cancellationToken = default) + public Task<Client> Client_(Client? input, CancellationToken cancellationToken = default) { _ = cancellationToken; - return Task.FromResult(new Client() { Foo = input.Foo }); + return Task.FromResult(new Client() { Foo = input?.Foo ?? 0 }); } - public Task<IAsync> IAsync_(IAsync input, CancellationToken cancellationToken = default) + public Task<IAsync> IAsync_(IAsync? input, CancellationToken cancellationToken = default) { - return Task.FromResult(new IAsync() { Foo = input.Foo }); + return Task.FromResult(new IAsync() { Foo = input?.Foo ?? 0 }); } - public Task<InternalStructs> InternalStructs_(InternalStructs input, CancellationToken cancellationToken = default) + public Task<InternalStructs> InternalStructs_(InternalStructs? input, CancellationToken cancellationToken = default) { - return Task.FromResult(new InternalStructs() { Foo = input.Foo }); + return Task.FromResult(new InternalStructs() { Foo = input?.Foo ?? 0 }); } public Task TestAsync(CancellationToken cancellationToken = default) @@ -62,9 +62,9 @@ namespace Thrift.PublicInterfaces.Compile.Tests.Impl.Thrift5253 return Task.CompletedTask; } - public Task<WorksRslt> Works(WorksArrrgs input, CancellationToken cancellationToken = default) + public Task<WorksRslt> Works(WorksArrrgs? input, CancellationToken cancellationToken = default) { - return Task.FromResult(new WorksRslt() { Foo = input.Foo }); + return Task.FromResult(new WorksRslt() { Foo = input?.Foo ?? 0 }); } } } diff --git a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj index 176e734b6..42a139cc8 100644 --- a/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj +++ b/lib/netstd/Tests/Thrift.PublicInterfaces.Compile.Tests/Thrift.PublicInterfaces.Compile.Tests.csproj @@ -68,14 +68,14 @@ <Error Condition="$('$(ThriftBinaryVersion)'::StartsWith('$(ThriftVersionOutput)')) == true" Text="Thrift version returned: '$(ThriftBinaryVersion)' is not equal to the projects version '$(ThriftVersionOutput)'." /> <Message Importance="high" Text="Generating tests with thrift binary: '$(PathToThrift)'" /> <!-- Generate the thrift test files --> - <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./CassandraTest.thrift" /> - <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./optional_required_default.thrift" /> - <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./name_conflicts.thrift" /> - <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./../../../../test/ThriftTest.thrift" /> - <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./../../../../contrib/fb303/if/fb303.thrift" /> - <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./Thrift5253.thrift" /> - <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./Thrift5320.thrift" /> - <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial -r ./Thrift5382.thrift" /> + <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./CassandraTest.thrift" /> + <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./optional_required_default.thrift" /> + <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./name_conflicts.thrift" /> + <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./../../../../test/ThriftTest.thrift" /> + <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./../../../../contrib/fb303/if/fb303.thrift" /> + <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./Thrift5253.thrift" /> + <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./Thrift5320.thrift" /> + <Exec Command="$(PathToThrift) -gen netstd:wcf,union,serial,net6 -r ./Thrift5382.thrift" /> </Target> </Project> diff --git a/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs b/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs index 778d24c80..49108d187 100644 --- a/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs +++ b/lib/netstd/Tests/Thrift.Tests/Collections/TCollectionsTests.cs @@ -83,8 +83,8 @@ namespace Thrift.Tests.Collections [TestMethod] public void TCollection_Set_Equals_Primitive_Test() { - var collection1 = new THashSet<int> {1,2,3}; - var collection2 = new THashSet<int> {1,2,3}; + var collection1 = new HashSet<int> {1,2,3}; + var collection2 = new HashSet<int> {1,2,3}; Assert.IsTrue(TCollections.Equals(collection1, collection2)); Assert.IsTrue(collection1.SequenceEqual(collection2)); } @@ -92,8 +92,8 @@ namespace Thrift.Tests.Collections [TestMethod] public void TCollection_Set_Equals_Primitive_Different_Test() { - var collection1 = new THashSet<int> { 1, 2, 3 }; - var collection2 = new THashSet<int> { 1, 2 }; + var collection1 = new HashSet<int> { 1, 2, 3 }; + var collection2 = new HashSet<int> { 1, 2 }; Assert.IsFalse(TCollections.Equals(collection1, collection2)); Assert.IsFalse(collection1.SequenceEqual(collection2)); @@ -105,8 +105,8 @@ namespace Thrift.Tests.Collections [TestMethod] public void TCollection_Set_Equals_Objects_Test() { - var collection1 = new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; - var collection2 = new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; + var collection1 = new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; + var collection2 = new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; Assert.IsTrue(TCollections.Equals(collection1, collection2)); Assert.IsTrue(collection1.SequenceEqual(collection2)); } @@ -114,8 +114,8 @@ namespace Thrift.Tests.Collections [TestMethod] public void TCollection_Set_Set_Equals_Objects_Test() { - var collection1 = new THashSet<THashSet<ExampleClass>> { new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } }; - var collection2 = new THashSet<THashSet<ExampleClass>> { new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } }; + var collection1 = new HashSet<HashSet<ExampleClass>> { new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } }; + var collection2 = new HashSet<HashSet<ExampleClass>> { new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } } }; Assert.IsTrue(TCollections.Equals(collection1, collection2)); Assert.IsFalse(collection1.SequenceEqual(collection2)); // SequenceEqual() calls Equals() of the inner list instead of SequenceEqual() } @@ -123,7 +123,7 @@ namespace Thrift.Tests.Collections [TestMethod] public void TCollection_Set_Equals_OneAndTheSameObject_Test() { - var collection1 = new THashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; + var collection1 = new HashSet<ExampleClass> { new ExampleClass { X = 1 }, new ExampleClass { X = 2 } }; var collection2 = collection1; // references to one and the same collection Assert.IsTrue(TCollections.Equals(collection1, collection2)); Assert.IsTrue(collection1.SequenceEqual(collection2)); diff --git a/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs b/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs index 8de573eee..73921ea87 100644 --- a/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs +++ b/lib/netstd/Tests/Thrift.Tests/Collections/THashSetTests.cs @@ -1,4 +1,4 @@ -// Licensed to the Apache Software Foundation(ASF) under one +// Licensed to the Apache Software Foundation(ASF) under one // or more contributor license agreements.See the NOTICE file // distributed with this work for additional information // regarding copyright ownership.The ASF licenses this file @@ -22,6 +22,8 @@ using System.Text; using Microsoft.VisualStudio.TestTools.UnitTesting; using Thrift.Collections; +#pragma warning disable IDE0063 // simplify using + namespace Thrift.Tests.Collections { // ReSharper disable once InconsistentNaming @@ -33,7 +35,7 @@ namespace Thrift.Tests.Collections { const int value = 1; - var hashSet = new THashSet<int> {value}; + var hashSet = new HashSet<int> {value}; Assert.IsTrue(hashSet.Contains(value)); diff --git a/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs b/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs index 84fcab85c..afffed5f6 100644 --- a/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs +++ b/lib/netstd/Tests/Thrift.Tests/DataModel/DeepCopy.cs @@ -24,8 +24,6 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OptReqDefTest; using Thrift.Collections; -#nullable disable // this is just test code, we leave it at that - namespace Thrift.Tests.DataModel { // ReSharper disable once InconsistentNaming @@ -51,7 +49,7 @@ namespace Thrift.Tests.DataModel VerifyIdenticalContent(first, InitializeInstance(new RaceDetails())); } - private RaceDetails MakeNestedRaceDetails(int nesting) + private RaceDetails? MakeNestedRaceDetails(int nesting) { if (++nesting > 1) return null; @@ -61,7 +59,7 @@ namespace Thrift.Tests.DataModel return instance; } - private jack MakeNestedUnion(int nesting) + private jack? MakeNestedUnion(int nesting) { if (++nesting > 1) return null; @@ -88,11 +86,11 @@ namespace Thrift.Tests.DataModel instance.Req_one = default; instance.Req_two = default; instance.Req_three = default; - instance.Req_four = default; - instance.Req_five = default; + Assert.IsNotNull(instance.Req_four); + Assert.IsNotNull(instance.Req_five); instance.Req_six = default; instance.Req_seven = default;; - instance.Req_eight = default; + instance.Req_eight = default; // leave non-required fields unset again Assert.IsFalse(instance.__isset.def_one); @@ -119,15 +117,15 @@ namespace Thrift.Tests.DataModel Assert.AreEqual(instance.Req_two_with_value, 2.22); Assert.AreEqual(instance.Req_three_with_value, 3); Assert.AreEqual(instance.Req_four_with_value, "four"); - Assert.AreEqual("five", Encoding.UTF8.GetString(instance.Req_five_with_value)); + Assert.AreEqual("five", Encoding.UTF8.GetString(instance.Req_five_with_value!)); - Assert.IsTrue(instance.Req_six_with_value.Count == 1); + Assert.IsTrue(instance.Req_six_with_value!.Count == 1); Assert.AreEqual(instance.Req_six_with_value[0], 6 ); - Assert.IsTrue(instance.Req_seven_with_value.Count == 1); + Assert.IsTrue(instance.Req_seven_with_value!.Count == 1); Assert.IsTrue(instance.Req_seven_with_value.Contains(7)); - Assert.IsTrue(instance.Req_eight_with_value.Count == 1); + Assert.IsTrue(instance.Req_eight_with_value!.Count == 1); Assert.IsTrue(instance.Req_eight_with_value[8] == 8); Assert.IsTrue(instance.__isset.def_one_with_value); @@ -144,12 +142,16 @@ namespace Thrift.Tests.DataModel if (nesting < 2) { instance.Far_list = new List<Distance>() { Distance.foo, Distance.bar, Distance.baz }; - instance.Far_set = new THashSet<Distance>() { Distance.foo, Distance.bar, Distance.baz }; + instance.Far_set = new HashSet<Distance>() { Distance.foo, Distance.bar, Distance.baz }; instance.Far_map = new Dictionary<Distance, Distance>() { [Distance.foo] = Distance.foo, [Distance.bar] = Distance.bar, [Distance.baz] = Distance.baz }; - instance.Far_set_list = new THashSet<List<Distance>>() { new List<Distance>() { Distance.foo } }; - instance.Far_list_map_set = new List<Dictionary<sbyte, THashSet<Distance>>>() { new Dictionary<sbyte, THashSet<Distance>>() { [1] = new THashSet<Distance>() { Distance.baz } } }; - instance.Far_map_dist_to_rds = new Dictionary<Distance, List<RaceDetails>>() { [Distance.bar] = new List<RaceDetails>() { MakeNestedRaceDetails(nesting) } }; + instance.Far_set_list = new HashSet<List<Distance>>() { new List<Distance>() { Distance.foo } }; + instance.Far_list_map_set = new List<Dictionary<sbyte, HashSet<Distance>>>() { new Dictionary<sbyte, HashSet<Distance>>() { [1] = new HashSet<Distance>() { Distance.baz } } }; + + instance.Far_map_dist_to_rds = new Dictionary<Distance, List<RaceDetails>>() { [Distance.bar] = new List<RaceDetails>() }; + var details = MakeNestedRaceDetails(nesting); + if (details != null) + instance.Far_map_dist_to_rds[Distance.bar].Add(details); instance.Req_nested = MakeNestedRaceDetails(nesting); Assert.IsFalse(instance.__isset.opt_nested); @@ -245,19 +247,19 @@ namespace Thrift.Tests.DataModel instance.Triplesix = ModifyValue(instance.Triplesix); } - private jack ModifyValue(jack value, int level) + private jack? ModifyValue(jack? value, int level) { if (++level > 4) return value; if (value == null) value = MakeNestedUnion(0); - Debug.Assert(value.As_nested_struct != null); + Debug.Assert(value?.As_nested_struct != null); ModifyInstance(value.As_nested_struct, level); return value; } - private RaceDetails ModifyValue(RaceDetails value, int level) + private RaceDetails? ModifyValue(RaceDetails? value, int level) { if (++level > 4) return value; @@ -268,7 +270,7 @@ namespace Thrift.Tests.DataModel return value; } - private Dictionary<Distance, List<RaceDetails>> ModifyValue(Dictionary<Distance, List<RaceDetails>> value, int level) + private Dictionary<Distance, List<RaceDetails>> ModifyValue(Dictionary<Distance, List<RaceDetails>>? value, int level) { if (value == null) value = new Dictionary<Distance, List<RaceDetails>>(); @@ -283,29 +285,30 @@ namespace Thrift.Tests.DataModel if (value.TryGetValue(Distance.bar, out var list) && (list.Count > 0)) { ModifyInstance(list[0], level); - list.Add(null); + //list.Add(null); -- Thrift does not allow null values in containers } - value[Distance.baz] = null; + // Thrift does not allow null values in containers + //value[Distance.baz] = null; return value; } - private static List<Dictionary<sbyte, THashSet<Distance>>> ModifyValue(List<Dictionary<sbyte, THashSet<Distance>>> value) + private static List<Dictionary<sbyte, HashSet<Distance>>> ModifyValue(List<Dictionary<sbyte, HashSet<Distance>>>? value) { if (value == null) - value = new List<Dictionary<sbyte, THashSet<Distance>>>(); + value = new List<Dictionary<sbyte, HashSet<Distance>>>(); if (value.Count == 0) - value.Add(new Dictionary<sbyte, THashSet<Distance>>()); - else - value.Add(null); + value.Add(new Dictionary<sbyte, HashSet<Distance>>()); + //else + //value.Add(null); --Thrift does not allow null values in containers sbyte key = (sbyte)(value[0].Count + 10); if (value[0].Count == 0) - value[0].Add(key, new THashSet<Distance>()); - else - value[0].Add(key, null); + value[0].Add(key, new HashSet<Distance>()); + //else + //value[0].Add(key, null); --Thrift does not allow null values in containers foreach (var entry in value) { @@ -327,15 +330,15 @@ namespace Thrift.Tests.DataModel return value; } - private static THashSet<List<Distance>> ModifyValue(THashSet<List<Distance>> value) + private static HashSet<List<Distance>> ModifyValue(HashSet<List<Distance>>? value) { if (value == null) - value = new THashSet<List<Distance>>(); + value = new HashSet<List<Distance>>(); if (value.Count == 0) value.Add(new List<Distance>()); - else - value.Add(null); + //else + //value.Add(null); -- Thrift does not allow null values in containers foreach (var entry in value) if( entry != null) @@ -344,7 +347,7 @@ namespace Thrift.Tests.DataModel return value; } - private static Dictionary<Distance, Distance> ModifyValue(Dictionary<Distance, Distance> value) + private static Dictionary<Distance, Distance> ModifyValue(Dictionary<Distance, Distance>? value) { if (value == null) value = new Dictionary<Distance, Distance>(); @@ -354,10 +357,10 @@ namespace Thrift.Tests.DataModel return value; } - private static THashSet<Distance> ModifyValue(THashSet<Distance> value) + private static HashSet<Distance> ModifyValue(HashSet<Distance>? value) { if (value == null) - value = new THashSet<Distance>(); + value = new HashSet<Distance>(); if (value.Contains(Distance.foo)) value.Remove(Distance.foo); @@ -377,7 +380,7 @@ namespace Thrift.Tests.DataModel return value; } - private static List<Distance> ModifyValue(List<Distance> value) + private static List<Distance> ModifyValue(List<Distance>? value) { if (value == null) value = new List<Distance>(); @@ -392,7 +395,7 @@ namespace Thrift.Tests.DataModel return !value; } - private static Dictionary<sbyte, short> ModifyValue(Dictionary<sbyte, short> value) + private static Dictionary<sbyte, short> ModifyValue(Dictionary<sbyte, short>? value) { if (value == null) value = new Dictionary<sbyte, short>(); @@ -400,15 +403,15 @@ namespace Thrift.Tests.DataModel return value; } - private static THashSet<long> ModifyValue(THashSet<long> value) + private static HashSet<long> ModifyValue(HashSet<long>? value) { if (value == null) - value = new THashSet<long>(); + value = new HashSet<long>(); value.Add(value.Count+100); return value; } - private static List<int> ModifyValue(List<int> value) + private static List<int> ModifyValue(List<int>? value) { if (value == null) value = new List<int>(); @@ -416,16 +419,18 @@ namespace Thrift.Tests.DataModel return value; } - private static byte[] ModifyValue(byte[] value) + private static byte[] ModifyValue(byte[]? value) { if (value == null) value = new byte[1] { 0 }; if (value.Length > 0) value[0] = (value[0] < 0xFF) ? ++value[0] : (byte)0; + else + value = new byte[1] { 0 }; return value; } - private static string ModifyValue(string value) + private static string ModifyValue(string? value) { return value + "1"; } diff --git a/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs b/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs index 693b68ecc..ebc171747 100644 --- a/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs +++ b/lib/netstd/Tests/Thrift.Tests/DataModel/NullValuesSet.cs @@ -25,13 +25,15 @@ using Microsoft.VisualStudio.TestTools.UnitTesting; using OptReqDefTest; using Thrift.Collections; +#pragma warning disable IDE0017 // init can be simplified - we don't want that here + namespace Thrift.Tests.DataModel { // ReSharper disable once InconsistentNaming [TestClass] public class Thrift_5238 { - private void CheckInstance(RaceDetails instance) + private static void CheckInstance(RaceDetails instance) { // object Assert.IsTrue(instance.__isset.def_nested); @@ -42,14 +44,14 @@ namespace Thrift.Tests.DataModel // string Assert.IsTrue(instance.__isset.def_four); Assert.IsTrue(instance.__isset.opt_four); - Assert.IsNull(instance.Req_four); + Assert.IsTrue(string.IsNullOrEmpty(instance.Req_four)); Assert.IsNull(instance.Def_four); Assert.IsNull(instance.Opt_four); // byte[] Assert.IsTrue(instance.__isset.def_five); Assert.IsTrue(instance.__isset.opt_five); - Assert.IsNull(instance.Req_five); + Assert.IsTrue((instance.Req_five == null) || (instance.Req_five.Length == 0)); Assert.IsNull(instance.Def_five); Assert.IsNull(instance.Opt_five); @@ -66,6 +68,9 @@ namespace Thrift.Tests.DataModel { var instance = new OptReqDefTest.RaceDetails(); + // the following code INTENTIONALLY assigns null to non.nullable reftypes + #pragma warning disable CS8625 + // object instance.Def_nested = null; instance.Opt_nested = null; @@ -85,6 +90,9 @@ namespace Thrift.Tests.DataModel instance.Opt_six = null; instance.Def_six = null; + // back to normal + #pragma warning restore CS8625 + // test the setup CheckInstance(instance); diff --git a/lib/netstd/Thrift/Collections/THashSet.cs b/lib/netstd/Thrift/Collections/THashSet.cs index 1c060e5a7..fc2a507b2 100644 --- a/lib/netstd/Thrift/Collections/THashSet.cs +++ b/lib/netstd/Thrift/Collections/THashSet.cs @@ -15,69 +15,37 @@ // specific language governing permissions and limitations // under the License. +using System; using System.Collections; using System.Collections.Generic; namespace Thrift.Collections { // ReSharper disable once InconsistentNaming - public class THashSet<T> : ICollection<T> + [Obsolete("deprecated, use HashSet<T> instead")] + public class THashSet<T> : System.Collections.Generic.HashSet<T> { - private readonly HashSet<T> Items; - public THashSet() + : base() { - Items = new HashSet<T>(); } public THashSet(int capacity) +#if NET5_0_OR_GREATER + : base(capacity) +#elif NETFRAMEWORK || NETSTANDARD + : base(/*capacity not supported*/) +#else +#error Unknown platform +#endif { - #if NET5_0_OR_GREATER - Items = new HashSet<T>(capacity); - #elif NETFRAMEWORK || NETSTANDARD - Items = new HashSet<T>(/*capacity not supported*/); - #else - #error Unknown platform - #endif - } - - public int Count => Items.Count; - - public bool IsReadOnly => false; - - public void Add(T item) - { - Items.Add(item); } - public void Clear() + public THashSet(IEnumerable<T> collection) + : base(collection) { - Items.Clear(); } - public bool Contains(T item) - { - return Items.Contains(item); - } - - public void CopyTo(T[] array, int arrayIndex) - { - Items.CopyTo(array, arrayIndex); - } - - IEnumerator IEnumerable.GetEnumerator() - { - return Items.GetEnumerator(); - } - - public IEnumerator<T> GetEnumerator() - { - return ((IEnumerable<T>) Items).GetEnumerator(); - } - - public bool Remove(T item) - { - return Items.Remove(item); - } } } + diff --git a/test/netstd/Client/Client.csproj b/test/netstd/Client/Client.csproj index e312990c9..9d4ab48c9 100644 --- a/test/netstd/Client/Client.csproj +++ b/test/netstd/Client/Client.csproj @@ -49,8 +49,8 @@ <Exec Condition="'$(OS)' == 'Windows_NT'" Command="where thrift" ConsoleToMSBuild="true"> <Output TaskParameter="ConsoleOutput" PropertyName="PathToThrift" /> </Exec> - <Exec Condition="Exists('$(PathToThrift)')" Command=""$(PathToThrift)" -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" /> - <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" /> - <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" /> + <Exec Condition="Exists('$(PathToThrift)')" Command=""$(PathToThrift)" -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" /> + <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" /> + <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" /> </Target> </Project> diff --git a/test/netstd/Client/Performance/TestDataFactory.cs b/test/netstd/Client/Performance/TestDataFactory.cs index 833947c25..8dec3f303 100644 --- a/test/netstd/Client/Performance/TestDataFactory.cs +++ b/test/netstd/Client/Performance/TestDataFactory.cs @@ -40,9 +40,9 @@ namespace Client.Tests }; } - private static THashSet<Insanity> CreateSetField(int count) + private static HashSet<Insanity> CreateSetField(int count) { - var retval = new THashSet<Insanity>(); + var retval = new HashSet<Insanity>(); for (var i = 0; i < count; ++i) retval.Add(CreateInsanity(count)); return retval; @@ -90,41 +90,41 @@ namespace Client.Tests return retval; } - private static List<Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>>> CreateListField(int count) + private static List<Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>>> CreateListField(int count) { - var retval = new List<Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>>>(); + var retval = new List<Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>>>(); for (var i = 0; i < count; ++i) retval.Add(CreateListFieldData(count)); return retval; } - private static Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>> CreateListFieldData(int count) + private static Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>> CreateListFieldData(int count) { - var retval = new Dictionary<THashSet<int>, Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>>(); + var retval = new Dictionary<HashSet<int>, Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>>(); for (var i = 0; i < count; ++i) retval.Add( CreateIntHashSet(count), CreateListFieldDataDict(count)); return retval; } - private static THashSet<int> CreateIntHashSet(int count) + private static HashSet<int> CreateIntHashSet(int count) { - var retval = new THashSet<int>(); + var retval = new HashSet<int>(); for (var i = 0; i < count; ++i) retval.Add(i); return retval; } - private static Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>> CreateListFieldDataDict(int count) + private static Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>> CreateListFieldDataDict(int count) { - var retval = new Dictionary<int, THashSet<List<Dictionary<Insanity, string>>>>(); + var retval = new Dictionary<int, HashSet<List<Dictionary<Insanity, string>>>>(); for (var i = 0; i < count; ++i) retval.Add(i, CreateListFieldDataDictValue(count)); return retval; } - private static THashSet<List<Dictionary<Insanity, string>>> CreateListFieldDataDictValue(int count) + private static HashSet<List<Dictionary<Insanity, string>>> CreateListFieldDataDictValue(int count) { - var retval = new THashSet<List<Dictionary<Insanity, string>>>(); + var retval = new HashSet<List<Dictionary<Insanity, string>>>(); for (var i = 0; i < count; ++i) retval.Add( CreateListFieldDataDictValueList(count)); return retval; diff --git a/test/netstd/Client/TestClient.cs b/test/netstd/Client/TestClient.cs index 0a7fa003b..0c80b9c48 100644 --- a/test/netstd/Client/TestClient.cs +++ b/test/netstd/Client/TestClient.cs @@ -644,9 +644,14 @@ namespace ThriftTest Struct_thing = o, I32_thing = 5 }; - var i2 = await client.testNest(o2, MakeTimeoutToken()); + Xtruct2 i2 = await client.testNest(o2, MakeTimeoutToken()); i = i2.Struct_thing; - Console.WriteLine(" = {" + i2.Byte_thing + ", {\"" + i.String_thing + "\", " + i.Byte_thing + ", " + i.I32_thing + ", " + i.I64_thing + "}, " + i2.I32_thing + "}"); + Console.WriteLine(" = {" + i2.Byte_thing + ", {\"" + + (i?.String_thing ?? "<null>") + "\", " + + (i?.Byte_thing ?? 0) + ", " + + (i?.I32_thing ?? 0) + ", " + + (i?.I64_thing ?? 0) + "}, " + + i2.I32_thing + "}"); var mapout = new Dictionary<int, int>(); for (var j = 0; j < 5; j++) @@ -681,7 +686,7 @@ namespace ThriftTest //set // TODO: Validate received message - var setout = new THashSet<int>(); + var setout = new HashSet<int>(); for (var j = -2; j < 3; j++) { setout.Add(j); @@ -937,7 +942,7 @@ namespace ThriftTest } catch (Xception2 ex) { - if (ex.ErrorCode != 2002 || ex.Struct_thing.String_thing != "This is an Xception2") + if (ex.ErrorCode != 2002 || ex.Struct_thing?.String_thing != "This is an Xception2") { Console.WriteLine("*** FAILED ***"); returnCode |= ErrorExceptions; diff --git a/test/netstd/Server/Server.csproj b/test/netstd/Server/Server.csproj index 546d0e2b9..439e5c1e6 100644 --- a/test/netstd/Server/Server.csproj +++ b/test/netstd/Server/Server.csproj @@ -51,8 +51,8 @@ <Exec Condition="'$(OS)' == 'Windows_NT'" Command="where thrift" ConsoleToMSBuild="true"> <Output TaskParameter="ConsoleOutput" PropertyName="PathToThrift" /> </Exec> - <Exec Condition="Exists('$(PathToThrift)')" Command=""$(PathToThrift)" -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" /> - <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" /> - <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../ThriftTest.thrift" /> + <Exec Condition="Exists('$(PathToThrift)')" Command=""$(PathToThrift)" -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" /> + <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" /> + <Exec Condition="Exists('$(ProjectDir)/../../../compiler/cpp/thrift')" Command="$(ProjectDir)/../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../ThriftTest.thrift" /> </Target> </Project> diff --git a/test/netstd/Server/TestServer.cs b/test/netstd/Server/TestServer.cs index 515a299f8..86072b0a7 100644 --- a/test/netstd/Server/TestServer.cs +++ b/test/netstd/Server/TestServer.cs @@ -229,10 +229,10 @@ namespace ThriftTest return Task.CompletedTask; } - public Task<string> testString(string thing, CancellationToken cancellationToken) + public Task<string> testString(string? thing, CancellationToken cancellationToken) { - logger.Invoke("testString({0})", thing); - return Task.FromResult(thing); + logger.Invoke("testString({0})", thing ?? "<null>"); + return Task.FromResult(thing ?? string.Empty); } public Task<bool> testBool(bool thing, CancellationToken cancellationToken) @@ -265,117 +265,129 @@ namespace ThriftTest return Task.FromResult(thing); } - public Task<byte[]> testBinary(byte[] thing, CancellationToken cancellationToken) + public Task<byte[]> testBinary(byte[]? thing, CancellationToken cancellationToken) { - logger.Invoke("testBinary({0} bytes)", thing.Length); - return Task.FromResult(thing); + logger.Invoke("testBinary({0} bytes)", thing?.Length ?? 0); + return Task.FromResult(thing ?? Array.Empty<byte>()); } - public Task<Xtruct> testStruct(Xtruct thing, CancellationToken cancellationToken) + public Task<Xtruct> testStruct(Xtruct? thing, CancellationToken cancellationToken) { - logger.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing.String_thing, thing.Byte_thing, thing.I32_thing, thing.I64_thing); - return Task.FromResult(thing); + logger.Invoke("testStruct({{\"{0}\", {1}, {2}, {3}}})", thing?.String_thing ?? "<null>", thing?.Byte_thing ?? 0, thing?.I32_thing ?? 0, thing?.I64_thing ?? 0); + return Task.FromResult(thing ?? new Xtruct()); // null returns are not allowed in Thrift } - public Task<Xtruct2> testNest(Xtruct2 nest, CancellationToken cancellationToken) + public Task<Xtruct2> testNest(Xtruct2? nest, CancellationToken cancellationToken) { - var thing = nest.Struct_thing; + var thing = nest?.Struct_thing; logger.Invoke("testNest({{{0}, {{\"{1}\", {2}, {3}, {4}, {5}}}}})", - nest.Byte_thing, - thing.String_thing, - thing.Byte_thing, - thing.I32_thing, - thing.I64_thing, - nest.I32_thing); - return Task.FromResult(nest); + nest?.Byte_thing ?? 0, + thing?.String_thing ?? "<null>", + thing?.Byte_thing ?? 0, + thing?.I32_thing ?? 0, + thing?.I64_thing ?? 0, + nest?.I32_thing ?? 0); + return Task.FromResult(nest ?? new Xtruct2()); // null returns are not allowed in Thrift } - public Task<Dictionary<int, int>> testMap(Dictionary<int, int> thing, CancellationToken cancellationToken) + public Task<Dictionary<int, int>> testMap(Dictionary<int, int>? thing, CancellationToken cancellationToken) { sb.Clear(); sb.Append("testMap({{"); - var first = true; - foreach (var key in thing.Keys) + if (thing != null) { - if (first) - { - first = false; - } - else + var first = true; + foreach (var key in thing.Keys) { - sb.Append(", "); + if (first) + { + first = false; + } + else + { + sb.Append(", "); + } + sb.AppendFormat("{0} => {1}", key, thing[key]); } - sb.AppendFormat("{0} => {1}", key, thing[key]); } sb.Append("}})"); logger.Invoke(sb.ToString()); - return Task.FromResult(thing); + return Task.FromResult(thing ?? new Dictionary<int, int>()); // null returns are not allowed in Thrift } - public Task<Dictionary<string, string>> testStringMap(Dictionary<string, string> thing, CancellationToken cancellationToken) + public Task<Dictionary<string, string>> testStringMap(Dictionary<string, string>? thing, CancellationToken cancellationToken) { sb.Clear(); sb.Append("testStringMap({{"); - var first = true; - foreach (var key in thing.Keys) + if (thing != null) { - if (first) - { - first = false; - } - else + var first = true; + foreach (var key in thing.Keys) { - sb.Append(", "); + if (first) + { + first = false; + } + else + { + sb.Append(", "); + } + sb.AppendFormat("{0} => {1}", key, thing[key]); } - sb.AppendFormat("{0} => {1}", key, thing[key]); } sb.Append("}})"); logger.Invoke(sb.ToString()); - return Task.FromResult(thing); + return Task.FromResult(thing ?? new Dictionary<string, string>()); // null returns are not allowed in Thrift } - public Task<THashSet<int>> testSet(THashSet<int> thing, CancellationToken cancellationToken) + public Task<HashSet<int>> testSet(HashSet<int>? thing, CancellationToken cancellationToken) { sb.Clear(); sb.Append("testSet({{"); - var first = true; - foreach (int elem in thing) + if (thing != null) { - if (first) - { - first = false; - } - else + var first = true; + foreach (int elem in thing) { - sb.Append(", "); + if (first) + { + first = false; + } + else + { + sb.Append(", "); + } + sb.AppendFormat("{0}", elem); } - sb.AppendFormat("{0}", elem); } sb.Append("}})"); logger.Invoke(sb.ToString()); - return Task.FromResult(thing); + return Task.FromResult(thing ?? new HashSet<int>()); // null returns are not allowed in Thrift } - public Task<List<int>> testList(List<int> thing, CancellationToken cancellationToken) + public Task<List<int>> testList(List<int>? thing, CancellationToken cancellationToken) { sb.Clear(); sb.Append("testList({{"); - var first = true; - foreach (var elem in thing) + if (thing != null) { - if (first) + var first = true; + foreach (var elem in thing) { - first = false; + if (first) + { + first = false; + } + else + { + sb.Append(", "); + } + sb.AppendFormat("{0}", elem); } - else - { - sb.Append(", "); - } - sb.AppendFormat("{0}", elem); } sb.Append("}})"); logger.Invoke(sb.ToString()); - return Task.FromResult(thing); + return Task.FromResult(thing ?? new List<int>()); // null returns are not allowed in Thrift } public Task<Numberz> testEnum(Numberz thing, CancellationToken cancellationToken) @@ -409,7 +421,7 @@ namespace ThriftTest return Task.FromResult(mapmap); } - public Task<Dictionary<long, Dictionary<Numberz, Insanity>>> testInsanity(Insanity argument, CancellationToken cancellationToken) + public Task<Dictionary<long, Dictionary<Numberz, Insanity>>> testInsanity(Insanity? argument, CancellationToken cancellationToken) { logger.Invoke("testInsanity()"); @@ -428,8 +440,9 @@ namespace ThriftTest var first_map = new Dictionary<Numberz, Insanity>(); var second_map = new Dictionary<Numberz, Insanity>(); ; - first_map[Numberz.TWO] = argument; - first_map[Numberz.THREE] = argument; + // null dict keys/values are not allowed in Thrift + first_map[Numberz.TWO] = argument ?? new Insanity(); + first_map[Numberz.THREE] = argument ?? new Insanity(); second_map[Numberz.SIX] = new Insanity(); @@ -442,7 +455,7 @@ namespace ThriftTest return Task.FromResult(insane); } - public Task<Xtruct> testMulti(sbyte arg0, int arg1, long arg2, Dictionary<short, string> arg3, Numberz arg4, long arg5, + public Task<Xtruct> testMulti(sbyte arg0, int arg1, long arg2, Dictionary<short, string>? arg3, Numberz arg4, long arg5, CancellationToken cancellationToken) { logger.Invoke("testMulti()"); @@ -455,9 +468,9 @@ namespace ThriftTest return Task.FromResult(hello); } - public Task testException(string arg, CancellationToken cancellationToken) + public Task testException(string? arg, CancellationToken cancellationToken) { - logger.Invoke("testException({0})", arg); + logger.Invoke("testException({0})", arg ?? "<null>"); if (arg == "Xception") { var x = new Xception @@ -474,9 +487,9 @@ namespace ThriftTest return Task.CompletedTask; } - public Task<Xtruct> testMultiException(string arg0, string arg1, CancellationToken cancellationToken) + public Task<Xtruct> testMultiException(string? arg0, string? arg1, CancellationToken cancellationToken) { - logger.Invoke("testMultiException({0}, {1})", arg0, arg1); + logger.Invoke("testMultiException({0}, {1})", arg0 ?? "<null>", arg1 ?? "<null>"); if (arg0 == "Xception") { var x = new Xception diff --git a/tutorial/netstd/Interfaces/Interfaces.csproj b/tutorial/netstd/Interfaces/Interfaces.csproj index f81c88d96..d04b243f3 100644 --- a/tutorial/netstd/Interfaces/Interfaces.csproj +++ b/tutorial/netstd/Interfaces/Interfaces.csproj @@ -41,8 +41,8 @@ <Exec Condition="'$(OS)' == 'Windows_NT'" Command="where thrift" ConsoleToMSBuild="true"> <Output TaskParameter="ConsoleOutput" PropertyName="PathToThrift" /> </Exec> - <Exec Condition="Exists('$(PathToThrift)')" Command="$(PathToThrift) -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../tutorial.thrift" /> - <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../tutorial.thrift" /> - <Exec Condition="Exists('./../../../compiler/cpp/thrift')" Command="./../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial -r ./../../tutorial.thrift" /> + <Exec Condition="Exists('$(PathToThrift)')" Command="$(PathToThrift) -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../tutorial.thrift" /> + <Exec Condition="Exists('thrift')" Command="thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../tutorial.thrift" /> + <Exec Condition="Exists('./../../../compiler/cpp/thrift')" Command="./../../../compiler/cpp/thrift -out $(ProjectDir) -gen netstd:wcf,union,serial,net6 -r ./../../tutorial.thrift" /> </Target> </Project> diff --git a/tutorial/netstd/Server/Program.cs b/tutorial/netstd/Server/Program.cs index d9c9dbf12..29b21d073 100644 --- a/tutorial/netstd/Server/Program.cs +++ b/tutorial/netstd/Server/Program.cs @@ -131,7 +131,7 @@ Sample: if (selectedTransport == Transport.Http) { if (multiplex) - throw new Exception("This tutorial semple code does not yet allow multiplex over http (although Thrift itself of course does)"); + throw new Exception("This tutorial sample code does not yet allow multiplex over http (although Thrift itself of course does)"); new HttpServerSample().Run(cancellationToken); } else |