Accept message literals as list elements in annotation arguments - #128
Merged
Conversation
RFC-001 §8.1 constrains list literals only by homogeneity, and the
carrier's LiteralValue.literal slot names the message kind directly —
but the §5.1 capture classifier rejected typed message-literal
elements, so `@meta([Item{status: 1}])` fell through to opaque
expression capture and was diagnosed with the misleading "must be a
literal or a qualified name" error (issue #127).
The element's type path had nowhere to live: at argument level it
rides on AnnotationUseArg.MessageType, but list elements have no
argument node. ExprDict now carries an optional leading type name
(ast.ExprDict.TypeName, attached only by the annotation classifier),
so:
- parser: classifyMessageLiteral attaches the type name to the dict
it builds and classifyListLiteral accepts typed elements.
Field-initializer values still keep type names out (nested literals
type themselves from the field they initialize).
- ir: classifyListElement resolves the element's explicit type name,
evaluates the literal against it (shared option-value evaluator,
map-field rejection included), and records it keyed by the dict's
braces token; AnnotationUse.MessageLiteralElem reads it back.
Untyped `{...}` elements under an `any` context now get the same
explicit-type-name diagnostic as argument-level literals. Message
elements participate in the homogeneity check as their own kind.
- fdp: buildArgValue lowers dict elements to
LiteralValue.literal.message — a google.protobuf.Any serialized at
lowering — matching the argument-level form; nested lists compose.
Verified: the issue's repro compiles clean and lowers the pinned
ListLiteral{elements: {literal: {message: ...}}} shape; protowire's
10_literal_args.proto fixture is unregressed.
Fixes #127.
This was referenced Jul 25, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the corpus-ahead-of-toolchain gap from #127:
@meta([Item{status: 1}])was rejected with the misleading "must be a literal or a qualified name" error because the §5.1 capture classifier refused typed message-literal list elements, making the whole argument an opaque expression capture. RFC-001 §8.1 constrains lists only by homogeneity, and the carrier'sLiteralValue.literalslot represents message elements directly.What
The root problem was representational: a typed element's type path had no home (argument-level literals carry it on
AnnotationUseArg.MessageType; list elements have no argument node).ast:ExprDictgains an optional leading type name —ExprDict.TypeName()+Nodes.NewTypedExprDict. Only the annotation classifier attaches it; dicts from every other grammar position stay zero.parser:classifyMessageLiteralattaches the name to the dict it builds;classifyListLiteralaccepts typed elements. Field-initializer values still keep type names out of the value grammar (nested literals type themselves from the field they initialize).ir(B3):classifyListElement's dict arm resolves the element's explicit type name against the use site's scope, evaluates the literal through the shared option-value evaluator (map-field rejection included), and records it keyed by the dict's braces token; newAnnotationUse.MessageLiteralElemreads it back. An untyped{...}element under anany-typed context now gets the same explicit-type-name diagnostic as the argument-level form instead of a blanket rejection. Message elements join the §8.1 homogeneity check as their own kind.fdp: dict elements lower toLiteralValue.literal.message— agoogle.protobuf.Anyserialized at lowering — via the same path as argument-level literals (deduped into amessageLiteralhelper). Nested lists compose.Verification
MessageLiteralElemround-trip), untyped-element diagnostic, unresolved type name, heterogeneity.ListLiteral{elements: {literal: {message: Any}}}shape, flat and nested, down to the wire bytes.10_literal_args.protois unregressed (its only diagnostic is a pre-existing unused-import warning — the fixture importsannotations.protobut uses only local declarations; unrelated to this change).go test ./...andmake lintclean.Follow-up (protowire side, per the issue): extend
10_literal_args.protowith a message-element list and pin the lowered shape in11_literal_carrier_golden.textproto; then the deferred@httpresponsesparameter (RFC-001-issues §#80) can land.Fixes #127.