---
name: hearth-api-reference
description: >
  Quick-reference table of commonly used Hearth API signatures: Expr, Type, MIO,
  ValDefsCache, CaseClass, Enum, LambdaBuilder, Environment. Always verify with MCP.
user-invocable: false
---

# Hearth API Quick Reference

Living cache of hearth API signatures used in kindlings. **Always verify with `kindlings-metals` MCP before use.**

**Last verified against hearth version:** check `build.sbt` `versions.hearth`

## Effect system (MIO, Log)

| API | Signature | Description |
|-----|-----------|-------------|
| `MIO.pure` | `[A](a: A): MIO[A]` | Lift a pure value |
| `MIO.fail` | `(error: Throwable): MIO[Nothing]` | Fail with error |
| `MIO { ... }` | `[A](thunk: => A): MIO[A]` | Lift a thunk |
| `mio1 >> mio2` | `MIO[A] >> MIO[B]: MIO[B]` | Sequence, discard first result |
| `mio.flatMap(f)` | `MIO[A].flatMap(A => MIO[B]): MIO[B]` | Monadic bind |
| `mio.map(f)` | `MIO[A].map(A => B): MIO[B]` | Functor map |
| `MIO.scoped` | `[A](f: (MIO ~> Id) => A): MIO[A]` | Unsafe escape hatch for builders |
| `Log.info` | `(msg: => String): MIO[Unit]` | Log at info level |
| `Log.debug` | `(msg: => String): MIO[Unit]` | Log at debug level |
| `Log.namedScope` | `[A](name: String)(body: MIO[A]): MIO[A]` | Hierarchical log scope |
| `Environment.reportErrorAndAbort` | `(msg: String): Nothing` | Abort compilation with error (outside MIO chain) |

## Type representations

| API | Signature | Description |
|-----|-----------|-------------|
| `Type.of[A]` | `[A](implicit ev: Type[A]): Type[A]` | Get type representation |
| `Type[A].prettyPrint` | `: String` | Human-readable type name |
| `Type[A].shortName` | `: String` | Short type name (no package) |
| `Type[A] <:< Type[B]` | `: Boolean` | Subtype check |
| `Type[A] =:= Type[B]` | `: Boolean` | Type equality |
| `Type[A].summonExpr` | `: ImplicitSearchResult[A]` | Summon implicit |
| `Type[A].summonExprIgnoring` | `(symbols: UntypedMethod*): ImplicitSearchResult[A]` | Summon ignoring symbols |
| `Type[A].methods` | `: List[Existential[Method]]` | List methods on type |
| `Type.Ctor1.of[F]` | Higher-kinded type constructor | For `F[_]` types |

## Expression manipulation

| API | Signature | Description |
|-----|-----------|-------------|
| `Expr(value)` | `[A](value: A)(implicit Liftable[A]): Expr[A]` | Lift literal value |
| `Expr.quote { ... }` | `[A](body: => A): Expr[A]` | Cross-platform quasiquote |
| `Expr.splice(expr)` | `[A](expr: Expr[A]): A` | Splice inside quote |
| `expr.prettyPrint` | `: String` | String representation of tree |
| `expr.upcast[B]` | `[B](implicit ev: A <:< B): Expr[B]` | Widen type (not narrow!) |
| `expr.as_??` | `: Expr_??` | Wrap into existential |

## Existentials (`Expr_??`, `??`)

| API | Signature | Description |
|-----|-----------|-------------|
| `expr.as_??` | `Expr[A] => Expr_??` | Erase concrete type |
| `Type[A].as_??` | `Type[A] => ??` | Erase concrete type from Type |
| `import e.Underlying` | Brings `Type[Underlying]` into scope | Pattern: `import e.{Underlying as T, value as v}` |
| `import e.value` | Brings typed expression into scope | Used with `Underlying` alias |

## Lambda and val builders

| API | Signature | Description |
|-----|-----------|-------------|
| `LambdaBuilder.of1[A]` | `(name: String): LambdaBuilder1[A]` | 1-arg lambda builder |
| `LambdaBuilder.of2[A, B]` | `(name1: String, name2: String): LambdaBuilder2[A, B]` | 2-arg lambda builder |
| `LambdaBuilder.of3[A, B, C]` | `(name1: String, name2: String, name3: String): LambdaBuilder3[A, B, C]` | 3-arg lambda builder |
| `builder.traverse` | `(f: Expr[A] => MIO[Expr[B]]): MIO[Built1[A, B]]` | Build with MIO body |
| `builder.buildWith` | `(f: Expr[A] => Expr[B]): Expr[A => B]` | Build with pure body |
| `built.build[B]` | `: Expr[A => B]` | Finalize lambda |
| `ValDefBuilder.ofLazy[T]` | `(name: String): ValDefBuilder` | `lazy val` builder |
| `ValDefBuilder.ofDef1[A, B]` | `(name: String, argName: String): ValDefBuilder` | `def` with 1 arg |
| `ValDefBuilder.ofDef2[A, B, C]` | `(name: String, argName1: String, argName2: String): ValDefBuilder` | `def` with 2 args |
| `ValDefBuilder.ofDef3[A, B, C, D]` | `(name: String, a1: String, a2: String, a3: String): ValDefBuilder` | `def` with 3 args |
| `ValDefsCache.mlocal` | `: MLocal[ValDefsCache]` | Create cache storage |
| `cache.get0Ary[T]` | `(key: String): MIO[Option[Expr[T]]]` | Get cached value |
| `cache.get1Ary[A, B]` | `(key: String): MIO[Option[Expr[A] => Expr[B]]]` | Get cached function |
| `cache.get2Ary[A, B, C]` | `(key: String): MIO[Option[(Expr[A], Expr[B]) => Expr[C]]]` | Get cached 2-arg function |
| `cache.get3Ary[A, B, C, D]` | `(key: String): MIO[Option[(Expr[A], Expr[B], Expr[C]) => Expr[D]]]` | Get cached 3-arg function |
| `cache.buildCachedWith` | `(key: String, builder: ValDefBuilder)(body: ...): MIO[...]` | Build and cache |
| `cache.forwardDeclare` | `(key: String, builder: ValDefBuilder): MIO[Unit]` | Forward-declare for recursion |
| `cache.get` | `: MIO[ValDefsCache]` | Extract accumulated cached defs |
| `vals.toValDefs.use` | `(f: ... => Expr[A]): Expr[A]` | Wrap expr in cached val defs block |

## Data model introspection

| API | Signature | Description |
|-----|-----------|-------------|
| `ClassViewResult` | Sealed trait: `Compatible(value)` / `Incompatible(reason)` | Return type of all `.parse` methods |
| `result.toOption` | `: Option[V]` | `Some(value)` for Compatible, `None` for Incompatible |
| `result.toEither` | `: Either[String, V]` | `Right(value)` for Compatible, `Left(reason)` for Incompatible |
| `CaseClass.parse[A]` | `: ClassViewResult[CaseClass[A]]` | Parse type as case class (excludes singletons) |
| `cc.caseFieldValuesAt(expr)` | `: ListMap[String, Expr_??]` | Read field values (encoder) |
| `cc.primaryConstructor(map)` | `(Map[String, Expr_??]): Either[String, Expr[A]]` | Construct from field map (decoder) |
| `cc.construct[F](f)` | `(ConstructField[F] => F[Option[Expr[A]]])` | Construct via callback (Scala 2 pitfall!) |
| `SingletonValue.parse[A]` | `: ClassViewResult[SingletonValue[A]]` | Parse type as singleton (case object, parameterless enum case, etc.) |
| `SingletonValue.unapply(tpe)` | `(Type[A]): Option[SingletonValue[A]]` | Pattern match extractor for singletons |
| `sv.singletonExpr` | `: Expr[A]` | The expression representing the singleton value |
| `Enum.parse[A]` | `: ClassViewResult[Enum[A]]` | Parse type as enum/sealed trait |
| `enum.parMatchOn[F, R]` | `(expr)(handler): F[Expr[R]]` | Exhaustive pattern match |
| `NamedTuple.parse[A]` | `: ClassViewResult[NamedTuple[A]]` | Parse type as named tuple (Scala 3.7+) |
| `NamedTuple.unapply(tpe)` | `(Type[A]): Option[NamedTuple[A]]` | Pattern match extractor for named tuples |
| `nt.primaryConstructor` | `: Method.NoInstance[A]` | Named tuple's primary constructor |
| `nt.fields` | `: List[(String, ??)]` | Named tuple field names and types |
| `nt.construct[F](f)` | `(ConstructField[F], Accessible): F[Option[Expr[A]]]` | Construct named tuple |
| `param.tpe` | `: Existential[Type]` | Parameter's type (existential) |
| `param.index` | `: Int` | Parameter position index |
| `param.isImplicit` / `param.isByName` | `: Boolean` | Implicit / by-name parameter flags |
| `param.byNameUnderlying` | `: Option[??]` | Underlying `A` of a by-name (`=> A`) param, both platforms (Hearth 0.3.1-48+) |

## Expression destructuring & DSL parsing (`DestructuredExpr`)

Parse selector/path lambdas (`_.a.b.each.when[T]`) and method references. See
[hearth-expr-parsing-dsl](../hearth-expr-parsing-dsl/SKILL.md).

| API | Signature | Description |
|-----|-----------|-------------|
| `DestructuredExpr.parse(e)` | `(Expr[A]): DestructuredExpr` | Semantic parse; `.collect { case ... }` walks pre-order |
| `DestructuredExpr.extractLambda(e)` | strips `Inlined`/`Block(DefDef…)` | Get the lambda body |
| `DestructuredExpr.extractFieldPath[S, A](p)` | `: Either[String, FieldPath]` | FIELD-ONLY path `_.a.b`; `Left`s on any method-call step |
| `fieldPath.fieldNames` | `: List[String]` | The field names of a `FieldPath` |
| `DestructuredExpr.MethodCall` | `{ method: Method; applied: List[Applied] }` | A resolved call node |
| `MethodCall.AppliedInstance` | `{ value: DestructuredExpr }` | Receiver |
| `MethodCall.AppliedTypes` | `{ typeArgs: List[??] }` | A `[T]` clause (S3 extension `[C]` THEN method `[T]` → take the LAST) |
| `MethodCall.AppliedValues` | `{ args: List[DestructuredExpr] }` | A `(...)` value clause |
| `node.tpe` / `node.toUntypedExpr` | `: ??` / `: UntypedExpr` | Recover precise type: `node.toUntypedExpr.asTyped[node.tpe.Underlying].as_??` |

## Class building & matching (`AnonymousInstance`, `MatchCase`)

| API | Signature | Description |
|-----|-----------|-------------|
| `AnonymousInstance.parse[A]` | `: ClassViewResult[AnonymousInstance[A]]` | Synthesize a subtype of a trait/class |
| `ai.mustOverride` | `: List[ClassifiedMethod]` | Members the subtype must implement |
| `ai.classParent` | `: Option[(??, List[Method])]` | For a CLASS parent: its accessible constructors |
| `ai.construct(ctor, ctorArgs, overrides)` | `(Option[Method], Arguments, Map[UntypedMethod, OverrideBody]): Either[NEV[String], Expr[A]]` | Build it. For a class parent pass `Some(ai.classParent.flatMap(_._2.headOption))` + dummy `ctorArgs` (e.g. summoned `Defaultable`/`ClassTag`) |
| `OverrideContext` | `{ self: Expr_??; method; parameters: List[Expr_??]; returnType: ??; returnsThisType: Boolean }` | Override body context. `returnsThisType` (Hearth 0.3.1-49+) ⇒ return `octx.self` uncast (do NOT cast to the widened `returnType`) |
| `MatchCase.typeMatch[Sub]()` | builds a `case s: Sub => …` arm | One arm of a type match |
| `MatchCase.matchOn[S, R](scrutinee)(arms)` | `(NonEmptyVector[MatchCase]): Expr[R]` | Emit the match. Non-exhaustive `.when[Sub]`: arm `typeMatch[Sub]` + catch-all `typeMatch[Parent]` (returns unchanged). S3 marks scrutinee `@unchecked` |

## Class lookup (`Type.classOfType`)

| API | Signature | Description |
|-----|-----------|-------------|
| `Type.classOfType[A]` / `Type[A].getRuntimeClass` | `: Option[java.lang.Class[A]]` | Runtime class at compile time — only for CLASSPATH types (not the current project) |
| (emit `classOf[M]`) | — | NOT possible for abstract `M`; summon `ClassTag[M]` + `.runtimeClass` instead — see cross-compilation pitfall #35 |

## Type extractors (std extensions)

| API | Pattern | Description |
|-----|---------|-------------|
| `IsCollection` | `Type[A] match { case IsCollection(ic) => ... }` | Detect collection types |
| `IsMap` | `Type[A] match { case IsMap(im) => ... }` | Detect map types |
| `IsOption` | `Type[A] match { case IsOption(io) => ... }` | Detect optional types |
| `IsValueType` | `Type[A] match { case IsValueType(ivt) => ... }` | Detect value classes |

## Rules

| API | Signature | Description |
|-----|-----------|-------------|
| `Rule.matched(value)` | `: Rule.Applicability[A]` | Rule applies with result |
| `Rule.yielded(reason)` | `: Rule.Applicability[Nothing]` | Rule does not apply |
| `Rules(r1, r2, ...)` | `(f): MIO[Either[reasons, A]]` | Try rules in order, first match wins |

## Known gotchas

| Gotcha | Details | Workaround |
|--------|---------|------------|
| Path-dependent types in `Expr.quote` | `import param.tpe.Underlying as F; Expr.quote { x.asInstanceOf[F] }` fails on Scala 2 | Use `LambdaBuilder.of1[F]` or runtime type witness |
| Macro-internal types leak | `??`, `Expr_??` inside `Expr.quote` causes reification failures on Scala 2 | Extract to `val` before `Expr.quote` |
| `Array` needs `ClassTag` | `Array.empty[T]` inside `Expr.quote` fails on Scala 2 | Use `List.empty[T]` and `::` |
| `upcast` only widens | `expr.upcast[B]` requires `A <:< B`; also needs `Type[A]` in scope | Use `.asInstanceOf` for narrowing |
| Raw quotes in `LambdaBuilder` | `'{ }` captures wrong `Quotes` on Scala 3 | Use `Expr.quote`/`Expr.splice` |
| Generic macro wrapper | `def helper[A](v: A)` calling macro sees abstract `A` | Call macros with concrete types |
| Sibling splice isolation | Each `Expr.splice` in an `Expr.quote` gets its own `Quotes` context on Scala 3; types/exprs can't be shared between sibling splices | Derive everything in one `runSafe`, use `LambdaBuilder` to package as function `Expr`s, then splice references only |
| `IsMap`/`IsCollection` types need import | `isMap.Key`, `isMap.Value`, `isMap.CtorResult` need `Type` in scope | `import isMap.{Key, Value, CtorResult}` before `Expr.quote` |
| `toValDefs.use` wrapping scope | Wrapping individual lambdas with all cached defs causes unused-method warnings | Wrap the outermost expression that contains all references |
| ValDefsCache key is composite | Cache key = `(String name, Seq[UntypedType] args, UntypedType returned)`. `hashCode` uses only the string; `equals` checks all three. Using `Any` as return type for all types causes cache collisions — different types sharing the same string key will overwrite each other | Use type-specific string keys (e.g., `s"cached-decode-method:${Type[B].prettyPrint}"`) when the return type must be `Any`, OR use a type-specific return type (e.g., `Either[Err, B]`) in the `ValDefBuilder` |
| `.asInstanceOf` not erased for outer types | `validated.asInstanceOf[Either[E, A]]` fails at runtime — JVM checks the outer type (`Either` vs `Validated`). Only inner type parameters are erased: `Left[E, String].asInstanceOf[Either[E, Int]]` succeeds | Don't rely on `.asInstanceOf` to cross between different outer types like `Either` and `Validated`; use `Any` as the declared type and cast at call sites where you know the actual type |
| Phantom type param inference | `schemaOf[A]: Schema` (A not in signature) infers `Nothing` on Scala 2, `Any` on Scala 3 | Guard against both: `Type[A] =:= Type.of[Nothing]... \|\| Type[A] =:= Type.of[Any]...` |
| Scala 2 reification of refined types | `DecodingFailure("msg", Nil): Either[DecodingFailure, A]` inside nested `Expr.quote` fails on Scala 2 with `scala.reflect.api.Trees` errors. Even `.asInstanceOf` doesn't help — the reifier captures refined type trees | Avoid constructing such expressions in nested quotes. Use runtime helper methods or `LambdaBuilder` + fresh derivation to build the expression outside the problematic quote scope |
| `MacroExtension` ClassTag erasure | `MacroExtension[A & B & C]` — ClassTag only preserves first component (`A`). ServiceLoader discovery fails silently for extensions with custom traits in the intersection type | Use `MacroExtension[A & B]` (minimal intersection), do runtime `match`/`asInstanceOf` in `extend()` for the custom trait |
| Cross-quotes implicit `Type` unused warning | `implicit val t: Type[X] = ...` inside `Expr.quote` scope flagged as "is never used" — fatal with `-Xfatal-warnings` | Wrap in `@scala.annotation.nowarn("msg=is never used") def myMethod = { implicit val t: ...; Expr.quote { ... } }` |
| `ProviderResult` ≠ `Option` | `IsOption.parse[A]`, `IsCollection.parse[A]`, `IsMap.parse[A]` return `ProviderResult`, not `Option` — no `.orElse` chaining | Use `Type[A] match { case IsOption(io) => ... case IsCollection(ic) => ... }` pattern matching |
| `IsMap` before `IsCollection` ordering | `Map <: Iterable`, so `IsCollection` matches maps. If checked first, maps lose key information and are handled as flat collections | Always check `IsMap` before `IsCollection` in `Type[A] match` blocks |
| `summonExprIgnoring` OOM failure | Using `Expr.summonImplicit[TC[A]]` instead of `summonExprIgnoring` when target library has auto-derivation (e.g., `Schema.derivedSchema`) causes infinite macro expansion -> OOM -> SBT crash | Always use `summonExprIgnoring` with both own `derived` and library's auto-derivation methods in `ignoredImplicits` |
| `Type.Ctor2.of[Function1].unapply` wrong on Scala 3 | `Impl.unapply` uses `case '[HKT[a, b]]` pattern matching which returns `(Nothing, Boolean)` instead of `(Int, Boolean)` for `Function1[Int, Boolean]` on Scala 3 | Wrap with `fromUntyped`: `val c = Type.Ctor2.of[Function1]; Type.Ctor2.fromUntyped[Function1](c.asUntyped)` |
| `primaryConstructor` strict type check | `primaryConstructor(Map[String, Expr_??])` checks `Expr_??.Underlying <:< paramType`. Passing `Expr[Any].as_??` for a `Function1[Any, R]` field fails | Use helper method pattern: move `Expr.quote` with `.asInstanceOf[Field]` into a method where `Field` is a regular type param with `implicit Type[Field]` |

## Related skills

- [`../hearth-documentation/`](../hearth-documentation/SKILL.md) — finding Hearth docs, verifying APIs via MCP, Hearth source as reference
- [`../hearth-def-caching/`](../hearth-def-caching/SKILL.md) — ValDefsCache detailed usage patterns
- [`../hearth-lambda-builder/`](../hearth-lambda-builder/SKILL.md) — when to use LambdaBuilder vs alternatives
- [`../hearth-standard-extensions/`](../hearth-standard-extensions/SKILL.md) — loading type extractors (IsCollection, IsMap, etc.)
