{"record":{"id":"158327cd75d48720","repo":"airbnb/epoxy","slug":"nullability-has-not-been-set","errorCode":null,"errorMessage":"Nullability has not been set","messagePattern":"Nullability has not been set","errorType":"exception","errorClass":"IllegalStateException","httpStatus":null,"severity":"error","filePath":"epoxy-processor/src/main/java/com/airbnb/epoxy/processor/AttributeInfo.kt","lineNumber":177,"sourceCode":"        var implicit: CodeBlock? = null\n\n        val isPresent: Boolean\n            get() = explicit != null || implicit != null\n\n        val isEmpty: Boolean\n            get() = !isPresent\n\n        fun value(): CodeBlock? = explicit ?: implicit\n    }\n\n    protected fun setJavaDocString(docComment: String?) {\n        javaDoc = docComment?.trim()\n            ?.let { if (it.isNotEmpty()) CodeBlock.of(it) else null }\n    }\n\n    fun isNullable(): Boolean {\n        if (!hasSetNullability()) {\n            throw IllegalStateException(\"Nullability has not been set\")\n        }\n\n        return isNullable == true\n    }\n\n    fun hasSetNullability(): Boolean = isNullable != null\n\n    fun getterCode(): String = if (isPrivate) getterMethodName!! + \"()\" else fieldName\n\n    // Special case to avoid generating recursive getter if field and its getter names are the same\n    fun superGetterCode(): String =\n        if (isPrivate) String.format(\"super.%s()\", getterMethodName) else fieldName\n\n    fun setterCode(): String =\n        (if (isGenerated) \"this.\" else \"super.\") +\n            if (isPrivate)\n                setterMethodName!! + \"(\\$L)\"\n            else","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/airbnb/epoxy/blob/e45bd3a61fe3a1f130e184f5b8dcf172ab99025a/epoxy-processor/src/main/java/com/airbnb/epoxy/processor/AttributeInfo.kt#L159-L195","documentation":"AttributeInfo tracks whether an Epoxy model attribute is nullable via a nullable Boolean. isNullable() is only valid after nullability has been explicitly resolved (hasSetNullability()); if processors or extensions query nullability before it was set, the annotation processor throws this IllegalStateException to prevent silently defaulting to non-null.","triggerScenarios":"A processor extension or custom validator calls attributeInfo.isNullable() before setNullable/derivation ran — e.g. reading attributes in a custom step before EpoxyProcessor's nullability resolution stage.","commonSituations":"Custom Epoxy annotation-processor plugins hooking into the wrong processing round; forked/patched EpoxyProcessor code paths; new attribute types added to the processor without setting nullability in the builder.","solutions":["Call hasSetNullability() first and skip or defer isNullable() when false.","Ensure the attribute builder sets nullability (setNullable or package-derived) before the attribute is used.","If writing a processor extension, move the nullability query to a later processing step after EpoxyProcessor resolves it.","Upgrade epoxy-processor — versions may resolve nullability earlier for your attribute kind."],"exampleFix":"// before\nval nullable = attributeInfo.isNullable() // throws if unset\n\n// after\nval nullable = if (attributeInfo.hasSetNullability()) attributeInfo.isNullable() else false","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"fun AttributeInfo.nullabilityOrNull(): Boolean? =\n  if (hasSetNullability()) isNullable() else null","tryCatchPattern":null,"preventionTips":["Always gate isNullable() behind hasSetNullability().","Query nullability only in the late processing rounds, after EpoxyProcessor resolves it.","In custom attribute builders, always set nullability before handing the AttributeInfo around."],"tags":["android","epoxy","annotation-processor","kotlin","nullability"],"backgroundTag":"internal-invariant-violation","analyzedSha":"e45bd3a61fe3a1f130e184f5b8dcf172ab99025a","analyzedAt":"2026-09-13T03:24:16.052Z","contentChangedAt":"2026-09-13T03:24:16.052Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}