Skip to content

Don't leak an expression body's indent into later declarations - #2365

Open
eyupcanakman wants to merge 1 commit into
square:mainfrom
eyupcanakman:fix/expression-body-runaway-indent-1421
Open

Don't leak an expression body's indent into later declarations#2365
eyupcanakman wants to merge 1 commit into
square:mainfrom
eyupcanakman:fix/expression-body-runaway-indent-1421

Conversation

@eyupcanakman

@eyupcanakman eyupcanakman commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

An expression body that opens or closes an indent leaks it into every declaration after it, so each following function lands two spaces deeper than the last. Deeper nesting throws cannot unindent instead.

CodeBlock.trim() strips no-arg placeholders from both ends, dropping one half of a pair while its partner stays behind. It now takes them back from either end until nothing is left unmatched.

A block that was already unmatched before trimming is left alone, since taking one end back cannot balance it. Markers that open and close out of order are left alone as well, and building that shape takes raw placeholders rather than the builder.

A body that opens an indent before the returned expression emits as a block body now, since the trimmed block no longer starts with the return. Same code, different shape. Most of the diff is tests.

Fixes #1421.

  • docs/changelog.md has been updated if applicable.
    • Changes not visible to library consumers, such as build script, documentation, or test code updates, don't need to
      be added to the changelog.
  • CLA signed.

@JakeWharton

Copy link
Copy Markdown
Collaborator

I'm wondering if we should just turn off expression bodies. Seems like we only gain pain by having them enabled.

@eyupcanakman
eyupcanakman force-pushed the fix/expression-body-runaway-indent-1421 branch from d8ef22e to 731768a Compare August 21, 2026 16:11
@eyupcanakman

Copy link
Copy Markdown
Contributor Author

Turning them off does fix it, but 101 of the 1172 tests here fail and the two #1300 examples don't move at all since those are property initializers rather than function bodies. Reworked this one to leave a block alone when its indents already don't match.

Comment thread kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/CodeBlock.kt Outdated
Comment thread kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/CodeBlock.kt Outdated
A function body that opens or closes an indent had that half trimmed away while its partner stayed, so CodeWriter kept the wrong level for every declaration after it.
Deeper cases threw cannot unindent instead.
Trimming now takes placeholders back from either end until nothing in the block is left unmatched.

Fixes square#1421.
@eyupcanakman
eyupcanakman force-pushed the fix/expression-body-runaway-indent-1421 branch from 731768a to 1d828b5 Compare August 28, 2026 20:26

@Egorand Egorand left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good overall, got some feedback on naming.

'«' -> statementTotal++
'»' -> statementTotal--
}
if (indentTotal < 0 || statementTotal < 0) break

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there a test case where this statement will evaluate to true? All tests still pass if I comment it out.

Comment thread kotlinpoet/src/jvmMain/kotlin/com/squareup/kotlinpoet/CodeBlock.kt Outdated
while (start < end && formatParts[end - 1] in NO_ARG_PLACEHOLDERS) {
end--
}
if (keepBalanced && (start > 0 || end < formatParts.size)) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It took me a while to fully grasp what's going on here, and something that I think could help is making the naming a bit less ambiguous, I've got a few suggestions:

  1. First, let's rename start and end above to keepFrom and keepUntil. Simpler naming was fine while this method was trivial, now it heavily clashes with first, last and keptEnd making the whole thing hard to grok.
  2. Similarly, let's rename keptEnd to keepPlaceholdersUntil.
  3. Let's also introduce keepPlaceholdersFrom instead of using keepFrom. While it's convenient that they have the same value, IMO it's better to have a separate pointer for a separate array.
  4. We can probably squash first and last into a single variable called pointer or just i, but don't feel strongly here.


internal fun hasStatements() = formatParts.any { "«" in it }

private fun isBalanced(placeholders: CharArray, count: Int): Boolean {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Let's move this helper inside trim()?

}

@Test
fun trimKeepsAStatementAndItsIndentTogether() {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

nit: maybe trimKeepsStatementsAndIndentsBalanced?

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.

Expression function runaway indentation

3 participants