diff --git a/README.md b/README.md index 0fda966..07fb4d6 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,67 @@ Then install python requirements. Python >= 3.5 is required. ```pip install -r requirements.txt``` +### Solidity Compatibility + +ProtoSolGenerator includes comprehensive compatibility features for Solidity development: + +#### Reserved Keyword Handling + +The generator automatically mangles protobuf field names that conflict with Solidity reserved keywords: + +- **Automatic detection**: Comprehensive list of Solidity reserved keywords including types (uint256, bytes32, etc.), control flow (if, while, etc.), and language features (contract, library, etc.) +- **Safe mangling**: Conflicting field names are automatically suffixed with `_field` (e.g., `uint32` → `uint32_field`) +- **No manual intervention**: Field name conflicts are resolved transparently during code generation + +#### Enhanced Parameter Processing + +The plugin supports both value-based and boolean flag parameters: + +```bash +# Boolean flags (automatically detected) +--sol_opt=gen_runtime +--sol_opt=use_builtin_enum + +# Value-based parameters +--sol_opt=gen_runtime=MyRuntime.sol +--sol_opt=solc_version=0.8.19 +``` + +This enables more flexible plugin configuration and better integration with build tools like buf. + +### Circular Dependency Resolution + +ProtoSolGenerator includes advanced handling for circular dependencies commonly found in validation schemas: + +#### Automatic Detection + +The generator automatically detects circular references in message structures, particularly in buf/validate types: + +- **FieldRules** ↔ **MapRules**: Field rules can reference map validation rules +- **FieldRules** ↔ **RepeatedRules**: Field rules can reference repeated field validation rules +- **MapRules** → **FieldRules**: Map rules reference field rules for key/value validation + +#### Bytes Field Solution + +Circular dependencies are resolved by using `bytes` fields with encode/decode helpers: + +```solidity +struct BufValidateFieldRules { + bytes map_field; // Circular reference: use encode/decode helpers + bytes repeated_field; // Circular reference: use encode/decode helpers + // ... other fields +} +``` + +The circular fields are marked with descriptive comments and can be serialized/deserialized using the standard protobuf encoding functions, maintaining full compatibility while avoiding Solidity compilation errors. + +#### No Manual Intervention + +Circular dependency resolution is automatic and transparent: +- Detected at generation time based on message and field type analysis +- Fields are automatically converted to `bytes` representation where needed +- Original protobuf semantics are preserved through encode/decode operations + ### Data Definition Users can use the custom type definition to define their data structure. Below is an example: diff --git a/protobuf-solidity/src/protoc/plugin/gen_decoder.py b/protobuf-solidity/src/protoc/plugin/gen_decoder.py index 73cf3ac..8dfb53d 100644 --- a/protobuf-solidity/src/protoc/plugin/gen_decoder.py +++ b/protobuf-solidity/src/protoc/plugin/gen_decoder.py @@ -1,4 +1,5 @@ import gen_util as util +from gen_util import mangle_solidity_field_name import gen_decoder_constants as decoder_constants from google.protobuf.descriptor import Descriptor, FieldDescriptor @@ -19,32 +20,32 @@ def gen_inner_field_decoder(field: FieldDescriptor, first_pass: bool, index: int return (decoder_constants.INNER_REPEATED_SCALAR_NUMERIC_FIELD_DECODER_FIRST_PASS).format( control = ("else " if index > 0 else ""), id = field.number, - field = field.name + field = mangle_solidity_field_name(field.name) ) elif first_pass: return (decoder_constants.INNER_REPEATED_NON_SCALAR_NUMERIC_FIELD_DECODER_FIRST_PASS).format( control = ("else " if index > 0 else ""), id = field.number, - field = field.name + field = mangle_solidity_field_name(field.name) ) elif scalar_numeric: return (decoder_constants.INNER_REPEATED_SCALAR_NUMERIC_FIELD_DECODER_SECOND_PASS).format( control = ("else " if index > 0 else ""), id = field.number, - field = field.name + field = mangle_solidity_field_name(field.name) ) else: return (decoder_constants.INNER_REPEATED_NON_SCALAR_NUMERIC_FIELD_DECODER_SECOND_PASS).format( control = ("else " if index > 0 else ""), id = field.number, - field = field.name + field = mangle_solidity_field_name(field.name) ) else: if first_pass: return (decoder_constants.INNER_FIELD_DECODER).format( control = ("else " if index > 0 else ""), id = field.number, - field = field.name + field = mangle_solidity_field_name(field.name) ) else: return "" @@ -63,7 +64,7 @@ def gen_inner_array_allocator(f: FieldDescriptor, is_repeated: bool) -> str: return "" return (decoder_constants.INNER_ARRAY_ALLOCATOR).format( t = util.gen_global_type_from_field(f), - field = f.name, + field = mangle_solidity_field_name(f.name), i = f.number ) @@ -71,7 +72,7 @@ def gen_inner_map_size(f: FieldDescriptor) -> str: if not util.is_map_type(f): return "" return (decoder_constants.INNER_MAP_SIZE).format( - field = f.name, + field = mangle_solidity_field_name(f.name), i = f.number ) @@ -117,7 +118,7 @@ def gen_field_reader(f: FieldDescriptor, msg: Descriptor) -> str: assert decode_type[0] != "." if not is_repeated: return (decoder_constants.ENUM_FIELD_READER).format( - field = f.name, + field = mangle_solidity_field_name(f.name), decoder = util.gen_decoder_name(f), decode_type = decode_type, t = util.gen_internal_struct_name(msg), @@ -126,7 +127,7 @@ def gen_field_reader(f: FieldDescriptor, msg: Descriptor) -> str: ) else: unpacked_reader = (decoder_constants.UNPACKED_REPEATED_ENUM_FIELD_READER).format( - field = f.name, + field = mangle_solidity_field_name(f.name), decoder = util.gen_decoder_name(f), decode_type = decode_type, t = util.gen_internal_struct_name(msg), @@ -136,7 +137,7 @@ def gen_field_reader(f: FieldDescriptor, msg: Descriptor) -> str: library_name = library_name ) packed_reader = (decoder_constants.PACKED_REPEATED_ENUM_FIELD_READER).format( - field = f.name, + field = mangle_solidity_field_name(f.name), decoder = util.gen_decoder_name(f), decode_type = decode_type, t = util.gen_internal_struct_name(msg), @@ -145,14 +146,23 @@ def gen_field_reader(f: FieldDescriptor, msg: Descriptor) -> str: ) return unpacked_reader + packed_reader if not is_repeated: - return (decoder_constants.FIELD_READER).format( - field = f.name, - decoder = util.gen_decoder_name(f), - decode_type = util.gen_global_type_decl_from_field(f), - t = util.gen_internal_struct_name(msg), - ) + # Handle circular dependencies by using bytes decoder instead of struct decoder + if util.should_use_bytes_for_field(msg, f): + return (decoder_constants.FIELD_READER).format( + field = mangle_solidity_field_name(f.name), + decoder = "ProtoBufRuntime._decode_bytes", + decode_type = "bytes memory", + t = util.gen_internal_struct_name(msg), + ) + else: + return (decoder_constants.FIELD_READER).format( + field = mangle_solidity_field_name(f.name), + decoder = util.gen_decoder_name(f), + decode_type = util.gen_global_type_decl_from_field(f), + t = util.gen_internal_struct_name(msg), + ) unpacked_reader = (decoder_constants.UNPACKED_REPEATED_FIELD_READER).format( - field = f.name, + field = mangle_solidity_field_name(f.name), decoder = util.gen_decoder_name(f), decode_type = util.gen_global_type_decl_from_field(f), t = util.gen_internal_struct_name(msg), @@ -162,21 +172,21 @@ def gen_field_reader(f: FieldDescriptor, msg: Descriptor) -> str: packed_reader = '' # this remains empty if wire type is length-delimited if util.gen_wire_type(f) == 'Fixed32': packed_reader = (decoder_constants.PACKED_REPEATED_FIXED32_FIELD_READER).format( - field = f.name, + field = mangle_solidity_field_name(f.name), decoder = util.gen_decoder_name(f), decode_type = util.gen_global_type_decl_from_field(f), t = util.gen_internal_struct_name(msg) ) elif util.gen_wire_type(f) == 'Fixed64': packed_reader = (decoder_constants.PACKED_REPEATED_FIXED64_FIELD_READER).format( - field = f.name, + field = mangle_solidity_field_name(f.name), decoder = util.gen_decoder_name(f), decode_type = util.gen_global_type_decl_from_field(f), t = util.gen_internal_struct_name(msg) ) elif util.gen_wire_type(f) == 'Varint': packed_reader = (decoder_constants.PACKED_REPEATED_VARINT_FIELD_READER).format( - field = f.name, + field = mangle_solidity_field_name(f.name), decoder = util.gen_decoder_name(f), decode_type = util.gen_global_type_decl_from_field(f), t = util.gen_internal_struct_name(msg) diff --git a/protobuf-solidity/src/protoc/plugin/gen_encoder.py b/protobuf-solidity/src/protoc/plugin/gen_encoder.py index 2e83386..4978e71 100644 --- a/protobuf-solidity/src/protoc/plugin/gen_encoder.py +++ b/protobuf-solidity/src/protoc/plugin/gen_encoder.py @@ -1,4 +1,5 @@ import gen_util as util +from gen_util import mangle_solidity_field_name import gen_encoder_constants as encoder_constants from google.protobuf.descriptor import Descriptor, FieldDescriptor from typing import List @@ -19,7 +20,11 @@ def gen_inner_field_encoder(f: FieldDescriptor, msg: Descriptor) -> str: """Generates a code snippet that encodes a field (a part of _encode)""" is_repeated = util.field_is_repeated(f) is_packed = util.field_is_packed(f) - if is_repeated: + + # Handle circular dependencies by using bytes encoder + if util.should_use_bytes_for_field(msg, f): + template = encoder_constants.INNER_FIELD_ENCODER_NOT_REPEATED + elif is_repeated: if util.is_map_type(f): template = encoder_constants.INNER_FIELD_ENCODER_REPEATED_MAP elif f.type == FieldDescriptor.TYPE_ENUM and is_packed: @@ -44,12 +49,17 @@ def gen_inner_field_encoder(f: FieldDescriptor, msg: Descriptor) -> str: if is_repeated and is_packed: size = gen_packed_repeated_array_size(f) ecblk = util.EmptyCheckBlock(f) + + # Use bytes encoder for circular dependencies + encoder_name = "ProtoBufRuntime._encode_bytes" if util.should_use_bytes_for_field(msg, f) else util.gen_encoder_name(f) + wire_type = "LengthDelim" if util.should_use_bytes_for_field(msg, f) else util.gen_wire_type(f) + return template.format( block_begin=ecblk.begin(), - field = f.name, + field = mangle_solidity_field_name(f.name), key = f.number, - wiretype = util.gen_wire_type(f), - encoder = util.gen_encoder_name(f), + wiretype = wire_type, + encoder = encoder_name, enum_name = type_name.split(".")[-1], library_name = library_name, size = size, @@ -81,7 +91,7 @@ def gen_packed_repeated_array_size(f: FieldDescriptor) -> str: assert util.field_is_repeated(f) assert util.field_is_packed(f) wt = util.gen_wire_type(f) - field = f.name + field = mangle_solidity_field_name(f.name) if wt == "Fixed32": return "(4 * r.{field}.length)".format(field = field) if wt == "Fixed64": @@ -108,13 +118,18 @@ def gen_packed_repeated_array_size(f: FieldDescriptor) -> str: Determine the estimated size given the field type """ def gen_field_scalar_size(f: FieldDescriptor, msg: Descriptor) -> str: + # Handle circular dependencies as bytes + if util.should_use_bytes_for_field(msg, f): + fname = mangle_solidity_field_name(f.name) + ("[i]" if util.field_is_repeated(f) else "") + return ("ProtoBufRuntime._sz_lendelim(r.{field}.length)").format(field = fname) + wt = util.gen_wire_type(f) vt = util.field_pb_type(f) if util.field_is_repeated(f) and util.field_is_packed(f): return "ProtoBufRuntime._sz_lendelim({})".format( gen_packed_repeated_array_size(f) ) - fname = f.name + ("[i]" if util.field_is_repeated(f) else "") + fname = mangle_solidity_field_name(f.name) + ("[i]" if util.field_is_repeated(f) else "") if wt == "Varint": if vt == "bool": return "1" @@ -169,7 +184,7 @@ def gen_field_estimator(f: FieldDescriptor, msg: Descriptor) -> str: else: template = encoder_constants.FIELD_ESTIMATOR_NOT_REPEATED return template.format( - field = f.name, + field = mangle_solidity_field_name(f.name), szKey = (1 if f.number < 16 else 2), szItem = gen_field_scalar_size(f, msg) ) diff --git a/protobuf-solidity/src/protoc/plugin/gen_sol.py b/protobuf-solidity/src/protoc/plugin/gen_sol.py index b0dc932..7624e80 100755 --- a/protobuf-solidity/src/protoc/plugin/gen_sol.py +++ b/protobuf-solidity/src/protoc/plugin/gen_sol.py @@ -13,16 +13,25 @@ from gen_decoder import gen_decoder_section from gen_encoder import gen_encoder_section import gen_util as util +from gen_util import mangle_solidity_field_name import gen_sol_constants as sol_constants import solidity_protobuf_extensions_pb2 as solpbext def gen_fields(msg: Descriptor) -> str: - return '\n'.join(map((lambda f: (" {type} {name};").format(type = util.gen_fieldtype(f), name = f.name)), msg.fields)) + def format_field(f): + field_name = mangle_solidity_field_name(f.name) + if util.should_use_bytes_for_field(msg, f): + # Use bytes for circular dependencies with a comment + return f" bytes {field_name}; // Circular reference: use encode/decode helpers" + else: + return f" {util.gen_fieldtype(f)} {field_name};" + + return '\n'.join(map(format_field, msg.fields)) def gen_map_fields_decl_for_field(f: FieldDescriptor) -> str: return (sol_constants.MAP_FIELD_DEFINITION).format( - name = f.name, + name = mangle_solidity_field_name(f.name), key_type = util.gen_global_type_name_from_field(f.message_type.fields[0]), container_type = util.gen_global_type_name_from_field(f) ) @@ -91,25 +100,30 @@ def gen_map_insert_on_store(f: FieldDescriptor, parent_msg: Descriptor) -> str: for nt in parent_msg.nested_types: if nt.GetOptions().map_entry: if f.message_type and f.message_type is nt: - return ('output._size_{name} = input._size_{name};\n').format(name = f.name) + return ('output._size_{name} = input._size_{name};\n').format(name = mangle_solidity_field_name(f.name)) return '' def gen_store_code_for_field(f: FieldDescriptor, msg: Descriptor) -> str: tmpl = "" - if util.field_is_message(f) and util.field_is_repeated(f): + # Handle circular dependencies as simple field assignment (bytes) + if util.should_use_bytes_for_field(msg, f): + return (sol_constants.STORE_OTHER).format( + field = mangle_solidity_field_name(f.name) + ) + elif util.field_is_message(f) and util.field_is_repeated(f): tmpl = sol_constants.STORE_REPEATED elif util.field_is_message(f): tmpl = sol_constants.STORE_MESSAGE else: return (sol_constants.STORE_OTHER).format( - field = f.name + field = mangle_solidity_field_name(f.name) ) libname = util.gen_struct_codec_lib_name_from_field(f) return tmpl.format( i = f.number, - field = f.name, + field = mangle_solidity_field_name(f.name), lib = libname, map_insert_code = gen_map_insert_on_store(f, msg) ) @@ -150,8 +164,8 @@ def gen_map_helper_codes_for_field(f: FieldDescriptor, nested_type: Descriptor) value_storage_type = "" return (sol_constants.MAP_HELPER_CODE).format( name = util.to_camel_case(f.name), - val_name = "self.{0}".format(f.name), - map_name = "self._size_{0}".format(f.name), + val_name = "self.{0}".format(mangle_solidity_field_name(f.name)), + map_name = "self._size_{0}".format(mangle_solidity_field_name(f.name)), key_type = key_type, value_type = value_type, field_type = field_type, @@ -164,7 +178,7 @@ def gen_array_helper_codes_for_field(f: FieldDescriptor) -> str: field_type = util.gen_global_type_name_from_field(f) return (sol_constants.ARRAY_HELPER_CODE).format( name = util.to_camel_case(f.name), - val_name = "self.{0}".format(f.name), + val_name = "self.{0}".format(mangle_solidity_field_name(f.name)), field_type = field_type, field_storage_type = "memory" if util.is_complex_type(field_type) else "" ) @@ -227,6 +241,9 @@ def gen_global_enum(file: FileDescriptor, delegate_codecs: List[str]): RUNTIME_FILE_NAME = "ProtoBufRuntime.sol" PROTOBUF_ANY_FILE_NAME = "GoogleProtobufAny.sol" +PROTOBUF_DURATION_FILE_NAME = "GoogleProtobufDuration.sol" +PROTOBUF_TIMESTAMP_FILE_NAME = "GoogleProtobufTimestamp.sol" +PROTOBUF_FIELD_DESCRIPTOR_PROTO_FILE_NAME = "GoogleProtobufFieldDescriptorProto.sol" GEN_RUNTIME = False COMPILE_META_SCHEMA = False def apply_options(params_string): @@ -236,7 +253,11 @@ def apply_options(params_string): raise ValueError('"gen_runtime" and "use_runtime" cannot be used together') if "gen_runtime" in params: GEN_RUNTIME = True - change_runtime_file_names(params["gen_runtime"]) + if isinstance(params["gen_runtime"], bool) or params["gen_runtime"] in ["true", "True", "1"]: + # Use default filename when just gen_runtime flag is provided + pass + else: + change_runtime_file_names(params["gen_runtime"]) if "use_runtime" in params: GEN_RUNTIME = False change_runtime_file_names(params["use_runtime"]) @@ -277,6 +298,8 @@ def get_location_option(f: FileDescriptor) -> str: return opts.location def extract_npm_package_name_and_directory_path(location: str) -> Tuple[str, str]: + if not location: + return "", "" parts = location.split('/') if location[0] == '@': return '/'.join(parts[:2]), '/'.join(parts[2:]) @@ -354,6 +377,9 @@ def generate_code(request, response): output.append('{0};'.format(pragma)) output.append('import "{0}";'.format(gen_import_path(RUNTIME_FILE_NAME, proto_file))) output.append('import "{0}";'.format(gen_import_path(PROTOBUF_ANY_FILE_NAME, proto_file))) + output.append('import "{0}";'.format(gen_import_path(PROTOBUF_DURATION_FILE_NAME, proto_file))) + output.append('import "{0}";'.format(gen_import_path(PROTOBUF_TIMESTAMP_FILE_NAME, proto_file))) + output.append('import "{0}";'.format(gen_import_path(PROTOBUF_FIELD_DESCRIPTOR_PROTO_FILE_NAME, proto_file))) for dep in proto_file.dependencies: if dep.package == "solidity": continue diff --git a/protobuf-solidity/src/protoc/plugin/gen_util.py b/protobuf-solidity/src/protoc/plugin/gen_util.py index 00d0df0..ee32e30 100644 --- a/protobuf-solidity/src/protoc/plugin/gen_util.py +++ b/protobuf-solidity/src/protoc/plugin/gen_util.py @@ -10,8 +10,8 @@ Num2Type = { - 1: "double", - 2: "float", + 1: "bytes8", # double -> 8-byte representation + 2: "bytes4", # float -> 4-byte representation 3: "int64", # not zigzag (proto3 compiler does not seem to use it) 4: "uint64", 5: "int32", # not zigzag (proto3 compiler does not seem to use it) @@ -216,7 +216,10 @@ def parse_urllike_parameter(s): if s: for e in s.split('&'): kv = e.split('=') - ret[kv[0]] = kv[1] + if len(kv) == 1: + ret[kv[0]] = True # Boolean flag parameters + else: + ret[kv[0]] = kv[1] return ret def field_is_message(f: FieldDescriptor) -> bool: @@ -501,7 +504,41 @@ def gen_global_enum_name(file: FileDescriptor) -> str: FILE_NAME_GLOBAL_ENUMS """ - return file.name.replace(".", "_").upper() + "_" + "GLOBAL_ENUMS" + return file.name.replace(".", "_").replace("/", "_").upper() + "_" + "GLOBAL_ENUMS" + +# Solidity reserved keywords that need mangling +SOLIDITY_RESERVED_KEYWORDS = { + 'address', 'bool', 'string', 'bytes', 'byte', 'int', 'int8', 'int16', 'int24', + 'int32', 'int40', 'int48', 'int56', 'int64', 'int72', 'int80', 'int88', 'int96', + 'int104', 'int112', 'int120', 'int128', 'int136', 'int144', 'int152', 'int160', + 'int168', 'int176', 'int184', 'int192', 'int200', 'int208', 'int216', 'int224', + 'int232', 'int240', 'int248', 'int256', 'uint', 'uint8', 'uint16', 'uint24', + 'uint32', 'uint40', 'uint48', 'uint56', 'uint64', 'uint72', 'uint80', 'uint88', + 'uint96', 'uint104', 'uint112', 'uint120', 'uint128', 'uint136', 'uint144', + 'uint152', 'uint160', 'uint168', 'uint176', 'uint184', 'uint192', 'uint200', + 'uint208', 'uint216', 'uint224', 'uint232', 'uint240', 'uint248', 'uint256', + 'bytes1', 'bytes2', 'bytes3', 'bytes4', 'bytes5', 'bytes6', 'bytes7', 'bytes8', + 'bytes9', 'bytes10', 'bytes11', 'bytes12', 'bytes13', 'bytes14', 'bytes15', + 'bytes16', 'bytes17', 'bytes18', 'bytes19', 'bytes20', 'bytes21', 'bytes22', + 'bytes23', 'bytes24', 'bytes25', 'bytes26', 'bytes27', 'bytes28', 'bytes29', + 'bytes30', 'bytes31', 'bytes32', 'fixed', 'ufixed', 'abstract', 'after', + 'alias', 'apply', 'auto', 'case', 'catch', 'copyof', 'default', 'define', + 'final', 'immutable', 'implements', 'in', 'inline', 'let', 'macro', 'match', + 'mutable', 'null', 'of', 'override', 'partial', 'promise', 'reference', + 'relocatable', 'sealed', 'sizeof', 'static', 'supports', 'switch', 'typedef', + 'typeof', 'var', 'assembly', 'break', 'continue', 'contract', 'do', 'else', + 'enum', 'event', 'for', 'function', 'if', 'import', 'library', 'mapping', + 'modifier', 'pragma', 'return', 'struct', 'using', 'while', 'constant', + 'anonymous', 'indexed', 'internal', 'external', 'private', 'public', 'pure', + 'view', 'payable', 'storage', 'memory', 'calldata', 'constructor', 'fallback', + 'receive', 'virtual', 'double', 'float', 'const' +} + +def mangle_solidity_field_name(field_name: str) -> str: + """Mangle field names that conflict with Solidity reserved keywords.""" + if field_name in SOLIDITY_RESERVED_KEYWORDS: + return field_name + "_field" + return field_name def change_pb_libname_prefix(new_name: str): global PB_LIB_NAME_PREFIX @@ -549,20 +586,20 @@ def gen_visibility(is_decoder) -> str: return "public" #"internal" if is_decoder else "" def simple_term(field: FieldDescriptor) -> str: - return "r.{name}".format(name=field.name) + return "r.{name}".format(name=mangle_solidity_field_name(field.name)) def string_term(field: FieldDescriptor) -> str: - return "bytes(r.{name}).length".format(name=field.name) + return "bytes(r.{name}).length".format(name=mangle_solidity_field_name(field.name)) def bytes_term(field: FieldDescriptor) -> str: - return "r.{name}.length".format(name=field.name) + return "r.{name}.length".format(name=mangle_solidity_field_name(field.name)) def message_term(field: FieldDescriptor) -> str: child = gen_struct_name_from_field(field) - return "{child}._empty(r.{name})".format(child=child, name=field.name) + return "{child}._empty(r.{name})".format(child=child, name=mangle_solidity_field_name(field.name)) def enum_term(field: FieldDescriptor) -> str: - return "uint(r.{name})".format(name=field.name) + return "uint(r.{name})".format(name=mangle_solidity_field_name(field.name)) default_values = { "bytes": {"cond": "!= 0", "f": bytes_term}, @@ -578,6 +615,8 @@ def enum_term(field: FieldDescriptor) -> str: "fixed64": {"cond": "!= 0", "f": simple_term}, "sfixed32": {"cond": "!= 0", "f": simple_term}, "sfixed64": {"cond": "!= 0", "f": simple_term}, + "float": {"cond": "!= 0", "f": simple_term}, + "double": {"cond": "!= 0", "f": simple_term}, "enum": {"cond": "!= 0", "f": enum_term}, "message": {"cond": "!= true", "f": message_term} } @@ -589,7 +628,7 @@ def __init__(self, field: FieldDescriptor): def begin(self): if field_is_repeated(self.field): - return "if ({term} != 0) {{".format(term="r." + self.field.name + ".length") + return "if ({term} != 0) {{".format(term="r." + mangle_solidity_field_name(self.field.name) + ".length") elif self.val in default_values: dv = default_values[self.val] params = dict( @@ -600,7 +639,7 @@ def begin(self): elif is_struct_type(self.field): return "" else: - raise Exception('Unsupported type: {}', self.field.type) + raise Exception('Unsupported type: {} in field: {} from message: {}'.format(self.field.type, self.field.name, self.field.containing_type.name)) def end(self): if is_struct_type(self.field) and not field_is_repeated(self.field): @@ -635,3 +674,62 @@ def __init__(self, wrapped): @property def fields(self): return self._self_fields + +# Circular dependency handling for buf/validate types +CIRCULAR_DEPENDENCY_MAP = { + # FieldRules has circular references to MapRules and RepeatedRules + "FieldRules": { + "map": "MapRules", + "repeated": "RepeatedRules" + }, + # MapRules has circular references back to FieldRules + "MapRules": { + "keys": "FieldRules", + "values": "FieldRules" + }, + # RepeatedRules has circular reference back to FieldRules + "RepeatedRules": { + "items": "FieldRules" + } +} + +def is_circular_dependency(msg: Descriptor, field: FieldDescriptor) -> bool: + """ + Detects if a message field creates a circular dependency. + + Args: + msg: The containing message descriptor + field: The field descriptor to check + + Returns: + True if the field creates a circular dependency + """ + msg_name = msg.name + field_name = mangle_solidity_field_name(field.name) + + + # Check if this message has known circular dependencies + if msg_name in CIRCULAR_DEPENDENCY_MAP: + circular_fields = CIRCULAR_DEPENDENCY_MAP[msg_name] + if field_name in circular_fields: + # Check if the field type matches the expected circular target + if field.message_type: + target_type = field.message_type.name + expected_target = circular_fields[field_name] + return target_type == expected_target + + return False + +def should_use_bytes_for_field(msg: Descriptor, field: FieldDescriptor) -> bool: + """ + Determines if a field should use bytes instead of its natural type + to break circular dependencies. + + Args: + msg: The containing message descriptor + field: The field descriptor to check + + Returns: + True if the field should use bytes instead of its natural type + """ + return is_circular_dependency(msg, field)