Skip to content

--reorder-code splits a single-line lambda that is the sole element of an array literal in a function parameter default, without adding a trailing comma — Godot rejects the output with Parse Error: Unindent doesn't match the previous indentation level #261

Description

@minami110

Tested on gdscript-formatter 0.21.0 (Pre-release 0.21.0-beta, published 2026-07-12), Linux x86_64. Upstream main checked up to 248fa13b (2026-07-12 09:20 UTC) — no fix commit found.

Impact (critical — Godot itself rejects the formatter output)

--reorder-code transforms a valid single-line lambda into a block-form lambda whose text does not parse as GDScript. Godot fails to load the whole script with Parse Error: Unindent doesn't match the previous indentation level. Any file with the triggering shape (see below) becomes silently broken after formatting: consumers of the file's types cascade into "Identifier X not declared" errors, and depending on how the file was loaded (autoload, preload chain) the whole engine boot may fail.

The transformation is also idempotent — running the formatter again on the broken output does not repair it — so the damage does not self-heal on the next format pass.

Trigger (all three conditions must hold)

  1. A function parameter default value contains an array literal.
  2. That array literal contains a single-line lambda (func(...) -> T: return X).
  3. The whole expression exceeds the max-line-length and the lambda is a sole/last element of its enclosing array literal — i.e. the token after the lambda's return X is ] (the array closer), with no comma between them.

None of these on their own is enough. The bug only shows when all three combine, which is why plain var xs = [...] at top level or in a function body is unaffected (see "Non-triggering" below).

Minimal repro

Input (bug.gd):

func demo(
		xs := [func(a: int, b: int, c: int, d: int, e: int, f: int) -> int: return a + b + c + d + e + f],
) -> void:
	print(xs)

Command:

gdscript-formatter --reorder-code bug.gd

Actual output

func demo(
		xs := [
			func(a: int, b: int, c: int, d: int, e: int, f: int) -> int:
				return a + b + c + d + e + f
		],
) -> void:
	print(xs)

Loading this in Godot 4.6.3 (verified) reports:

SCRIPT ERROR: Parse Error: Unindent doesn't match the previous indentation level.
ERROR: Failed to load script "res://bug.gd" with error "Parse error".

Root cause: the formatter split the lambda body from func(...) -> int: return a + … to func(...) -> int:\n\treturn a + …, but the closing ] of the enclosing array sits at the same indent as func(...). GDScript's parser then sees return a + … followed by a dedent to ] and cannot reconcile the indent levels (it expects either a trailing comma keeping the lambda-body statement complete, or an in-body statement, and finds neither).

Expected

Either:

  • Keep the lambda in single-line form (allow the physical line to exceed the max-line-length rather than emit invalid GDScript). This matches how black handles overlong lines it cannot legally wrap.
  • Or, if the block-form split is applied, also insert a trailing comma after return X so the parser accepts the shape. This is what a human writer would produce for the block form; it is also what the formatter already emits when the split lambda is followed by another array element (see "Related non-triggering cases" below).

Verified workaround

Adding a trailing comma inside the array literal in the input (before running the formatter) preserves the comma across the split and produces parseable output:

func demo(
		xs := [func(a: int, b: int, c: int, d: int, e: int, f: int) -> int: return a + b + c + d + e + f,],
) -> void:                                                                                              ^ added comma
	print(xs)

After --reorder-code:

func demo(
		xs := [
			func(a: int, b: int, c: int, d: int, e: int, f: int) -> int:
				return a + b + c + d + e + f,
		],
) -> void:
	print(xs)

Godot loads this variant without error. Only the case where the input array has no trailing comma is broken.

Related non-triggering cases (verified against the same formatter binary)

Each of these is otherwise the same construct but does not trigger the split-without-comma bug — helpful for narrowing down the offending code path:

  • Top-level var: var xs = [func(...) -> int: return ...] — formatter leaves the lambda single-line even if the line exceeds max-line-length. No split, no bug.
  • Function-body var: func demo() -> void: var xs := [func(...) -> int: return ...] — same as above, no split.
  • Function parameter default with a bare lambda (no array wrap): func demo(x := func(...) -> int: return ...,) -> void: — split happens, but the surrounding parameter list already provides a , after the lambda, so the resulting shape parses.
  • Array with two lambdas (the trailing comma of the first element is already present in the input): test_parameters := [[func_a, func_b],] — after split, return X, sits before func_b, so the comma survives and parses.

Concretely, the bug only fires when the split lambda has no following sibling and no trailing comma in the array literal, and only inside a function parameter default (the top-level and function-body var cases do not split at all).

Notes for the maintainer

  • 0.21.0-beta introduced the automated line-wrapping feature (Add support for automatic line wrapping #187). It is likely the wrap logic that decides to split the lambda body here without checking that the sibling context requires a trailing comma.
  • The fix is either "do not split the lambda in this position" or "always insert a trailing comma when splitting a lambda that is the last element of an array literal without one." Either one is sufficient; the second matches human style.
  • An adjacent observation, not necessarily worth its own issue: the formatter added a trailing comma to return event, in another lambda that was the last argument of a function call (Sh.setup(_a, _b, func(...): ... return event,)). There, the comma is legal (trailing arg comma) and Godot accepts it. The array-literal case seems to be the only spot where the comma is dropped.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Fields

    No fields configured for Bug.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions