Troubleshooting¶
This guide is for the common user-facing failure modes in kotlin-typeclasses.
The fast way to debug a failure is:
- identify the diagnostic family
- enable focused tracing on the failing scope
- check placement, visibility, derivation prerequisites, and builtin materialization rules
Quick Triage¶
The most common diagnostic families are:
TC_NO_CONTEXT_ARGUMENT: no viable evidence was found for the requested goalTC_AMBIGUOUS_INSTANCE: more than one viable candidate remainedTC_INVALID_INSTANCE_DECL: an@Instancedeclaration has an unsupported shape, owner, or provided typeTC_CANNOT_DERIVE: a requested derivation failed validation or could not be completedTC_INVALID_EQUIV_DECL: user code tried to author compiler-ownedEquivevidence directlyTC_INVALID_BUILTIN_EVIDENCE: IR reached a builtin-evidence request that cannot be materialized safely
TC_NO_CONTEXT_ARGUMENT¶
This means resolution did not end with a usable candidate.
Check these first:
- the requested head is actually annotated with
@Typeclass - the call site or lexical scope already has direct contextual evidence
- a matching
@Instanceexists in associated scope or legal top-level scope - the candidate's prerequisites can themselves be solved
- the relevant declaration is visible from the current module
- the goal is not asking for a builtin that is disabled or not materializable
Common causes¶
- missing
@Typeclasson the head @Instancedeclared in the wrong place- top-level
@Instancedeclared as an arbitrary orphan - dependency instance is
internalorprivate @Deriverequested a typeclass that was never actually derived- builtin requests like
KClass<T>,KSerializer<T>,KnownType<T>, orTypeId<T>are made for a non-reified or otherwise non-materializableT
Check owner-file placement¶
Top-level @Instance declarations are constrained. A missing-evidence or invalid-instance error often means the declaration is not in a legal owner file.
Fast check:
- the file must declare the typeclass head, or
- the file must declare one of the concrete provided classifiers in the target type
Use Instance Authoring as the canonical placement guide, including examples of legal and illegal owner files.
TC_AMBIGUOUS_INSTANCE¶
This means more than one candidate survived resolution.
Typical causes:
- two overlapping generic rules
- a manual rule and a derived rule both provide the same target
- two different
@DeriveViapaths reach the same requested typeclass goal through distinct user-visible paths - local and non-local evidence both remain viable in a shape the resolver cannot collapse
How to fix it:
- remove or narrow one of the competing rules
- move instances into a more specific companion so unrelated code stops seeing them
- keep only one canonical rule per reachable scope for a given head/target pair
- pass the desired evidence explicitly through local context when you need a local override
TC_INVALID_INSTANCE_DECL¶
This means an @Instance declaration was rejected at declaration site before normal call-site resolution.
Common causes:
- the declaration is local or a non-companion member
- a top-level instance lives in a file that owns none of the typeclass head or provided concrete target classifiers
- an instance function has ordinary value parameters
- an instance property is mutable
- the provided type is not a supported
@Typeclassapplication - a generic instance rule has type parameters or bounds that cannot be represented safely by the rule model
How to fix it:
- move the declaration to the typeclass companion, target-type companion, or a legal owner file
- use an
object, immutable property, or parameterless function whose context parameters are prerequisites - make the provided result type explicit when inference hides the real typeclass head
- replace local helper evidence with an explicit context parameter if it is meant to be local-only
TC_CANNOT_DERIVE¶
This means the compiler validated a derivation request and rejected it, or could not complete it soundly.
Common causes for @Derive:
- the typeclass companion does not implement the required deriver contract
- a required field or case instance is missing
- the requested typeclass shape is outside the current derivation contract
- a sealed root cannot be exported because one case is unsupported or unresolved
Common causes for @DeriveVia:
- empty path
- generic target classes outside the current monomorphic boundary
- pinned
Isopath segments that are disconnected, not singleton objects, or ambiguously attachable - transported members mention the transported type parameter in unsupported positions
Common causes for @DeriveEquiv:
- validated or normalizing constructors
- extra hidden or mutable state
- ambiguous product permutations or sum-case matches
- generic targets outside the current supported boundary
For the full contracts, see Derivation.
TC_INVALID_EQUIV_DECL¶
Equiv<A, B> is compiler-owned.
That means user code should not:
- subclass
Equivdirectly - publish manual
@Instancevalues ofEquiv - treat
Equivas the ordinary user-authored reversible-conversion surface
Use:
Iso<A, B>for explicit user-authored reversible conversions@DeriveEquivwhen you want compiler-exported equivalence evidence@DeriveViawhen you want one derivation request to transport through equivalence
TC_INVALID_BUILTIN_EVIDENCE¶
This means a builtin proof or optional builtin evidence request reached IR but could not be materialized.
Common causes:
KClass<T>was requested for nullable or non-runtime-materializableTKSerializer<T>was requested for a type the plugin cannot prove serializableKnownType<T>orTypeId<T>was requested for an unfixed non-reified type parameter- a builtin proof survived earlier filtering but exact backend materialization found a stricter mismatch
How to fix it:
- make the type concrete at the call site
- use
inline reifiedonly for builtins that explicitly support reified materialization - pass explicit local evidence when the compiler cannot prove the builtin itself
- enable the optional builtin if the failure is for
KClass<T>orKSerializer<T>
Resolution Tracing¶
When the failure is not obvious, enable tracing at the narrowest useful scope.
Global compiler option:
-P plugin:one.wabbit.typeclass:typeclassTraceMode=failures-and-alternatives
Source-scoped tracing:
@file:DebugTypeclassResolution(TypeclassTraceMode.FAILURES_AND_ALTERNATIVES)
Useful modes:
FAILURES: trace failed and ambiguous rootsFAILURES_AND_ALTERNATIVES: same, plus alternative candidate summariesALL: also trace successful rootsALL_AND_ALTERNATIVES: full local tracing plus alternative candidate summariesINHERIT: keep the parent or global modeDISABLED: mute a nested scope
Important detail:
- bare
@DebugTypeclassResolutionmeansFAILURES - it does not mean "inherit whatever the outer scope is doing"
- if you want to preserve an outer
ALLorALL_AND_ALTERNATIVESmode, usemode = INHERIT
Tracing is rooted at the failing declaration or call site. Annotating a callee does not automatically trace every caller.
Important limitation:
- alternative output is shallow
- candidates that were actually searched can report concrete reasons like missing or ambiguous prerequisites
- candidates that were not fully searched may only report goal-shape applicability, not a complete proof of why they would ultimately fail
Builtin Materialization Failures¶
These failures often present as ordinary TC_NO_CONTEXT_ARGUMENT, but the real issue is materialization.
KClass<T>¶
Check:
builtinKClassTypeclass=enabledTis non-nullableTis runtime-materializable- generic helpers are
reified
What fails:
summon<KClass<T>>()in a plain non-reified generic functionsummon<KClass<String?>>()
KSerializer<T>¶
Check:
builtinKSerializerTypeclass=enabledkotlinx.serializationruntime is present- the serialization compiler plugin is applied where needed
Tis serializable and runtime-materializable- the requested goal is not star-projected
What fails:
- non-serializable target types
KSerializer<List<*>>- non-reified generic
T
KnownType<T> And TypeId<T>¶
Check:
Tis concrete or reified enough for runtime materialization
What fails:
- unfixed non-reified generic
T
For exact contracts, see Proofs And Builtins.
Cross-Module Surprises¶
When a producer module compiles but a consumer cannot resolve evidence, check:
- whether the instance is actually
public - whether the evidence was exported at all
- whether the consumer compilation itself has the plugin enabled
Important examples:
internalandprivatedependency instances do not leak downstream- public companion instances from dependencies do participate in downstream resolution
- derived sealed-root evidence is not exported if the producer's hierarchy is incomplete or unsupported
@DeriveEquivexports summonableEquiv, but transientEquivlinks used only inside one@DeriveViarequest do not
For the full cross-module model, see Multi-Module Behavior.
Current Hard Limitation¶
Contextual property getter reads are still limited by the public FIR plugin API's lack of a property-read refinement hook.
That means source shapes like contextual property reads can still fail even when analogous function calls work.
See compiler-plugin/ISSUE_PROPERTIES.md for the current status.