Skip to content

ADFA-4611: Add null-safety code actions for UNSAFE_CALL#1545

Open
itsaky-adfa wants to merge 3 commits into
stagefrom
feat/ADFA-4611
Open

ADFA-4611: Add null-safety code actions for UNSAFE_CALL#1545
itsaky-adfa wants to merge 3 commits into
stagefrom
feat/ADFA-4611

Conversation

@itsaky-adfa

@itsaky-adfa itsaky-adfa commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Jira: ADFA-4611

What & why

Adds null-safety quick fixes on an UNSAFE_CALL member-access diagnostic. Three variants, each a separate suggestion on the offending receiver.selector:

  • !! - assert non-null: receiver!!.selector
  • ?. - safe call: receiver?.selector
  • ?: - Elvis fallback wrapping the receiver: (receiver ?: TODO()).selector (wrapping the receiver keeps the top-level member-access precedence, so it stays valid in any parent context)

Pure PSI/text rewrites re-derived from the live document; the trigger marker is captured as KotlinDiagnosticExtra.nullSafetyFactory. Registered in KotlinCodeActionsMenu, mirroring AddImportAction.

Tests

:lsp:kotlin:testV7DebugUnitTest - NullSafetyFixTest green (all three transforms incl. chained receiver + elvis-in-larger-expression, plus a trigger-path test that drives the real analyzer to assert the UNSAFE_CALL marker). :lsp:kotlin:assembleV8Debug builds. Spotless can't run in the worktree; formatting hand-verified.

Notes

Scope is UNSAFE_CALL member access (the ticket's case). Only cross-branch overlap with the sibling code-action branches is the KotlinCodeActionsMenu list append (trivial keep-both).

Offer three quick fixes on an UNSAFE_CALL member access: assert non-null (!!),
safe call (?.), and an Elvis fallback that wraps the receiver
((receiver ?: TODO()).selector) so the top-level access keeps its precedence
and stays valid in any parent context. Pure PSI/text rewrites re-derived from
the live document; the trigger marker is captured as
KotlinDiagnosticExtra.nullSafetyFactory. Adds NullSafetyFixTest.
@itsaky-adfa itsaky-adfa self-assigned this Jul 17, 2026

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough
  • Added Kotlin UNSAFE_CALL quick fixes for:
    • Non-null assertions (!!)
    • Safe calls (?.)
    • Elvis fallbacks using TODO()
  • Registered the new null-safety action in the Kotlin code actions menu.
  • Added diagnostic metadata to identify eligible null-safety fixes.
  • Added localized labels for the new actions.
  • Added tests covering chained accesses, larger Elvis expressions, rewrite behavior, and analyzer-triggered diagnostics.
  • Risk: the Elvis fix inserts TODO() as a placeholder, which may require developer replacement before production use.
  • Validation: NullSafetyFixTest passes and :lsp:kotlin:assembleV8Debug builds.

Walkthrough

Adds Kotlin null-safety diagnostic metadata, rewrite utilities, and a registered code action offering non-null assertion, safe-call, and Elvis fixes. The changes include localized labels and tests covering rewrites, diagnostic markers, and range matching.

Changes

Kotlin null-safety quick fixes

Layer / File(s) Summary
Diagnostic metadata and rewrite engine
lsp/kotlin/.../diagnostic/KotlinDiagnosticProvider.kt, lsp/kotlin/.../utils/NullSafetyFix.kt
Unsafe-call diagnostics now carry null-safety metadata, and utilities generate three corresponding text-edit variants.
Code action execution and presentation
lsp/kotlin/.../actions/NullSafetyAction.kt, lsp/kotlin/.../KotlinCodeActionsMenu.kt, resources/src/main/res/values/strings.xml
Registers NullSafetyAction, filters it to eligible diagnostics, applies one fix directly, or presents multiple fixes with localized labels.
Rewrite and diagnostic validation
lsp/kotlin/src/test/.../utils/NullSafetyFixTest.kt
Tests property and method rewrites, chained accesses, Elvis expressions, diagnostic markers, and mismatched ranges.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Editor
  participant KotlinDiagnosticProvider
  participant NullSafetyAction
  participant NullSafetyFix
  participant CodeActionDialog
  Editor->>KotlinDiagnosticProvider: request diagnostics
  KotlinDiagnosticProvider-->>Editor: return UNSAFE_CALL metadata
  Editor->>NullSafetyAction: request code actions
  NullSafetyAction->>NullSafetyFix: compute rewrite variants
  NullSafetyFix-->>NullSafetyAction: return text edits
  NullSafetyAction->>CodeActionDialog: show variants when multiple exist
  CodeActionDialog-->>Editor: apply selected text edit
Loading

Possibly related PRs

Suggested reviewers: dara-abijo-adfa, jatezzz

Poem

I’m a bunny fixing nullable code,
With !!, ?., or ?: bestowed.
Diagnostics guide my little feet,
Three neat edits make errors sweet.
Hop, select, and code is bright!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding null-safety code actions for UNSAFE_CALL diagnostics.
Description check ✅ Passed The description directly matches the implemented null-safety quick fixes and related tests.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/ADFA-4611

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@itsaky-adfa
itsaky-adfa requested a review from a team July 17, 2026 14:33
Signed-off-by: Akash Yadav <akashyadav@appdevforall.org>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/NullSafetyAction.kt`:
- Around line 58-83: Update execAction to replace runCatching with explicit
narrow exception handling around the diagnostic/file extraction and null-safety
computation. Catch only the expected IllegalArgumentException and
IllegalStateException failures for logging and return emptyList(), while always
rethrowing CancellationException before any broader handling; preserve the
existing successful result and fallback behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 16e64a1e-146c-4237-93de-d2f9d1e19916

📥 Commits

Reviewing files that changed from the base of the PR and between 8077407 and 90438e4.

📒 Files selected for processing (6)
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/KotlinCodeActionsMenu.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/NullSafetyAction.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/diagnostic/KotlinDiagnosticProvider.kt
  • lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/utils/NullSafetyFix.kt
  • lsp/kotlin/src/test/java/com/itsaky/androidide/lsp/kotlin/utils/NullSafetyFixTest.kt
  • resources/src/main/res/values/strings.xml

Comment on lines +58 to +83
override suspend fun execAction(data: ActionData): List<NullSafetyVariant> =
runCatching {
val diagnostic = data.require<DiagnosticItem>()
val extra = diagnostic.extra as? KotlinDiagnosticExtra ?: return emptyList()
if (extra.nullSafetyFactory == null) return emptyList()

val nioPath = data.requireFile().toPath()
// Fetch the live KtFile BEFORE entering `read` (deadlock rule: its refresh needs write access).
val ktFile =
extra.compilationEnv.ktSymbolIndex
.getCurrentKtFile(nioPath)
.get() ?: return emptyList()

extra.compilationEnv.project.read {
val qe =
findNullableMemberAccess(
ktFile,
diagnostic.range.start.requireIndex(),
diagnostic.range.end.requireIndex(),
) ?: return@read emptyList()
nullSafetyVariants(qe)
}
}.getOrElse { e ->
logger.warn("Failed to compute null-safety fixes", e)
emptyList()
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Prevent swallowing coroutine CancellationException and use narrow exception handling.

runCatching catches all Throwables, including CancellationException. Swallowing it breaks coroutine cancellation hierarchies and can lead to hangs or leaked work. As per coding guidelines and based on learnings, we should prefer narrow exception handling (e.g., catching IllegalArgumentException and IllegalStateException for data-extraction failures) instead of a broad catch-all, and ensure CancellationException is always rethrown.

🛠️ Proposed fix to replace `runCatching` with targeted catches
-	override suspend fun execAction(data: ActionData): List<NullSafetyVariant> =
-		runCatching {
+	override suspend fun execAction(data: ActionData): List<NullSafetyVariant> {
+		return try {
 			val diagnostic = data.require<DiagnosticItem>()
 			val extra = diagnostic.extra as? KotlinDiagnosticExtra ?: return emptyList()
 			if (extra.nullSafetyFactory == null) return emptyList()
 
 			val nioPath = data.requireFile().toPath()
 			// Fetch the live KtFile BEFORE entering `read` (deadlock rule: its refresh needs write access).
 			val ktFile =
 				extra.compilationEnv.ktSymbolIndex
 					.getCurrentKtFile(nioPath)
 					.get() ?: return emptyList()
 
 			extra.compilationEnv.project.read {
 				val qe =
 					findNullableMemberAccess(
 						ktFile,
 						diagnostic.range.start.requireIndex(),
 						diagnostic.range.end.requireIndex(),
 					) ?: return@read emptyList()
 				nullSafetyVariants(qe)
 			}
-		}.getOrElse { e ->
-			logger.warn("Failed to compute null-safety fixes", e)
-			emptyList()
-		}
+		} catch (e: IllegalArgumentException) {
+			logger.warn("Failed to compute null-safety fixes", e)
+			emptyList()
+		} catch (e: IllegalStateException) {
+			logger.warn("Failed to compute null-safety fixes", e)
+			emptyList()
+		}
+	}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@lsp/kotlin/src/main/java/com/itsaky/androidide/lsp/kotlin/actions/NullSafetyAction.kt`
around lines 58 - 83, Update execAction to replace runCatching with explicit
narrow exception handling around the diagnostic/file extraction and null-safety
computation. Catch only the expected IllegalArgumentException and
IllegalStateException failures for logging and return emptyList(), while always
rethrowing CancellationException before any broader handling; preserve the
existing successful result and fallback behavior.

Sources: Coding guidelines, Learnings

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants