blob: 9b14e985f8de2bdb144f043f1356aab37589c7e9 [file] [log] [blame]
// Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// @dart=2.11
part of '../protoc.dart';
class ProtobufField {
static final RegExp _hexLiteralRegex =
RegExp(r'^0x[0-9a-f]+$', multiLine: false, caseSensitive: false);
static final RegExp _integerLiteralRegex = RegExp(r'^[+-]?[0-9]+$');
static final RegExp _decimalLiteralRegexA = RegExp(
r'^[+-]?([0-9]*)\.[0-9]+(e[+-]?[0-9]+)?$',
multiLine: false,
caseSensitive: false);
static final RegExp _decimalLiteralRegexB = RegExp(
r'^[+-]?[0-9]+e[+-]?[0-9]+$',
multiLine: false,
caseSensitive: false);
final FieldDescriptorProto descriptor;
/// Dart names within a GeneratedMessage or `null` for an extension.
final FieldNames memberNames;
final String fullName;
final BaseType baseType;
ProtobufField.message(
FieldNames names, ProtobufContainer parent, GenerationContext ctx)
: this._(names.descriptor, names, parent, ctx);
ProtobufField.extension(FieldDescriptorProto descriptor,
ProtobufContainer parent, GenerationContext ctx)
: this._(descriptor, null, parent, ctx);
ProtobufField._(this.descriptor, FieldNames dartNames,
ProtobufContainer parent, GenerationContext ctx)
: memberNames = dartNames,
fullName = '${parent.fullName}.${descriptor.name}',
baseType = BaseType(descriptor, ctx);
/// The index of this field in MessageGenerator.fieldList.
///
/// `null` for an extension.
int get index => memberNames?.index;
String get quotedProtoName =>
(_unCamelCase(descriptor.jsonName) == descriptor.name)
? null
: "'${descriptor.name}'";
/// The position of this field as it appeared in the original DescriptorProto.
int get sourcePosition => memberNames.sourcePosition;
/// True if the field is to be encoded with [deprecated = true] encoding.
bool get isDeprecated => descriptor.options?.deprecated;
bool get isRequired =>
descriptor.label == FieldDescriptorProto_Label.LABEL_REQUIRED;
bool get isRepeated =>
descriptor.label == FieldDescriptorProto_Label.LABEL_REPEATED;
/// True if the field is to be encoded with [packed=true] encoding.
bool get isPacked =>
isRepeated && descriptor.options != null && descriptor.options.packed;
/// Whether the field has the `overrideGetter` annotation set to true.
bool get overridesGetter => _hasBooleanOption(Dart_options.overrideGetter);
/// Whether the field has the `overrideSetter` annotation set to true.
bool get overridesSetter => _hasBooleanOption(Dart_options.overrideSetter);
/// Whether the field has the `overrideHasMethod` annotation set to true.
bool get overridesHasMethod =>
_hasBooleanOption(Dart_options.overrideHasMethod);
/// Whether the field has the `overrideClearMethod` annotation set to true.
bool get overridesClearMethod =>
_hasBooleanOption(Dart_options.overrideClearMethod);
/// True if this field uses the Int64 from the fixnum package.
bool get needsFixnumImport =>
baseType.unprefixed == '$_fixnumImportPrefix.Int64';
/// True if this field is a map field definition:
/// `map<key_type, value_type> map_field = N`.
bool get isMapField {
if (!isRepeated || !baseType.isMessage) return false;
final generator = baseType.generator as MessageGenerator;
return generator._descriptor.options.hasMapEntry();
}
// `true` if this field should have a `hazzer` generated.
bool get hasPresence {
if (isRepeated) return false;
return true;
// TODO(sigurdm): to provide the correct semantics for non-optional proto3
// fields would need something like the following:
// return baseType.isMessage ||
// descriptor.proto3Optional ||
// parent.fileGen.descriptor.syntax == "proto2";
//
// This change would break any accidental uses of the proto3 hazzers, and
// would require some clean-up.
//
// We could consider keeping hazzers for proto3-oneof fields. There they
// seem useful and not breaking proto3 semantics, and dart protobuf uses it
// for example in package:protobuf/src/protobuf/mixins/well_known.dart.
}
/// Returns the expression to use for the Dart type.
///
/// This will be a List for repeated types.
/// [fileGen] represents the .proto file where we are generating code.
String getDartType(FileGenerator fileGen) {
if (isMapField) {
final d = baseType.generator as MessageGenerator;
var keyType = d._fieldList[0].baseType.getDartType(fileGen);
var valueType = d._fieldList[1].baseType.getDartType(fileGen);
return '$coreImportPrefix.Map<$keyType, $valueType>';
}
if (isRepeated) return baseType.getRepeatedDartType(fileGen);
return baseType.getDartType(fileGen);
}
/// Returns the tag number of the underlying proto field.
int get number => descriptor.number;
/// Returns the constant in PbFieldType corresponding to this type.
String get typeConstant {
var prefix = 'O';
if (isRequired) {
prefix = 'Q';
} else if (isPacked) {
prefix = 'K';
} else if (isRepeated) {
prefix = 'P';
}
return '$protobufImportPrefix.PbFieldType.' +
prefix +
baseType.typeConstantSuffix;
}
static String _formatArguments(
List<String> positionals, Map<String, String> named) {
final args = positionals.toList();
while (args.last == null) {
args.removeLast();
}
for (var i = 0; i < args.length; i++) {
if (args[i] == null) {
args[i] = 'null';
}
}
named.forEach((key, value) {
if (value != null) {
args.add('$key: $value');
}
});
return args.join(', ');
}
/// Returns Dart code adding this field to a BuilderInfo object.
/// The call will start with ".." and a method name.
/// [fileGen] represents the .proto file where the code will be evaluated.
String generateBuilderInfoCall(FileGenerator fileGen, String package) {
assert(descriptor.hasJsonName());
var quotedName = configurationDependent(
'protobuf.omit_field_names',
quoted(descriptor.jsonName),
);
var type = baseType.getDartType(fileGen);
String invocation;
var args = <String>[];
var named = <String, String>{'protoName': quotedProtoName};
args.add('$number');
args.add(quotedName);
if (isMapField) {
final generator = baseType.generator as MessageGenerator;
var key = generator._fieldList[0];
var value = generator._fieldList[1];
var keyType = key.baseType.getDartType(fileGen);
var valueType = value.baseType.getDartType(fileGen);
invocation = 'm<$keyType, $valueType>';
named['entryClassName'] = "'${generator.messageName}'";
named['keyFieldType'] = key.typeConstant;
named['valueFieldType'] = value.typeConstant;
if (value.baseType.isMessage || value.baseType.isGroup) {
named['valueCreator'] = '$valueType.create';
}
if (value.baseType.isEnum) {
named['valueOf'] = '$valueType.valueOf';
named['enumValues'] = '$valueType.values';
named['defaultEnumValue'] = value.generateDefaultFunction(fileGen);
}
if (package != '') {
named['packageName'] =
'const $protobufImportPrefix.PackageName(\'$package\')';
}
} else if (isRepeated) {
if (typeConstant == '$protobufImportPrefix.PbFieldType.PS') {
invocation = 'pPS';
} else {
args.add(typeConstant);
if (baseType.isMessage || baseType.isGroup || baseType.isEnum) {
invocation = 'pc<$type>';
} else {
invocation = 'p<$type>';
}
if (baseType.isMessage || baseType.isGroup) {
named['subBuilder'] = '$type.create';
} else if (baseType.isEnum) {
named['valueOf'] = '$type.valueOf';
named['enumValues'] = '$type.values';
named['defaultEnumValue'] = generateDefaultFunction(fileGen);
}
}
} else {
// Singular field.
var makeDefault = generateDefaultFunction(fileGen);
if (baseType.isEnum) {
args.add(typeConstant);
named['defaultOrMaker'] = makeDefault;
named['valueOf'] = '$type.valueOf';
named['enumValues'] = '$type.values';
invocation = 'e<$type>';
} else if (makeDefault == null) {
switch (type) {
case '$coreImportPrefix.String':
if (typeConstant == '$protobufImportPrefix.PbFieldType.OS') {
invocation = 'aOS';
} else if (typeConstant == '$protobufImportPrefix.PbFieldType.QS') {
invocation = 'aQS';
} else {
invocation = 'a<$type>';
args.add(typeConstant);
}
break;
case '$coreImportPrefix.bool':
if (typeConstant == '$protobufImportPrefix.PbFieldType.OB') {
invocation = 'aOB';
} else {
invocation = 'a<$type>';
args.add(typeConstant);
}
break;
default:
invocation = 'a<$type>';
args.add(typeConstant);
break;
}
} else {
if (makeDefault == '$_fixnumImportPrefix.Int64.ZERO' &&
type == '$_fixnumImportPrefix.Int64' &&
typeConstant == '$protobufImportPrefix.PbFieldType.O6') {
invocation = 'aInt64';
} else {
if (baseType.isMessage || baseType.isGroup) {
named['subBuilder'] = '$type.create';
}
if (baseType.isMessage) {
invocation = isRequired ? 'aQM<$type>' : 'aOM<$type>';
} else {
invocation = 'a<$type>';
named['defaultOrMaker'] = makeDefault;
args.add(typeConstant);
}
}
}
}
assert(invocation != null);
return '..$invocation(${_formatArguments(args, named)})';
}
/// Returns a Dart expression that evaluates to this field's default value.
///
/// Returns "null" if unavailable, in which case FieldSet._getDefault()
/// should be called instead.
String getDefaultExpr() {
if (isRepeated) return 'null';
switch (descriptor.type) {
case FieldDescriptorProto_Type.TYPE_BOOL:
return _getDefaultAsBoolExpr();
case FieldDescriptorProto_Type.TYPE_INT32:
case FieldDescriptorProto_Type.TYPE_UINT32:
case FieldDescriptorProto_Type.TYPE_SINT32:
case FieldDescriptorProto_Type.TYPE_FIXED32:
case FieldDescriptorProto_Type.TYPE_SFIXED32:
return _getDefaultAsInt32Expr('0');
case FieldDescriptorProto_Type.TYPE_STRING:
return _getDefaultAsStringExpr("''");
default:
return 'null';
}
}
/// Returns a function expression that returns the field's default value.
///
/// [fileGen] represents the .proto file where the expression will be
/// evaluated.
String generateDefaultFunction(FileGenerator fileGen) {
assert(!isRepeated);
switch (descriptor.type) {
case FieldDescriptorProto_Type.TYPE_BOOL:
return _getDefaultAsBoolExpr();
case FieldDescriptorProto_Type.TYPE_FLOAT:
case FieldDescriptorProto_Type.TYPE_DOUBLE:
if (!descriptor.hasDefaultValue()) {
return null;
} else if ('0.0' == descriptor.defaultValue ||
'0' == descriptor.defaultValue) {
return null;
} else if (descriptor.defaultValue == 'inf') {
return '$coreImportPrefix.double.infinity';
} else if (descriptor.defaultValue == '-inf') {
return '$coreImportPrefix.double.negativeInfinity';
} else if (descriptor.defaultValue == 'nan') {
return '$coreImportPrefix.double.nan';
} else if (_hexLiteralRegex.hasMatch(descriptor.defaultValue)) {
return '(${descriptor.defaultValue}).toDouble()';
} else if (_integerLiteralRegex.hasMatch(descriptor.defaultValue)) {
return '${descriptor.defaultValue}.0';
} else if (_decimalLiteralRegexA.hasMatch(descriptor.defaultValue) ||
_decimalLiteralRegexB.hasMatch(descriptor.defaultValue)) {
return descriptor.defaultValue;
}
throw _invalidDefaultValue;
case FieldDescriptorProto_Type.TYPE_INT32:
case FieldDescriptorProto_Type.TYPE_UINT32:
case FieldDescriptorProto_Type.TYPE_SINT32:
case FieldDescriptorProto_Type.TYPE_FIXED32:
case FieldDescriptorProto_Type.TYPE_SFIXED32:
return _getDefaultAsInt32Expr(null);
case FieldDescriptorProto_Type.TYPE_INT64:
case FieldDescriptorProto_Type.TYPE_UINT64:
case FieldDescriptorProto_Type.TYPE_SINT64:
case FieldDescriptorProto_Type.TYPE_FIXED64:
case FieldDescriptorProto_Type.TYPE_SFIXED64:
var value = '0';
if (descriptor.hasDefaultValue()) value = descriptor.defaultValue;
if (value == '0') return '$_fixnumImportPrefix.Int64.ZERO';
return "$protobufImportPrefix.parseLongInt('$value')";
case FieldDescriptorProto_Type.TYPE_STRING:
return _getDefaultAsStringExpr(null);
case FieldDescriptorProto_Type.TYPE_BYTES:
if (!descriptor.hasDefaultValue() || descriptor.defaultValue.isEmpty) {
return null;
}
var byteList = descriptor.defaultValue.codeUnits
.map((b) => '0x${b.toRadixString(16)}')
.join(',');
return '() => <$coreImportPrefix.int>[$byteList]';
case FieldDescriptorProto_Type.TYPE_GROUP:
case FieldDescriptorProto_Type.TYPE_MESSAGE:
return '${baseType.getDartType(fileGen)}.getDefault';
case FieldDescriptorProto_Type.TYPE_ENUM:
var className = baseType.getDartType(fileGen);
final gen = baseType.generator as EnumGenerator;
if (descriptor.hasDefaultValue() &&
descriptor.defaultValue.isNotEmpty) {
return '$className.${descriptor.defaultValue}';
} else if (gen._canonicalValues.isNotEmpty) {
return '$className.${gen.dartNames[gen._canonicalValues[0].name]}';
}
return null;
default:
throw _typeNotImplemented('generatedDefaultFunction');
}
}
String _getDefaultAsBoolExpr() {
if (descriptor.hasDefaultValue()) {
return descriptor.defaultValue;
} else {
return 'false';
}
}
String _getDefaultAsStringExpr(String noDefault) {
if (!descriptor.hasDefaultValue() || descriptor.defaultValue.isEmpty) {
return noDefault;
}
return quoted(descriptor.defaultValue);
}
String _getDefaultAsInt32Expr(String noDefault) {
if (descriptor.hasDefaultValue() && '0' != descriptor.defaultValue) {
return descriptor.defaultValue;
}
return noDefault;
}
bool _hasBooleanOption(Extension extension) =>
descriptor?.options?.getExtension(extension) as bool ?? false;
String get _invalidDefaultValue => 'dart-protoc-plugin:'
' invalid default value (${descriptor.defaultValue})'
' found in field $fullName';
String _typeNotImplemented(String methodName) => 'dart-protoc-plugin:'
' $methodName not implemented for type (${descriptor.type})'
' found in field $fullName';
static final RegExp _upperCase = RegExp('[A-Z]');
static String _unCamelCase(String name) {
return name.replaceAllMapped(
_upperCase, (match) => '_${match.group(0).toLowerCase()}');
}
}