From a5eaa164a77182ef7178f31d3db40c179e36e556 Mon Sep 17 00:00:00 2001 From: Marek Kaput Date: Fri, 20 Feb 2026 10:25:42 +0100 Subject: [PATCH] fix: preserve leading nil in stab clause body A -> clause body can begin with a semicolon token after optional newlines, and that semicolon represents an implicit leading nil expression in the clause rhs. The previous detection logic did not recognize the `-> \n;expr` shape reliably, so the resulting AST dropped that leading nil and diverged from Code.string_to_quoted/2 output in with ... else clauses. This change detects the leading semicolon by peeking past end-of-line trivia before consuming clause eoe tokens, preserving the expected rhs shape. A regression assertion was added to `property test regression cases` for `with x <- 1 do :ok else _ -> \n;a end` to prevent this AST mismatch from returning. fix #113 --- lib/spitfire.ex | 25 +++---------------------- test/spitfire_test.exs | 4 ++++ 2 files changed, 7 insertions(+), 22 deletions(-) diff --git a/lib/spitfire.ex b/lib/spitfire.ex index be54d5c..7d8688f 100644 --- a/lib/spitfire.ex +++ b/lib/spitfire.ex @@ -1195,28 +1195,9 @@ defmodule Spitfire do nl -> [newlines: nl] end - # Check if we have a semicolon right after -> (possibly after eol) - # Check if there's a leading semicolon right after -> - # Handles both ";expr" and "\n;expr" patterns - has_leading_semicolon = - case peek_token(parser) do - :";" -> - true - - :eol -> - # Peek at the next token after eol without consuming - # We need to manually check the token sequence - with {:eol, _} <- parser.current_token, - # Look at the tokens list to find what comes after eol - [{:";", _} | _] <- parser.tokens do - true - else - _ -> false - end - - _ -> - false - end + # A semicolon immediately after `->` (with optional newlines in between) + # starts the clause body with an implicit nil expression. + has_leading_semicolon = peek_token_skip_eol(parser) == :";" parser = eat_eoe_at(parser, 1) diff --git a/test/spitfire_test.exs b/test/spitfire_test.exs index 1dff579..9b0109f 100644 --- a/test/spitfire_test.exs +++ b/test/spitfire_test.exs @@ -2301,6 +2301,10 @@ defmodule SpitfireTest do assert Spitfire.parse("%e.(){}") == s2q("%e.(){}") assert Spitfire.parse("%e.(1){}") == s2q("%e.(1){}") assert Spitfire.parse("%e.(a, b){}") == s2q("%e.(a, b){}") + + # with/else stab body with leading semicolon after newline + assert Spitfire.parse("with x <- 1 do :ok else _ -> \n;a end") == + s2q("with x <- 1 do :ok else _ -> \n;a end") end end