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 Exprs, 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] |