Skip to content

Pretty: do not wrap non-compound if/while/for bodies in braces#109

Open
jkoppel wants to merge 1 commit into
visq:masterfrom
jkoppel:pretty-printer-roundtrip
Open

Pretty: do not wrap non-compound if/while/for bodies in braces#109
jkoppel wants to merge 1 commit into
visq:masterfrom
jkoppel:pretty-printer-roundtrip

Conversation

@jkoppel
Copy link
Copy Markdown

@jkoppel jkoppel commented May 13, 2026

What

Language.C.Pretty synthesizes a CCompound around any non-compound
if/while/for body before printing it. This commit removes that
synthesis and prints the body as-is.

Example

parseTest :: String -> IO ()
parseTest src = case parseC (inputStreamFromString src) (initPos \"<demo>\") of
  Right tu -> putStrLn (render (pretty tu))
  Left  e  -> print e

> parseTest \"int f(int x) { if (x) return 1; return 0; }\"
int f(int x)
{
    if (x)
    {
        return 1;
    }
    return 0;
}

The output has braces around return 1; that weren't in the source — those braces are introduced entirely by the pretty-printer. With this fix, the second-from-bottom line is just if (x) return 1;.

Why

parse >> pretty doesn't round-trip on any if/while/for/else
whose body is a single non-compound statement, which is a very common
shape in real-world C. The current behavior at
Pretty.hs:128 is:

prettyBody nonCompound = prettyPrec (-1) (CCompound [] [CBlockStmt nonCompound] undefined)

It wraps the body in a fresh CCompound (with undefined as its
NodeInfo, which is also a footgun if any downstream consumer
later inspects the synthesized node).

The fix:

prettyBody nonCompound = pretty nonCompound

Brace wrapping stays in the hands of whoever built the AST — the
parser already wraps with CCompound whenever the source did, so
compound bodies still print with braces.

Credit

Originally written by @dvekeman in 2016 against the downstream
jkoppel/language-c fork
(issue #1 there); never submitted upstream. The commit is preserved
intact with its original author.

Tested

Cherry-picked onto current upstream master (00db59a); cubix's
parametric-syntax test suite (~100 tests including round-trip checks
on a real C corpus) passes against the resulting language-c. Happy
to add a targeted test inside language-c itself if helpful.

`prettyBody nonCompound` synthesizes a `CCompound` around the body before
printing, so e.g.

    if (x) return 1;

is pretty-printed as

    if (x) {
      return 1;
    }

The output is semantically equivalent C but diverges from the parsed
AST: a `CReturn` becomes a `CCompound [CBlockStmt (CReturn …)]` on
the trip through `pretty`. Any consumer that needs `parse >> pretty`
to round-trip fails on every `if`/`else`/`while`/`for` that has a
single non-compound body in the source. The synthesised node also
uses `undefined` for its `NodeInfo`, which is a footgun for anything
that touches it.

This commit just prints the body as-is, leaving brace wrapping in
the hands of whoever constructed the AST (i.e., the parser, which
already wraps with `CCompound` whenever the source did).

Originally authored on a downstream fork by dvekeman in 2016
(https://github.com/jkoppel/language-c — fix for fork issue #1).
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