1// Protocol Buffers - Google's data interchange format
2// Copyright 2008 Google Inc. All rights reserved.
3// https://developers.google.com/protocol-buffers/
4//
5// Redistribution and use in source and binary forms, with or without
6// modification, are permitted provided that the following conditions are
7// met:
8//
9// * Redistributions of source code must retain the above copyright
10// notice, this list of conditions and the following disclaimer.
11// * Redistributions in binary form must reproduce the above
12// copyright notice, this list of conditions and the following disclaimer
13// in the documentation and/or other materials provided with the
14// distribution.
15// * Neither the name of Google Inc. nor the names of its
16// contributors may be used to endorse or promote products derived from
17// this software without specific prior written permission.
18//
19// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30
31// Author: kenton@google.com (Kenton Varda)
32// Based on original Protocol Buffers design by
33// Sanjay Ghemawat, Jeff Dean, and others.
34
35#include <algorithm>
36#include <limits>
37#include <vector>
38#include <sstream>
39
40#include <google/protobuf/compiler/csharp/csharp_helpers.h>
41#include <google/protobuf/compiler/csharp/csharp_names.h>
42#include <google/protobuf/descriptor.pb.h>
43#include <google/protobuf/io/printer.h>
44#include <google/protobuf/wire_format.h>
45#include <google/protobuf/stubs/strutil.h>
46
47#include <google/protobuf/compiler/csharp/csharp_field_base.h>
48#include <google/protobuf/compiler/csharp/csharp_enum_field.h>
49#include <google/protobuf/compiler/csharp/csharp_map_field.h>
50#include <google/protobuf/compiler/csharp/csharp_message_field.h>
51#include <google/protobuf/compiler/csharp/csharp_options.h>
52#include <google/protobuf/compiler/csharp/csharp_primitive_field.h>
53#include <google/protobuf/compiler/csharp/csharp_repeated_enum_field.h>
54#include <google/protobuf/compiler/csharp/csharp_repeated_message_field.h>
55#include <google/protobuf/compiler/csharp/csharp_repeated_primitive_field.h>
56#include <google/protobuf/compiler/csharp/csharp_wrapper_field.h>
57
58namespace google {
59namespace protobuf {
60namespace compiler {
61namespace csharp {
62
63CSharpType GetCSharpType(FieldDescriptor::Type type) {
64 switch (type) {
65 case FieldDescriptor::TYPE_INT32:
66 return CSHARPTYPE_INT32;
67 case FieldDescriptor::TYPE_INT64:
68 return CSHARPTYPE_INT64;
69 case FieldDescriptor::TYPE_UINT32:
70 return CSHARPTYPE_UINT32;
71 case FieldDescriptor::TYPE_UINT64:
72 return CSHARPTYPE_UINT32;
73 case FieldDescriptor::TYPE_SINT32:
74 return CSHARPTYPE_INT32;
75 case FieldDescriptor::TYPE_SINT64:
76 return CSHARPTYPE_INT64;
77 case FieldDescriptor::TYPE_FIXED32:
78 return CSHARPTYPE_UINT32;
79 case FieldDescriptor::TYPE_FIXED64:
80 return CSHARPTYPE_UINT64;
81 case FieldDescriptor::TYPE_SFIXED32:
82 return CSHARPTYPE_INT32;
83 case FieldDescriptor::TYPE_SFIXED64:
84 return CSHARPTYPE_INT64;
85 case FieldDescriptor::TYPE_FLOAT:
86 return CSHARPTYPE_FLOAT;
87 case FieldDescriptor::TYPE_DOUBLE:
88 return CSHARPTYPE_DOUBLE;
89 case FieldDescriptor::TYPE_BOOL:
90 return CSHARPTYPE_BOOL;
91 case FieldDescriptor::TYPE_ENUM:
92 return CSHARPTYPE_ENUM;
93 case FieldDescriptor::TYPE_STRING:
94 return CSHARPTYPE_STRING;
95 case FieldDescriptor::TYPE_BYTES:
96 return CSHARPTYPE_BYTESTRING;
97 case FieldDescriptor::TYPE_GROUP:
98 return CSHARPTYPE_MESSAGE;
99 case FieldDescriptor::TYPE_MESSAGE:
100 return CSHARPTYPE_MESSAGE;
101
102 // No default because we want the compiler to complain if any new
103 // types are added.
104 }
105 GOOGLE_LOG(FATAL)<< "Can't get here.";
106 return (CSharpType) -1;
107}
108
109std::string StripDotProto(const std::string& proto_file) {
110 int lastindex = proto_file.find_last_of(s: ".");
111 return proto_file.substr(pos: 0, n: lastindex);
112}
113
114std::string GetFileNamespace(const FileDescriptor* descriptor) {
115 if (descriptor->options().has_csharp_namespace()) {
116 return descriptor->options().csharp_namespace();
117 }
118 return UnderscoresToCamelCase(input: descriptor->package(), cap_next_letter: true, preserve_period: true);
119}
120
121// Returns the Pascal-cased last part of the proto file. For example,
122// input of "google/protobuf/foo_bar.proto" would result in "FooBar".
123std::string GetFileNameBase(const FileDescriptor* descriptor) {
124 std::string proto_file = descriptor->name();
125 int lastslash = proto_file.find_last_of(s: "/");
126 std::string base = proto_file.substr(pos: lastslash + 1);
127 return UnderscoresToPascalCase(input: StripDotProto(proto_file: base));
128}
129
130std::string GetReflectionClassUnqualifiedName(const FileDescriptor* descriptor) {
131 // TODO: Detect collisions with existing messages,
132 // and append an underscore if necessary.
133 return GetFileNameBase(descriptor) + "Reflection";
134}
135
136std::string GetExtensionClassUnqualifiedName(const FileDescriptor* descriptor) {
137 // TODO: Detect collisions with existing messages,
138 // and append an underscore if necessary.
139 return GetFileNameBase(descriptor) + "Extensions";
140}
141
142// TODO(jtattermusch): can we reuse a utility function?
143std::string UnderscoresToCamelCase(const std::string& input,
144 bool cap_next_letter,
145 bool preserve_period) {
146 std::string result;
147 // Note: I distrust ctype.h due to locales.
148 for (int i = 0; i < input.size(); i++) {
149 if ('a' <= input[i] && input[i] <= 'z') {
150 if (cap_next_letter) {
151 result += input[i] + ('A' - 'a');
152 } else {
153 result += input[i];
154 }
155 cap_next_letter = false;
156 } else if ('A' <= input[i] && input[i] <= 'Z') {
157 if (i == 0 && !cap_next_letter) {
158 // Force first letter to lower-case unless explicitly told to
159 // capitalize it.
160 result += input[i] + ('a' - 'A');
161 } else {
162 // Capital letters after the first are left as-is.
163 result += input[i];
164 }
165 cap_next_letter = false;
166 } else if ('0' <= input[i] && input[i] <= '9') {
167 result += input[i];
168 cap_next_letter = true;
169 } else {
170 cap_next_letter = true;
171 if (input[i] == '.' && preserve_period) {
172 result += '.';
173 }
174 }
175 }
176 // Add a trailing "_" if the name should be altered.
177 if (input.size() > 0 && input[input.size() - 1] == '#') {
178 result += '_';
179 }
180 return result;
181}
182
183std::string UnderscoresToPascalCase(const std::string& input) {
184 return UnderscoresToCamelCase(input, cap_next_letter: true);
185}
186
187// Convert a string which is expected to be SHOUTY_CASE (but may not be *precisely* shouty)
188// into a PascalCase string. Precise rules implemented:
189
190// Previous input character Current character Case
191// Any Non-alphanumeric Skipped
192// None - first char of input Alphanumeric Upper
193// Non-letter (e.g. _ or 1) Alphanumeric Upper
194// Numeric Alphanumeric Upper
195// Lower letter Alphanumeric Same as current
196// Upper letter Alphanumeric Lower
197std::string ShoutyToPascalCase(const std::string& input) {
198 std::string result;
199 // Simple way of implementing "always start with upper"
200 char previous = '_';
201 for (int i = 0; i < input.size(); i++) {
202 char current = input[i];
203 if (!ascii_isalnum(c: current)) {
204 previous = current;
205 continue;
206 }
207 if (!ascii_isalnum(c: previous)) {
208 result += ascii_toupper(c: current);
209 } else if (ascii_isdigit(c: previous)) {
210 result += ascii_toupper(c: current);
211 } else if (ascii_islower(c: previous)) {
212 result += current;
213 } else {
214 result += ascii_tolower(c: current);
215 }
216 previous = current;
217 }
218 return result;
219}
220
221// Attempt to remove a prefix from a value, ignoring casing and skipping underscores.
222// (foo, foo_bar) => bar - underscore after prefix is skipped
223// (FOO, foo_bar) => bar - casing is ignored
224// (foo_bar, foobarbaz) => baz - underscore in prefix is ignored
225// (foobar, foo_barbaz) => baz - underscore in value is ignored
226// (foo, bar) => bar - prefix isn't matched; return original value
227std::string TryRemovePrefix(const std::string& prefix, const std::string& value) {
228 // First normalize to a lower-case no-underscores prefix to match against
229 std::string prefix_to_match = "";
230 for (size_t i = 0; i < prefix.size(); i++) {
231 if (prefix[i] != '_') {
232 prefix_to_match += ascii_tolower(c: prefix[i]);
233 }
234 }
235
236 // This keeps track of how much of value we've consumed
237 size_t prefix_index, value_index;
238 for (prefix_index = 0, value_index = 0;
239 prefix_index < prefix_to_match.size() && value_index < value.size();
240 value_index++) {
241 // Skip over underscores in the value
242 if (value[value_index] == '_') {
243 continue;
244 }
245 if (ascii_tolower(c: value[value_index]) != prefix_to_match[prefix_index++]) {
246 // Failed to match the prefix - bail out early.
247 return value;
248 }
249 }
250
251 // If we didn't finish looking through the prefix, we can't strip it.
252 if (prefix_index < prefix_to_match.size()) {
253 return value;
254 }
255
256 // Step over any underscores after the prefix
257 while (value_index < value.size() && value[value_index] == '_') {
258 value_index++;
259 }
260
261 // If there's nothing left (e.g. it was a prefix with only underscores afterwards), don't strip.
262 if (value_index == value.size()) {
263 return value;
264 }
265
266 return value.substr(pos: value_index);
267}
268
269// Format the enum value name in a pleasant way for C#:
270// - Strip the enum name as a prefix if possible
271// - Convert to PascalCase.
272// For example, an enum called Color with a value of COLOR_BLUE should
273// result in an enum value in C# called just Blue
274std::string GetEnumValueName(const std::string& enum_name, const std::string& enum_value_name) {
275 std::string stripped = TryRemovePrefix(prefix: enum_name, value: enum_value_name);
276 std::string result = ShoutyToPascalCase(input: stripped);
277 // Just in case we have an enum name of FOO and a value of FOO_2... make sure the returned
278 // string is a valid identifier.
279 if (ascii_isdigit(c: result[0])) {
280 result = "_" + result;
281 }
282 return result;
283}
284
285uint GetGroupEndTag(const Descriptor* descriptor) {
286 const Descriptor* containing_type = descriptor->containing_type();
287 if (containing_type != NULL) {
288 const FieldDescriptor* field;
289 for (int i = 0; i < containing_type->field_count(); i++) {
290 field = containing_type->field(index: i);
291 if (field->type() == FieldDescriptor::Type::TYPE_GROUP &&
292 field->message_type() == descriptor) {
293 return internal::WireFormatLite::MakeTag(
294 field_number: field->number(), type: internal::WireFormatLite::WIRETYPE_END_GROUP);
295 }
296 }
297 for (int i = 0; i < containing_type->extension_count(); i++) {
298 field = containing_type->extension(index: i);
299 if (field->type() == FieldDescriptor::Type::TYPE_GROUP &&
300 field->message_type() == descriptor) {
301 return internal::WireFormatLite::MakeTag(
302 field_number: field->number(), type: internal::WireFormatLite::WIRETYPE_END_GROUP);
303 }
304 }
305 } else {
306 const FileDescriptor* containing_file = descriptor->file();
307 if (containing_file != NULL) {
308 const FieldDescriptor* field;
309 for (int i = 0; i < containing_file->extension_count(); i++) {
310 field = containing_file->extension(index: i);
311 if (field->type() == FieldDescriptor::Type::TYPE_GROUP &&
312 field->message_type() == descriptor) {
313 return internal::WireFormatLite::MakeTag(
314 field_number: field->number(), type: internal::WireFormatLite::WIRETYPE_END_GROUP);
315 }
316 }
317 }
318 }
319
320 return 0;
321}
322
323std::string ToCSharpName(const std::string& name, const FileDescriptor* file) {
324 std::string result = GetFileNamespace(descriptor: file);
325 if (!result.empty()) {
326 result += '.';
327 }
328 std::string classname;
329 if (file->package().empty()) {
330 classname = name;
331 } else {
332 // Strip the proto package from full_name since we've replaced it with
333 // the C# namespace.
334 classname = name.substr(pos: file->package().size() + 1);
335 }
336 result += StringReplace(s: classname, oldsub: ".", newsub: ".Types.", replace_all: true);
337 return "global::" + result;
338}
339
340std::string GetReflectionClassName(const FileDescriptor* descriptor) {
341 std::string result = GetFileNamespace(descriptor);
342 if (!result.empty()) {
343 result += '.';
344 }
345 result += GetReflectionClassUnqualifiedName(descriptor);
346 return "global::" + result;
347}
348
349std::string GetFullExtensionName(const FieldDescriptor* descriptor) {
350 if (descriptor->extension_scope()) {
351 return GetClassName(descriptor: descriptor->extension_scope()) + ".Extensions." + GetPropertyName(descriptor);
352 }
353 else {
354 return GetExtensionClassUnqualifiedName(descriptor: descriptor->file()) + "." + GetPropertyName(descriptor);
355 }
356}
357
358std::string GetClassName(const Descriptor* descriptor) {
359 return ToCSharpName(name: descriptor->full_name(), file: descriptor->file());
360}
361
362std::string GetClassName(const EnumDescriptor* descriptor) {
363 return ToCSharpName(name: descriptor->full_name(), file: descriptor->file());
364}
365
366// Groups are hacky: The name of the field is just the lower-cased name
367// of the group type. In C#, though, we would like to retain the original
368// capitalization of the type name.
369std::string GetFieldName(const FieldDescriptor* descriptor) {
370 if (descriptor->type() == FieldDescriptor::TYPE_GROUP) {
371 return descriptor->message_type()->name();
372 } else {
373 return descriptor->name();
374 }
375}
376
377std::string GetFieldConstantName(const FieldDescriptor* field) {
378 return GetPropertyName(descriptor: field) + "FieldNumber";
379}
380
381std::string GetPropertyName(const FieldDescriptor* descriptor) {
382 // TODO(jtattermusch): consider introducing csharp_property_name field option
383 std::string property_name = UnderscoresToPascalCase(input: GetFieldName(descriptor));
384 // Avoid either our own type name or reserved names. Note that not all names
385 // are reserved - a field called to_string, write_to etc would still cause a problem.
386 // There are various ways of ending up with naming collisions, but we try to avoid obvious
387 // ones.
388 if (property_name == descriptor->containing_type()->name()
389 || property_name == "Types"
390 || property_name == "Descriptor") {
391 property_name += "_";
392 }
393 return property_name;
394}
395
396std::string GetOneofCaseName(const FieldDescriptor* descriptor) {
397 // The name in a oneof case enum is the same as for the property, but as we always have a "None"
398 // value as well, we need to reserve that by appending an underscore.
399 std::string property_name = GetPropertyName(descriptor);
400 return property_name == "None" ? "None_" : property_name;
401}
402
403std::string GetOutputFile(const FileDescriptor* descriptor,
404 const std::string file_extension,
405 const bool generate_directories,
406 const std::string base_namespace,
407 std::string* error) {
408 std::string relative_filename = GetFileNameBase(descriptor) + file_extension;
409 if (!generate_directories) {
410 return relative_filename;
411 }
412 std::string ns = GetFileNamespace(descriptor);
413 std::string namespace_suffix = ns;
414 if (!base_namespace.empty()) {
415 // Check that the base_namespace is either equal to or a leading part of
416 // the file namespace. This isn't just a simple prefix; "Foo.B" shouldn't
417 // be regarded as a prefix of "Foo.Bar". The simplest option is to add "."
418 // to both.
419 std::string extended_ns = ns + ".";
420 if (extended_ns.find(str: base_namespace + ".") != 0) {
421 *error = "Namespace " + ns + " is not a prefix namespace of base namespace " + base_namespace;
422 return ""; // This will be ignored, because we've set an error.
423 }
424 namespace_suffix = ns.substr(pos: base_namespace.length());
425 if (namespace_suffix.find(s: ".") == 0) {
426 namespace_suffix = namespace_suffix.substr(pos: 1);
427 }
428 }
429
430 std::string namespace_dir = StringReplace(s: namespace_suffix, oldsub: ".", newsub: "/", replace_all: true);
431 if (!namespace_dir.empty()) {
432 namespace_dir += "/";
433 }
434 return namespace_dir + relative_filename;
435}
436
437// TODO: c&p from Java protoc plugin
438// For encodings with fixed sizes, returns that size in bytes. Otherwise
439// returns -1.
440int GetFixedSize(FieldDescriptor::Type type) {
441 switch (type) {
442 case FieldDescriptor::TYPE_INT32 : return -1;
443 case FieldDescriptor::TYPE_INT64 : return -1;
444 case FieldDescriptor::TYPE_UINT32 : return -1;
445 case FieldDescriptor::TYPE_UINT64 : return -1;
446 case FieldDescriptor::TYPE_SINT32 : return -1;
447 case FieldDescriptor::TYPE_SINT64 : return -1;
448 case FieldDescriptor::TYPE_FIXED32 : return internal::WireFormatLite::kFixed32Size;
449 case FieldDescriptor::TYPE_FIXED64 : return internal::WireFormatLite::kFixed64Size;
450 case FieldDescriptor::TYPE_SFIXED32: return internal::WireFormatLite::kSFixed32Size;
451 case FieldDescriptor::TYPE_SFIXED64: return internal::WireFormatLite::kSFixed64Size;
452 case FieldDescriptor::TYPE_FLOAT : return internal::WireFormatLite::kFloatSize;
453 case FieldDescriptor::TYPE_DOUBLE : return internal::WireFormatLite::kDoubleSize;
454
455 case FieldDescriptor::TYPE_BOOL : return internal::WireFormatLite::kBoolSize;
456 case FieldDescriptor::TYPE_ENUM : return -1;
457
458 case FieldDescriptor::TYPE_STRING : return -1;
459 case FieldDescriptor::TYPE_BYTES : return -1;
460 case FieldDescriptor::TYPE_GROUP : return -1;
461 case FieldDescriptor::TYPE_MESSAGE : return -1;
462
463 // No default because we want the compiler to complain if any new
464 // types are added.
465 }
466 GOOGLE_LOG(FATAL) << "Can't get here.";
467 return -1;
468}
469
470static const char base64_chars[] =
471 "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
472
473std::string StringToBase64(const std::string& input) {
474 std::string result;
475 size_t remaining = input.size();
476 const unsigned char *src = (const unsigned char*) input.c_str();
477 while (remaining > 2) {
478 result += base64_chars[src[0] >> 2];
479 result += base64_chars[((src[0] & 0x3) << 4) | (src[1] >> 4)];
480 result += base64_chars[((src[1] & 0xf) << 2) | (src[2] >> 6)];
481 result += base64_chars[src[2] & 0x3f];
482 remaining -= 3;
483 src += 3;
484 }
485 switch (remaining) {
486 case 2:
487 result += base64_chars[src[0] >> 2];
488 result += base64_chars[((src[0] & 0x3) << 4) | (src[1] >> 4)];
489 result += base64_chars[(src[1] & 0xf) << 2];
490 result += '=';
491 src += 2;
492 break;
493 case 1:
494 result += base64_chars[src[0] >> 2];
495 result += base64_chars[((src[0] & 0x3) << 4)];
496 result += '=';
497 result += '=';
498 src += 1;
499 break;
500 }
501 return result;
502}
503
504std::string FileDescriptorToBase64(const FileDescriptor* descriptor) {
505 std::string fdp_bytes;
506 FileDescriptorProto fdp;
507 descriptor->CopyTo(proto: &fdp);
508 fdp.SerializeToString(output: &fdp_bytes);
509 return StringToBase64(input: fdp_bytes);
510}
511
512FieldGeneratorBase* CreateFieldGenerator(const FieldDescriptor* descriptor,
513 int presenceIndex,
514 const Options* options) {
515 switch (descriptor->type()) {
516 case FieldDescriptor::TYPE_GROUP:
517 case FieldDescriptor::TYPE_MESSAGE:
518 if (descriptor->is_repeated()) {
519 if (descriptor->is_map()) {
520 return new MapFieldGenerator(descriptor, presenceIndex, options);
521 } else {
522 return new RepeatedMessageFieldGenerator(descriptor, presenceIndex, options);
523 }
524 } else {
525 if (IsWrapperType(descriptor)) {
526 if (descriptor->real_containing_oneof()) {
527 return new WrapperOneofFieldGenerator(descriptor, presenceIndex, options);
528 } else {
529 return new WrapperFieldGenerator(descriptor, presenceIndex, options);
530 }
531 } else {
532 if (descriptor->real_containing_oneof()) {
533 return new MessageOneofFieldGenerator(descriptor, presenceIndex, options);
534 } else {
535 return new MessageFieldGenerator(descriptor, presenceIndex, options);
536 }
537 }
538 }
539 case FieldDescriptor::TYPE_ENUM:
540 if (descriptor->is_repeated()) {
541 return new RepeatedEnumFieldGenerator(descriptor, presenceIndex, options);
542 } else {
543 if (descriptor->real_containing_oneof()) {
544 return new EnumOneofFieldGenerator(descriptor, presenceIndex, options);
545 } else {
546 return new EnumFieldGenerator(descriptor, presenceIndex, options);
547 }
548 }
549 default:
550 if (descriptor->is_repeated()) {
551 return new RepeatedPrimitiveFieldGenerator(descriptor, presenceIndex, options);
552 } else {
553 if (descriptor->real_containing_oneof()) {
554 return new PrimitiveOneofFieldGenerator(descriptor, presenceIndex, options);
555 } else {
556 return new PrimitiveFieldGenerator(descriptor, presenceIndex, options);
557 }
558 }
559 }
560}
561
562bool IsNullable(const FieldDescriptor* descriptor) {
563 if (descriptor->is_repeated()) {
564 return true;
565 }
566
567 switch (descriptor->type()) {
568 case FieldDescriptor::TYPE_ENUM:
569 case FieldDescriptor::TYPE_DOUBLE:
570 case FieldDescriptor::TYPE_FLOAT:
571 case FieldDescriptor::TYPE_INT64:
572 case FieldDescriptor::TYPE_UINT64:
573 case FieldDescriptor::TYPE_INT32:
574 case FieldDescriptor::TYPE_FIXED64:
575 case FieldDescriptor::TYPE_FIXED32:
576 case FieldDescriptor::TYPE_BOOL:
577 case FieldDescriptor::TYPE_UINT32:
578 case FieldDescriptor::TYPE_SFIXED32:
579 case FieldDescriptor::TYPE_SFIXED64:
580 case FieldDescriptor::TYPE_SINT32:
581 case FieldDescriptor::TYPE_SINT64:
582 return false;
583
584 case FieldDescriptor::TYPE_MESSAGE:
585 case FieldDescriptor::TYPE_GROUP:
586 case FieldDescriptor::TYPE_STRING:
587 case FieldDescriptor::TYPE_BYTES:
588 return true;
589
590 default:
591 GOOGLE_LOG(FATAL) << "Unknown field type.";
592 return true;
593 }
594}
595
596} // namespace csharp
597} // namespace compiler
598} // namespace protobuf
599} // namespace google
600