Skip to content

Add --force_curly_braces option to insert braces around single-statem…#614

Open
burner wants to merge 6 commits intodlang-community:masterfrom
burner:forced_braces
Open

Add --force_curly_braces option to insert braces around single-statem…#614
burner wants to merge 6 commits intodlang-community:masterfrom
burner:forced_braces

Conversation

@burner
Copy link

@burner burner commented Feb 17, 2026

…ent bodies.

When enabled, the preprocessor inserts synthetic { and } tokens around braceless bodies for all block header constructs: if, else, for, foreach, while, do, scope, synchronized, with, catch, debug, version, finally, and their combinations (static if, static foreach, etc.). Implementation uses token stream preprocessing so the formatter's existing brace-handling logic handles style and indentation naturally. Synthetic tokens use size_t.max-based indices to avoid collisions with AST info lookups. Handles compound constructs correctly:

  • try/catch/finally chains as single statements
  • Nested braceless blocks (each level gets its own braces)
  • Mixed braced/braceless siblings (only braceless branches get braces)
  • else if/else version/else static if chains (no double-wrapping)
  • debug/version with and without parens
  • Respects // dfmt off / // dfmt on regions

…ent bodies.

When enabled, the preprocessor inserts synthetic `{` and `}` tokens around
braceless bodies for all block header constructs: if, else, for, foreach,
while, do, scope, synchronized, with, catch, debug, version, finally, and
their combinations (static if, static foreach, etc.).
Implementation uses token stream preprocessing so the formatter's existing
brace-handling logic handles style and indentation naturally. Synthetic tokens
use size_t.max-based indices to avoid collisions with AST info lookups.
Handles compound constructs correctly:
- try/catch/finally chains as single statements
- Nested braceless blocks (each level gets its own braces)
- Mixed braced/braceless siblings (only braceless branches get braces)
- else if/else version/else static if chains (no double-wrapping)
- debug/version with and without parens
- Respects // dfmt off / // dfmt on regions
@WebFreak001
Copy link
Member

I just edited the code locally to enable your flag by default (dfmt_force_curly_braces = OptionalBoolean.t) and then reran the tests and checked some outputs:

Some bugs I encountered:

void foo() {
	string command;
	version (Posix) command ~= " 2> /dev/null 1> /dev/null";

	version (Posix) command ~= " 2> /dev/null 1> /dev/null";

	unittest
	{
		version (Posix) command ~= " 2> /dev/null 1> /dev/null";
	}
}

results in weird whitespace - I would like if the empty line could be after the closing brace instead:

void foo()
{
    string command;
    version (Posix)
    {
        command ~= " 2> /dev/null 1> /dev/null";

    }
    version (Posix)
    {
        command ~= " 2> /dev/null 1> /dev/null";

    }
    unittest
    {
        version (Posix)
        {
            command ~= " 2> /dev/null 1> /dev/null";
        }
    }
}

and it would be nice if you could make comments work, but not a dealbreaker:

unittest
{
	if (imin.value > 0x10FFFFUL) // ??
		imin.value = 0x10FFFFUL; // ??
}

->

unittest
{
    if (imin.value > 0x10FFFFUL) // ??
    {
        imin.value = 0x10FFFFUL;
    } // ??
}

The whitespace here is a bit broken, but the braces are fine imo:

unittest
{
	version (Windows) __locale_decpoint = save;
	else version (Foo) __locale_decpoint = save;
	else debug __x = 3;
	else foobar();
}

->


unittest
{
    version (Windows)
    {
        __locale_decpoint = save;
    }
    else version (Foo)
    {
        __locale_decpoint = save;
    }
    else
    {
        debug
        {
            __x = 3;
        }
    else
        {
            foobar();
        }
    }
}

It doesn't support debug / else debug / else blocks yet:

void main() {
debug (Foo) foo();
else debug (Bar) bar();
else baz();
}

->

void main()
{
    debug (Foo)
    {
        foo();
    }
    else
    {
        debug (Bar)
        {
            bar();
        }
        else
        {
            baz();
        }
    }
}

Perhaps with (...) switch (...) blocks should not be nested since they are kind of a paradigm to write together, but I'm not particularly set on that idea:

unittest
{
	with (SomeEnum) final switch (value)
	{
	case a:
		aa();
		break;
	case b:
		bb();
		break;
	}
}

should stay and not get the {} for the with (SomeEnum)

burner added 4 commits March 4, 2026 16:41
When a 'debug' conditional is followed by an 'else' clause, the 'else'
was incorrectly indented one level too deep. This was caused by:

1. 'debug' was being popped from the indent stack too early (after closing brace)
2. 'debug' was not being preserved when there's a following 'else'
3. 'debug' was not considered when aligning 'else' with its parent construct
4. 'debug' was not popped before pushing 'else' for non-braced bodies

The fix adds 'debug' to the same handling as 'if' and 'version':
- Check for 'debug' when aligning 'else' indentation (line 1800)
- Don't pop 'debug' after closing brace (line 943)
- Keep 'debug' on stack when there's a following 'else' (line 1877)
- Pop 'debug' before pushing 'else' for non-braced bodies (line 1266)

Fixes issue where complex chains like:
    version (Windows) { ... }
    else { debug { ... } else { ... } }

Would incorrectly format as:
    else { debug { ... }
    else { ... } }  // wrong indentation

Instead of:
    else { debug { ... }
        else { ... } }  // correct indentation
Added comprehensive test coverage for the bug fix:

1. Test 27 in force_curly_braces.d:
   - Complex version/else/debug/else chain
   - Tests nested conditionals with multiple else clauses

2. New test file issue_else_after_debug.d with edge cases:
   - Simple debug with else
   - Nested version with debug/else
   - Nested debug inside if/else with another else
   - Static if with debug/else
   - Multiple nested levels
   - Debug with parens and else
   - Deep nesting: version/debug/else chain

All tests verify correct indentation alignment of 'else' with its
parent conditional construct (if/version/debug) across all three
brace styles (allman, otbs, knr).
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