Skip to content

Accept message literals as list elements in annotation arguments - #128

Merged
trendvidia merged 1 commit into
trendvidiafrom
message-list-elements
Jul 25, 2026
Merged

Accept message literals as list elements in annotation arguments#128
trendvidia merged 1 commit into
trendvidiafrom
message-list-elements

Conversation

@trendvidia

Copy link
Copy Markdown
Owner

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's LiteralValue.literal slot 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: ExprDict gains an optional leading type name — ExprDict.TypeName() + Nodes.NewTypedExprDict. Only the annotation classifier attaches it; dicts from every other grammar position stay zero.
  • parser: classifyMessageLiteral attaches the name to the dict it builds; classifyListLiteral accepts 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; new AnnotationUse.MessageLiteralElem reads it back. An untyped {...} element under an any-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 to LiteralValue.literal.message — a google.protobuf.Any serialized at lowering — via the same path as argument-level literals (deduped into a messageLiteral helper). Nested lists compose.

Verification

  • IR tests: bare/qualified/multi-element/nested-list acceptance (including MessageLiteralElem round-trip), untyped-element diagnostic, unresolved type name, heterogeneity.
  • fdp test pins the lowered ListLiteral{elements: {literal: {message: Any}}} shape, flat and nested, down to the wire bytes.
  • E2E: the issue's exact repro compiles clean; protowire's 10_literal_args.proto is unregressed (its only diagnostic is a pre-existing unused-import warning — the fixture imports annotations.proto but uses only local declarations; unrelated to this change).
  • go test ./... and make lint clean.

Follow-up (protowire side, per the issue): extend 10_literal_args.proto with a message-element list and pin the lowered shape in 11_literal_carrier_golden.textproto; then the deferred @http responses parameter (RFC-001-issues §#80) can land.

Fixes #127.

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.
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.

Message literals inside list literals are rejected at annotation arguments (RFC-001 §8.1 LiteralValue.literal)

1 participant