Why it is shaped this way
A mapped scalar's host type is named per language
Status: accepted, 2026-10-07; amended 2026-10-07, the Kotlin entry's shape. Serves The compiler decides and Two runtimes, one compiler. Built 2026-10-07. Reopen if Relay's customScalarTypes takes a shape of its own for more than one language.
Status: accepted, 2026-10-07; amended 2026-10-07, the Kotlin entry's
shape. Serves
The compiler decides and
Two runtimes, one compiler.
Built 2026-10-07. Reopen if Relay's customScalarTypes takes a shape of its
own for more than one language.
Context#
config.rs documents customScalarTypes as "the Swift type a custom scalar
reads as". The lowering puts that Swift type on the plan IR:
TypePlan::Named.mapped in pipeline/plan.rs is "the Swift type a custom
scalar reads as", filled in pipeline/lower.rs from the configuration. The
IR is meant to be the seam no language owns: pipeline/plan.rs opens by
saying nothing after it sees a Relay type, and a Kotlin emitter reads the
same IR.
The operation's onError has the same leak. OperationPlan.error_behavior
is a string filled from OnError::swift_case, the case of
Baton.ErrorBehavior that names the value.
Decision#
customScalarTypeskeeps Relay's key. A value is either a string, the Swift type as today, or an object by language, for example{"swift": "Foundation.Decimal", "kotlin": {"type": "java.math.BigDecimal", "converter": "baton.scalars.Decimals"}}.- A language's entry has the shape that language needs. Swift's is the
type, which conforms to
MappedScalarand so parses and renders itself. Kotlin's is the type and the converter: a Kotlin type the runtime does not own cannot implement an interface, so an object implementingScalarConverter<T>parses and renders it, and the compiler must be told the type, since nothing about a converter object names it. - The plan carries the scalar's schema name, and each language's writer resolves the host type from the configuration.
OperationPlan.error_behaviorcarries theonErrorvalue, not a Swift case name.- The goldens prove each step: the Swift written does not change by a byte.
Evidence#
-
The Kotlin emitter's first milestone, 2026-10-07: a constructor parameter of an input object or an operation needs its type, and a converter object named alone gives the compiler nothing to print for it; the entry became the type and the converter together.
-
The compiler as built, by reading: the doc comment of
custom_scalar_typesincompiler/src/config.rs;mappedonTypePlan::Namedincompiler/src/pipeline/plan.rs, and its doc comment; the lowering that fills it fromself.config.custom_scalar_typesincompiler/src/pipeline/lower.rs;error_behavior: Option<String>on the operation's plan, filled bybehavior.swift_case()in the same file;OnError::swift_caseinconfig.rs. -
The seam as stated: the module comment of
pipeline/plan.rs.
Not chosen#
- A second key per language: Relay has one key, and a web project already has it.
- The Swift type on the IR, with the Kotlin writer translating it: a Swift name is not a fact of the schema, and the translation would be a table from one language's types to another's.
Baton