0.4.0 - Method Builder Chain, DestructuredExpr, AnonymousInstance, ExprCodec.derived, Union Soundness, and Call-Site Scopes
LatestAfter the 0.3.x line wrapped up with the "more exciting things to come in 0.4.0!" promise, here it is — the release where Hearth grew the building blocks for real derivation and metaprogramming libraries. 0.4.0 is a breaking feature release: it reworks the Method API into a typed builder chain, adds semantic expression decomposition (DestructuredExpr), compile-time anonymous instantiation (AnonymousInstance), semi-automatic ExprCodec.derived, and a call-site-aware lexical scope primitive — alongside a serious soundness pass on union types and a batch of cross-platform bug fixes. Most of this work was driven by, and verified end-to-end against, the Kindlings and refined-compat libraries.
Highlights
-
Method builder chain — a reworked, typed
MethodAPI (#272)- The old
Method[+Instance, Returned]is replaced by an unparameterizedMethodsealed trait with a four-step builder chain:OnInstance→ApplyTypes→ApplyValues→Result[R]. - Type-parametric (polymorphic) methods are now representable; adds path-dependency detection,
fold/foldF,prettyPrint, visibility scoping, modifier flags, type bounds, and clause interleaving. - Calling methods, folding over them, and pattern matching on them are all documented with runnable examples.
- The old
-
DestructuredExpr— semantic expression decomposition (#272)- Decomposes a macro expression into resolved nodes:
MethodCall,Lambda,ParamRef,Literal,Singleton,Block,Varargs, andNonDestructurable. - Constructor calls (
new Foo(args)) are parsed asMethodCalls, enabling cross-platform annotation/constructor-argument destructuring with no platform-specific code.
- Decomposes a macro expression into resolved nodes:
-
AnonymousInstance— compile-time anonymous subtype instantiation (#272, #287, #289)- Instantiate anonymous subtypes of a trait/class at macro-expansion time with validation, diamond-conflict detection, and constructor selection.
- Supports overloaded and generic/polymorphic abstract-method overrides, symbolic/operator names,
implicit/usingclauses, abstractvaltargets,this.type(fluent) returns, by-name parameters, and context-function returns — surfaced throughOverrideContext(returnType,typeParameters,returnsThisType). These were the building blocks Kindlings needed for ScalaMock-style mocking.
-
ExprCodec.derived[A]+semiQuote(#272)ExprCodec.derived[A]— semi-automaticExprCodecderivation for case classes, sealed traits, and singletons, using override maps from implicit scope.semiQuote— converts runtime values back into expression trees (the inverse ofExpr.semiEval);semiEvalgains anEvalOverrideoverload for custom per-type dispatch.toExpron an unquotable type now reports a clean positioned diagnostic instead of throwing anAssertionError(#287).
-
Union types: soundness fixes & TypeTest fallback (#286)
- Soundness fix: union member disjointness was unsound (
List[Int] | Seq[String]could dispatch to the wrong branch). It is now checked via compiler class symbols (classSymbol+derivesFrom+ boxed-symbol normalization), which also works for types compiled in the same run. - Singleton union members (
Color.Red.type | Color.Blue.type) are matched by value viaeqValueinstead of being refused. - Same-erasure unions (
List[Int] | List[String]) work when user-providedscala.reflect.TypeTestgivens are in scope; refusals now name the missing instance.
- Soundness fix: union member disjointness was unsound (
-
New type/annotation/parameter APIs (
@since 0.4.0) (#286)Type.opaqueUnderlyingType— resolve the underlying type behind a Scala 3 opaque alias (with type-arg re-substitution for applied parameterized opaques).Type.decompose1/decompose2(returning live, re-applicableCtors),Type.typeArguments,sameTypeConstructorAson allCtorN/CtorK1/UntypedType, plus untypeddealias/typeConstructor(#284).annotationsOfType[Ann]/hasAnnotationOfType[Ann]onType/Method/Parameter, andAnnotations.constructorArguments/decodedConstructorArguments(#283).Parameter.isVararg(with cross-platformSeq[A]normalization,A*rendering, vararg splicing, and Java varargs support); fixed Scala 3Parameter.isByNamealways returningfalse; newParameter.byNameUnderlying(#289).
-
Call-site scopes:
enclosingScope& source text (#287)enclosingScope: NonEmptyVector[Enclosure]— the lexical enclosure chain from the current scope outwards (Method/Value/Class/Object/Package), each with name / fullName / position;ClassandObjectexpose callablemembersplus a receiver. Enables macwire-style dependency injection and other call-site-aware macros. (Local vals inside the enclosing method body are populated on Scala 2; Scala 3 returnsNilas a documented follow-up.)Position.sourceCodeandExpr/UntypedExpr.position/.sourceCode, plus a runtimeText[T]materializer — the primitives expecty/munit-styleassertmacros need.- Positioned reporting overloads:
reportInfo/reportWarn/reportError/reportErrorAndAbort(msg, position).
-
Standard-extensions performance: zero-closure codegen (#291)
IsCollectionOf.foreachnow defaults to a zero-closurewhileloop (wasiterable.foreach(<Function1>), allocating a closure per iteration for List/Vector/Map/Set).IsEitherProviderForScalaEither.foldnow generates a zero-closurematchviaMatchCaseinstead ofeither.fold(<closure>, <closure>)— enabling allocation-free fail-fast decoders.MatchCase.matchOn(Scala 3) re-owns case bodies to the splice owner, fixing "Block contains definitions with different owners" for lambda-bearing case bodies.IsCollectionOf.asMap: Option[IsMapOf[CollA, Item]]— typed map-ness check without a secondIsCollection.parseor an erasure-unsoundisInstanceOf.
-
Breaking changes for 0.4.0
CaseClass.caseFieldValuesAt's default visibility changed fromAnywheretoAtCallSite— the derivation-safe default, so generatedinstance.fieldaccess compiles at the expansion point.Anywhere(all fields, for enumeration) andEverywhere(public only) remain available. (#287)Accessible.Unrestrictedwas renamed toAccessible.Anywhere. (#287)- The
Method[+Instance, Returned]API was replaced by the unparameterizedMethodbuilder chain (see above). (#272)
-
Cross-platform bug fixes
- Self-referential implicit
Type/Ctordefinitions (implicit val ConfigT: Type[Configuration] = Type.of[Configuration]) no longer self-reference in cross-quotes — fixes a silent wrong-code/NPE case on Scala 2 (#285). - Scala 3 annotation expressions are now spliceable under
-Xcheck-macros(trees rebuilt with proper spans); Scala 2ExprCodecexprs no longer report the codec's free type param; Scala 3 no-posreportWarnnow emits a real warning. Type.parentswas broken on both platforms (Scala 2 infinite recursion; Scala 3NoSuchMethodExceptionon 3.3.x) — now derived from linearization/ClassInfoType.- Scala 3.8.4 compatibility:
safeMemberTypewrapper forTypeRepr.memberType, default-value handling on generic case classes, and context-function-return desugaring fixes.
- Self-referential implicit
-
Hearth-based libraries to learn from
- Kindlings — a repository showing how Hearth features are used in practice (Show, Schema, codecs, mocking, DI, and more). The newest release based on Hearth 0.4.0 is 0.3.0.
- refined-compat — a Hearth-based macro library that lets you migrate
Refinedcodebases to Scala 3. Now released as 0.2.0.
-
Build & CI
- Build simplified further via sbt-kubuszok 0.2.x; scala-steward pinned to Scala 3.3 LTS + sbt 1.x;
scala-yamlbumped to 0.3.2; routine GitHub Actions/dependency bumps.
- Build simplified further via sbt-kubuszok 0.2.x; scala-steward pinned to Scala 3.3 LTS + sbt 1.x;
Summary
0.4.0 turns Hearth from a metaprogramming toolkit into a foundation for real derivation and code-generation libraries: a typed Method builder chain, semantic DestructuredExpr decomposition, compile-time AnonymousInstance instantiation, ExprCodec.derived with semiQuote, sound union dispatch with a TypeTest fallback, a call-site-aware enclosingScope primitive, and zero-closure standard-extension codegen. It is a breaking release (notably the Method API rework and the caseFieldValuesAt default change), and almost every feature was hardened against real downstream use in Kindlings and refined-compat.
Be sure to look at our documentation, star the project ⭐ and leave us some feedback!