MacroSelectors

dev.constructive.eo.generics.MacroSelectors

Quote-context selector-AST helpers shared between LensMacro and the JsonPrismMacro / AvroPrismMacro cursor macros. All parse single-field selector lambdas (_.fieldName) and validate non-duplicates; the per-macro divergence is just the error-message tag.

Attributes

Source
MacroSelectors.scala
Graph
Supertypes
class Object
trait Matchable
class Any
Self type

Members list

Value members

Concrete methods

def caseFieldType[A : Type](using q: Quotes)(who: String, nameE: Expr[String]): (String, Int, q.reflect.TypeRepr)

Validate a Dynamic-sugar literal field name against A's case-class schema. Returns the field name, its declaration index in A, and its widened TypeRepr; aborts with a who-tagged error when the name isn't a compile-time literal or A has no such case field. Shared backbone of the cursor macros' selectDynamic sugar — the caller owns the module-specific codec summon + emit.

Validate a Dynamic-sugar literal field name against A's case-class schema. Returns the field name, its declaration index in A, and its widened TypeRepr; aborts with a who-tagged error when the name isn't a compile-time literal or A has no such case field. Shared backbone of the cursor macros' selectDynamic sugar — the caller owns the module-specific codec summon + emit.

Attributes

Source
MacroSelectors.scala
def extractFieldName(using Quotes)(t: x$1.reflect.Term): Option[String]

Wrapper-tolerant variant of extractSingleFieldName for the cursor macros' .field(_.x) sugar: it strips Inlined / Typed around the lambda, around its Select body, AND around the Select's RECEIVER, which the strict form does not.

Wrapper-tolerant variant of extractSingleFieldName for the cursor macros' .field(_.x) sugar: it strips Inlined / Typed around the lambda, around its Select body, AND around the Select's RECEIVER, which the strict form does not.

'''Receiver-is-the-lambda-parameter is load-bearing (issue #95).''' This used to match Lambda(_, Select(_, name)) with ANY receiver, so a nested path _.inner.y — Select(Select(Ident(_), "inner"), "y") — parsed as the single name "y" and every cursor macro then resolved y on the PARENT. On a record that happens to carry a field of that name (and an inner record always might) the result is a well-typed, perfectly lawful optic aimed at the wrong field: silent corruption with no schema divergence involved, and the macros' own "nested paths … chain them" abort unreachable for exactly the shape it was written for. Rejecting the nested receiver here makes that abort fire.

Attributes

Source
MacroSelectors.scala
def extractSingleFieldName(using Quotes)(t: x$1.reflect.Term): Option[String]

Strips Inlined / Typed wrappers and peeks inside the lambda body for a single Select whose receiver is exactly the lambda parameter (an Ident). Receiver-is-Ident is load-bearing — nested paths like _.a.b parse as Select(Select(Ident(_), "a"), "b") and fall through to None so each macro can produce its own "nested paths" message.

Strips Inlined / Typed wrappers and peeks inside the lambda body for a single Select whose receiver is exactly the lambda parameter (an Ident). Receiver-is-Ident is load-bearing — nested paths like _.a.b parse as Select(Select(Ident(_), "a"), "b") and fall through to None so each macro can produce its own "nested paths" message.

Attributes

Source
MacroSelectors.scala
def fieldsSelectorNT[A : Type](using q: Quotes)(who: String, selectorsE: Expr[Seq[A => Any]]): (List[String], List[Int], q.reflect.TypeRepr)

Validate a .fields(_.a, _.b, …) varargs selector list against A's case-class schema and synthesise the SELECTOR-order NamedTuple type: arity ≥ 2, single-hop selectors only, known fields, no duplicates. Returns the selected names (selector order), their declaration indices in A (declaration order lookup — the field-schema-naming resolution of issue #35), and the NamedTuple[names, values] TypeRepr. Shared backbone of AvroPrismMacro.fieldsCommon / JsonPrismMacro.fieldsCommon — the caller owns the module-specific codec summon + emit.

Validate a .fields(_.a, _.b, …) varargs selector list against A's case-class schema and synthesise the SELECTOR-order NamedTuple type: arity ≥ 2, single-hop selectors only, known fields, no duplicates. Returns the selected names (selector order), their declaration indices in A (declaration order lookup — the field-schema-naming resolution of issue #35), and the NamedTuple[names, values] TypeRepr. Shared backbone of AvroPrismMacro.fieldsCommon / JsonPrismMacro.fieldsCommon — the caller owns the module-specific codec summon + emit.

Attributes

Source
MacroSelectors.scala
def iterableElementType[A](who: String)(using evidence$1: Type[A], q: Quotes): q.reflect.TypeRepr

The single element type of a Scala collection type A (via its Iterable base type), or abort with a who-tagged error. Shared by the cursor macros' .at / .each sugar.

The single element type of a Scala collection type A (via its Iterable base type), or abort with a who-tagged error. Shared by the cursor macros' .at / .each sugar.

Attributes

Source
MacroSelectors.scala
def reportDuplicateSelectors(using Quotes)(who: String, resolved: List[(Int, String)]): Unit

Reject duplicate field names; aborts on the first duplicate. The who tag prefixes the message.

Reject duplicate field names; aborts on the first duplicate. The who tag prefixes the message.

Attributes

Source
MacroSelectors.scala
def requireCaseField[A : Type](using Quotes)(who: String, name: String): Unit

Abort unless name is a case field of A.

Abort unless name is a case field of A.

The other half of the cursor macros' selector rung (issue #95): a single-hop selector naming something that is not a case field — _.hashCode, or a no-arg method on the case class — used to be passed through as a LITERAL field name and become a runtime miss on the wire format. The declaration index the resolvers ask for comes back -1 for exactly those, and -1 is also the legitimate "this parent has no case fields" signal, so the two cannot be told apart downstream. They are told apart here instead.

Skipped when A has no case fields at all (a NamedTuple parent, say), which is the shape the literal-name fallback exists for.

Attributes

Source
MacroSelectors.scala
def tupleTypeOf(using q: Quotes)(ts: List[q.reflect.TypeRepr]): q.reflect.TypeRepr

Build the tuple type carrying ts — scala.TupleN[T1, …, Tn] up to arity 22, a *: cons chain above it.

Build the tuple type carrying ts — scala.TupleN[T1, …, Tn] up to arity 22, a *: cons chain above it.

'''Why the spelling matters (issue #96).''' T1 *: T2 *: EmptyTuple and (T1, T2) are =:=, so for type-checking either will do. They are NOT interchangeable for a downstream macro that has to '''construct''' a value of the type: hearth's SyntheticNamedTupleConstructor (behind kindlings' NamedTuple derivation rules, and hence behind AvroCodec.derived for a .fields focus) emits new (args…) for arity < 23 — which for the cons spelling is new *:(a, b), and *: declares no value parameters. Every derivation over a macro-synthesised NamedTuple then died with wrong number of arguments at inlining … (): (T1, T2), expected: 0, found: 2. Emitting what a human would have written keeps them working, and costs nothing: user-written givens over the TupleN spelling still match either way.

Above arity 22 there is no TupleN to reach for — (A, …, A) with 23 components already '''is''' the cons chain — so the fallback is forced. hearth ≥ 0.4.2 handles exactly that range through Tuple.fromArray; on hearth < 0.4.2 the hazard survives there for consumers.

'''Both branches are load-bearing, permanently.''' hearth 0.4.2's fix is scoped to arity ≥ 23: its case n if n &lt; 23 arm still emits the primary-constructor call, so a cons chain below 23 fails exactly as it did on 0.4.0. Collapsing this to a uniform cons fold would therefore re-break every .fields call of arity 2..22. NamedTupleSpellingSpec pins both directions.

Attributes

Source
MacroSelectors.scala