Skip to content

Commit 02eecea

Browse files
Internal change
PiperOrigin-RevId: 897682871
1 parent 514aceb commit 02eecea

12 files changed

Lines changed: 129 additions & 29 deletions

csharp/Google.Protobuf.Tools.nuspec

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,8 @@
2323
<file src="protoc/linux_aarch64/protoc" target="tools/linux_aarch64/protoc"/>
2424
<file src="protoc/macosx_x64/protoc" target="tools/macosx_x64/protoc"/>
2525
<!--
26-
- Include the protos for the well-known types in a directory where protoc will
27-
- find them by default.
26+
- Include the protos for the well-known types and language-specific editions features in a
27+
- directory where protoc will find them by default.
2828
-->
2929
<file src="src/google/protobuf/any.proto" target="tools/include/google/protobuf"/>
3030
<file src="src/google/protobuf/api.proto" target="tools/include/google/protobuf"/>
@@ -37,9 +37,11 @@
3737
<file src="src/google/protobuf/timestamp.proto" target="tools/include/google/protobuf"/>
3838
<file src="src/google/protobuf/type.proto" target="tools/include/google/protobuf"/>
3939
<file src="src/google/protobuf/wrappers.proto" target="tools/include/google/protobuf"/>
40+
<file src="csharp/google/protobuf/c_sharp_features.proto" target="tools/include/google/protobuf"/>
4041
<!--
4142
- Include the protos for the well-known types again in their old location,
42-
- for backward compatibility.
43+
- for backward compatibility. This does not include features files, as those have never been
44+
- in tools/google/protobuf directory.
4345
-->
4446
<file src="src/google/protobuf/any.proto" target="tools/google/protobuf"/>
4547
<file src="src/google/protobuf/api.proto" target="tools/google/protobuf"/>

csharp/generate_protos.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ $PROTOC -Isrc -I. -Ijava/core/src/main/resources/ \
6767
conformance/test_protos/test_messages_edition2023.proto \
6868
conformance/test_protos/test_messages_edition_unstable.proto \
6969
csharp/protos/map_unittest_proto3.proto \
70+
csharp/protos/nrt.proto \
7071
csharp/protos/unittest_issues.proto \
7172
csharp/protos/unittest_custom_options_proto3.proto \
7273
csharp/protos/unittest_proto3.proto \

csharp/google/protobuf/c_sharp_features.proto

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,17 @@ extend google.protobuf.FeatureSet {
2020
}
2121

2222
message CSharpFeatures {
23+
// Whether the generated files should have nullable reference type annotations.
24+
// When enabled, the generated C# code includes `#nullable enable annotations`
25+
// and annotates message-typed properties and certain parameters with `?`.
26+
optional bool nullable_reference_types = 1 [
27+
retention = RETENTION_RUNTIME,
28+
targets = TARGET_TYPE_FIELD,
29+
targets = TARGET_TYPE_MESSAGE,
30+
targets = TARGET_TYPE_FILE,
31+
feature_support = {
32+
edition_introduced: EDITION_UNSTABLE,
33+
},
34+
edition_defaults = { edition: EDITION_LEGACY, value: "false" }
35+
];
2336
}

csharp/protos/nrt.proto

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Protocol Buffers - Google's data interchange format
2+
// Copyright 2026 Google Inc. All rights reserved.
3+
//
4+
// Use of this source code is governed by a BSD-style
5+
// license that can be found in the LICENSE file or at
6+
// https://developers.google.com/open-source/licenses/bsd
7+
8+
edition = "UNSTABLE";
9+
10+
package protobuf_editionstesting;
11+
12+
import "csharp/protos/unittest_import_proto3.proto";
13+
14+
import option "csharp/google/protobuf/c_sharp_features.proto";
15+
option features.(pb.csharp).nullable_reference_types = true;
16+
option csharp_namespace = "Google.Protobuf.TestProtos";
17+
18+
// This file is used for testing NRT support in C#, both in terms of
19+
// manual inspection of the generated code, and checking that it compiles.
20+
// It exercises many aspects of protobuf, but does not attempt to be
21+
// comprehensive in checking (for example) all primitive numeric types.
22+
23+
message NrtMessage {
24+
25+
protobuf_unittest_import.ImportMessage proto3_import_message = 1;
26+
OtherNrtMessage other_message = 2;
27+
28+
message NestedMessage {
29+
string text = 1;
30+
}
31+
}
32+
33+
message OtherNrtMessage {
34+
string text = 1;
35+
}
36+
37+
enum NrtEnum {
38+
NRT_ENUM_UNSPECIFIED = 0;
39+
FOO = 1;
40+
BAR = 2;
41+
}

src/google/protobuf/compiler/csharp/csharp_field_base.cc

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414

1515
#include "absl/log/absl_log.h"
1616
#include "google/protobuf/compiler/code_generator.h"
17+
#include "google/protobuf/compiler/csharp/csharp_generator.h"
1718
#include "google/protobuf/compiler/csharp/csharp_helpers.h"
1819
#include "google/protobuf/compiler/csharp/names.h"
1920
#include "google/protobuf/descriptor.h"
@@ -115,6 +116,9 @@ void FieldGeneratorBase::SetCommonFieldVariables(
115116
absl::StrCat("other.", (*variables)["property_name"],
116117
" != ", (*variables)["default_value"])});
117118
}
119+
// This isn't valid everywhere, but we assume that the code which uses
120+
// the variable will only do so in a context where the annotation is valid.
121+
(*variables)["nrt_annotation"] = nrt_enabled_ ? "?" : "";
118122
}
119123

120124
void FieldGeneratorBase::SetCommonOneofFieldVariables(
@@ -129,13 +133,19 @@ void FieldGeneratorBase::SetCommonOneofFieldVariables(
129133
}
130134
(*variables)["oneof_case_name"] = oneof_case_name();
131135
(*variables)["oneof_property_name"] = oneof_property_name();
136+
// This isn't valid everywhere, but we assume that the code which uses
137+
// the variable will only do so in a context where the annotation is valid.
138+
(*variables)["nrt_annotation"] = nrt_enabled_ ? "?" : "";
132139
}
133140

134141
FieldGeneratorBase::FieldGeneratorBase(const FieldDescriptor* descriptor,
135142
int presenceIndex, const Options* options)
136143
: SourceGeneratorBase(options),
137144
descriptor_(descriptor),
138145
presenceIndex_(presenceIndex) {
146+
nrt_enabled_ = Generator::GetResolvedSourceFeatures(*descriptor->file())
147+
.GetExtension(pb::csharp)
148+
.nullable_reference_types();
139149
SetCommonFieldVariables(&variables_);
140150
}
141151

@@ -216,10 +226,12 @@ std::string FieldGeneratorBase::type_name(const FieldDescriptor* descriptor) {
216226
const FieldDescriptor* wrapped_field =
217227
descriptor->message_type()->field(0);
218228
std::string wrapped_field_type_name = type_name(wrapped_field);
219-
// String and ByteString go to the same type; other wrapped types
220-
// go to the nullable equivalent.
221-
if (wrapped_field->type() == FieldDescriptor::TYPE_STRING ||
222-
wrapped_field->type() == FieldDescriptor::TYPE_BYTES) {
229+
// String and ByteString go to the same type (unless nullable reference
230+
// type is supported for this field); other wrapped types go to the
231+
// nullable equivalent.
232+
if (!nrt_enabled_ &&
233+
(wrapped_field->type() == FieldDescriptor::TYPE_STRING ||
234+
wrapped_field->type() == FieldDescriptor::TYPE_BYTES)) {
223235
return wrapped_field_type_name;
224236
} else {
225237
return absl::StrCat(wrapped_field_type_name, "?");

src/google/protobuf/compiler/csharp/csharp_field_base.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ class FieldGeneratorBase : public SourceGeneratorBase {
5555
protected:
5656
const FieldDescriptor* descriptor_;
5757
const int presenceIndex_;
58+
// NRT = Nullable Reference Types
59+
bool nrt_enabled_;
5860
absl::flat_hash_map<absl::string_view, std::string> variables_;
5961

6062
void AddDeprecatedFlag(io::Printer* printer);

src/google/protobuf/compiler/csharp/csharp_generator.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@
1111
#define GOOGLE_PROTOBUF_COMPILER_CSHARP_CSHARP_GENERATOR_H__
1212

1313
#include <string>
14+
#include <vector>
1415

1516
#include "google/protobuf/compiler/code_generator.h"
17+
#include "google/protobuf/csharp/google/protobuf/c_sharp_features.pb.h"
1618
#include "google/protobuf/port_def.inc"
1719

1820
namespace google {
@@ -37,6 +39,11 @@ class PROTOC_EXPORT Generator : public CodeGenerator {
3739
Edition GetMinimumEdition() const override { return Edition::EDITION_PROTO2; }
3840
Edition GetMaximumEdition() const override { return Edition::EDITION_2024; }
3941
using CodeGenerator::GetEdition;
42+
43+
std::vector<const FieldDescriptor*> GetFeatureExtensions() const override {
44+
return {GetExtensionReflection(pb::csharp)};
45+
}
46+
using CodeGenerator::GetResolvedSourceFeatures;
4047
};
4148

4249
} // namespace csharp

src/google/protobuf/compiler/csharp/csharp_message.cc

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
#include "google/protobuf/compiler/csharp/csharp_doc_comment.h"
1919
#include "google/protobuf/compiler/csharp/csharp_enum.h"
2020
#include "google/protobuf/compiler/csharp/csharp_field_base.h"
21+
#include "google/protobuf/compiler/csharp/csharp_generator.h"
2122
#include "google/protobuf/compiler/csharp/csharp_helpers.h"
2223
#include "google/protobuf/compiler/csharp/csharp_options.h"
2324
#include "google/protobuf/compiler/csharp/names.h"
@@ -60,6 +61,9 @@ MessageGenerator::MessageGenerator(const Descriptor* descriptor,
6061
}
6162
}
6263
has_bit_field_count_ = (presence_bit_count + 31) / 32;
64+
nrt_enabled_ = Generator::GetResolvedSourceFeatures(*descriptor->file())
65+
.GetExtension(pb::csharp)
66+
.nullable_reference_types();
6367
}
6468

6569
MessageGenerator::~MessageGenerator() = default;
@@ -93,6 +97,7 @@ void MessageGenerator::Generate(io::Printer* printer) {
9397
absl::flat_hash_map<absl::string_view, std::string> vars;
9498
vars["class_name"] = class_name();
9599
vars["access_level"] = class_access_level();
100+
vars["nrt_annotation"] = nrt_enabled_ ? "?" : "";
96101

97102
WriteMessageDocComment(printer, options(), descriptor_);
98103
AddDeprecatedFlag(printer);
@@ -120,7 +125,8 @@ void MessageGenerator::Generate(io::Printer* printer) {
120125
"private static readonly pb::MessageParser<$class_name$> _parser = new "
121126
"pb::MessageParser<$class_name$>(() => new $class_name$());\n");
122127

123-
printer->Print("private pb::UnknownFieldSet _unknownFields;\n");
128+
printer->Print(
129+
vars, "private pb::UnknownFieldSet$nrt_annotation$ _unknownFields;\n");
124130

125131
if (has_extension_ranges_) {
126132
if (IsDescriptorProto(descriptor_->file())) {
@@ -256,7 +262,7 @@ void MessageGenerator::Generate(io::Printer* printer) {
256262
"TValue> extension) {\n"
257263
" return pb::ExtensionSet.Get(ref _extensions, extension);\n"
258264
"}\n"
259-
"public pbc::RepeatedField<TValue> "
265+
"public pbc::RepeatedField<TValue>$nrt_annotation$ "
260266
"GetExtension<TValue>(pb::RepeatedExtension<$class_name$, TValue> "
261267
"extension) {\n"
262268
" return pb::ExtensionSet.Get(ref _extensions, extension);\n"
@@ -314,6 +320,7 @@ void MessageGenerator::Generate(io::Printer* printer) {
314320
"\n");
315321
}
316322

323+
// FIXME
317324
if (descriptor_->extension_count() > 0) {
318325
printer->Print(vars,
319326
"#region Extensions\n"
@@ -417,16 +424,17 @@ void MessageGenerator::GenerateFreezingCode(io::Printer* printer) {}
417424
void MessageGenerator::GenerateFrameworkMethods(io::Printer* printer) {
418425
absl::flat_hash_map<absl::string_view, std::string> vars;
419426
vars["class_name"] = class_name();
427+
vars["nrt_annotation"] = nrt_enabled_ ? "?" : "";
420428

421429
// Equality
422430
WriteGeneratedCodeAttributes(printer);
423431
printer->Print(vars,
424-
"public override bool Equals(object other) {\n"
432+
"public override bool Equals(object$nrt_annotation$ other) {\n"
425433
" return Equals(other as $class_name$);\n"
426434
"}\n\n");
427435
WriteGeneratedCodeAttributes(printer);
428436
printer->Print(vars,
429-
"public bool Equals($class_name$ other) {\n"
437+
"public bool Equals($class_name$$nrt_annotation$ other) {\n"
430438
" if (ReferenceEquals(other, null)) {\n"
431439
" return false;\n"
432440
" }\n"

src/google/protobuf/compiler/csharp/csharp_message.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class MessageGenerator : public SourceGeneratorBase {
4040
std::vector<const FieldDescriptor*> fields_by_number_;
4141
int has_bit_field_count_;
4242
bool has_extension_ranges_;
43+
// NRT = Nullable Reference Types
44+
bool nrt_enabled_;
4345

4446
void GenerateMessageSerializationMethods(io::Printer* printer);
4547
void GenerateWriteToBody(io::Printer* printer, bool use_write_context);

src/google/protobuf/compiler/csharp/csharp_message_field.cc

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -35,19 +35,17 @@ MessageFieldGenerator::MessageFieldGenerator(const FieldDescriptor* descriptor,
3535
MessageFieldGenerator::~MessageFieldGenerator() = default;
3636

3737
void MessageFieldGenerator::GenerateMembers(io::Printer* printer) {
38-
printer->Print(
39-
variables_,
40-
"private $type_name$ $name$_;\n");
38+
printer->Print(variables_, "private $type_name$$nrt_annotation$ $name$_;\n");
4139
WritePropertyDocComment(printer, options(), descriptor_);
4240
AddPublicMemberAttributes(printer);
4341
printer->Print(
44-
variables_,
45-
"$access_level$ $type_name$ $property_name$ {\n"
46-
" get { return $name$_; }\n"
47-
" set {\n"
48-
" $name$_ = value;\n"
49-
" }\n"
50-
"}\n");
42+
variables_,
43+
"$access_level$ $type_name$$nrt_annotation$ $property_name$ {\n"
44+
" get { return $name$_; }\n"
45+
" set {\n"
46+
" $name$_ = value;\n"
47+
" }\n"
48+
"}\n");
5149
if (SupportsPresenceApi(descriptor_)) {
5250
printer->Print(
5351
variables_,
@@ -189,14 +187,17 @@ void MessageOneofFieldGenerator::GenerateMembers(io::Printer* printer) {
189187
WritePropertyDocComment(printer, options(), descriptor_);
190188
AddPublicMemberAttributes(printer);
191189
printer->Print(
192-
variables_,
193-
"$access_level$ $type_name$ $property_name$ {\n"
194-
" get { return $has_property_check$ ? ($type_name$) $oneof_name$_ : null; }\n"
195-
" set {\n"
196-
" $oneof_name$_ = value;\n"
197-
" $oneof_name$Case_ = value == null ? $oneof_property_name$OneofCase.None : $oneof_property_name$OneofCase.$oneof_case_name$;\n"
198-
" }\n"
199-
"}\n");
190+
variables_,
191+
"$access_level$ $type_name$$nrt_annotation$ $property_name$ {\n"
192+
" get { return $has_property_check$ ? ($type_name$) $oneof_name$_ : "
193+
"null; }\n"
194+
" set {\n"
195+
" $oneof_name$_ = value;\n"
196+
" $oneof_name$Case_ = value == null ? "
197+
"$oneof_property_name$OneofCase.None : "
198+
"$oneof_property_name$OneofCase.$oneof_case_name$;\n"
199+
" }\n"
200+
"}\n");
200201
if (SupportsPresenceApi(descriptor_)) {
201202
printer->Print(
202203
variables_,

0 commit comments

Comments
 (0)