Skip to content

Handle map fields distinctly from repeated messages#15

Merged
cocoahero merged 1 commit into
mainfrom
feature/map-fields
Jul 7, 2026
Merged

Handle map fields distinctly from repeated messages#15
cocoahero merged 1 commit into
mainfrom
feature/map-fields

Conversation

@cocoahero

@cocoahero cocoahero commented Jul 7, 2026

Copy link
Copy Markdown
Owner

Summary

protoc lowers map<K, V> to a repeated message of synthetic entry types. With the field API added in #14 that made map fields indistinguishable from repeated message fields, and surfaced the synthetic entry types as if they were real nested messages:

  • field.repeated?true, field.message?true, and field.type_descriptor resolved to the synthetic <Field>Entry message
  • message.messages included the synthetic entry types

A generator walking fields/messages would emit a bogus repeated-message field and a phantom entry class. This closes that gap.

Changes

FieldDescriptor

  • #map? — detects the map representation
  • #key / #value — the entry's key (field 1) and value (field 2), as FieldDescriptors (so field.value.message?, field.value.type_descriptor, etc. work)
  • Map fields are excluded from #repeated?, #message?, and #scalar?, and #type_descriptor returns nil for them — so for any field exactly one shape predicate is true and if map? / elsif repeated? / else singular is clean

MessageDescriptor#messages — omits synthetic map_entry types.

Design note

Map detection resolves the entry from the containing message's raw nested_type rather than via Context. This is deliberate: Context builds its type index by walking #messages, and since synthetic entries are now filtered out of #messages, a context lookup of the entry would miss. Resolving from the raw nested_type keeps the two decoupled.

Testing

  • New cases: scalar-valued map, message-valued map (value resolves to Comment), repeated-scalar-is-not-a-map, and synthetic-entries-excluded-from-#messages.
  • CommentDigest message added to the blog fixtures (and regenerated via bin/fixtures).
  • bin/rake (test + rubocop): 73 runs, 191 assertions, 0 failures, rubocop clean.

protoc lowers map<K, V> to a repeated message of synthetic entry types,
which made map fields indistinguishable from repeated message fields and
surfaced the synthetic entries as real nested messages.

FieldDescriptor now detects maps: #map? plus #key/#value (the entry's
key/value fields as FieldDescriptors). Map fields are excluded from
#repeated?, #message?, and #scalar?, and #type_descriptor returns nil
for them, so exactly one shape predicate is true. The entry is resolved
from the containing message's raw nested_type rather than the context.

MessageDescriptor#messages now omits synthetic map-entry types.

Add scalar-, message-valued, and repeated-scalar map cases to the blog
fixtures (and regenerate) to cover the distinction.
@cocoahero cocoahero merged commit ef99958 into main Jul 7, 2026
5 checks passed
@cocoahero cocoahero deleted the feature/map-fields branch July 7, 2026 19:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant