Skip to content

[Repo Assist] Fix FormatException when #if directive appears between attribute lists in mutually recursive type#3290

Draft
github-actions[bot] wants to merge 1 commit intomainfrom
repo-assist/fix-issue-3174-if-in-mutually-recursive-type-ccdc32837d3ac0a0
Draft

[Repo Assist] Fix FormatException when #if directive appears between attribute lists in mutually recursive type#3290
github-actions[bot] wants to merge 1 commit intomainfrom
repo-assist/fix-issue-3174-if-in-mutually-recursive-type-ccdc32837d3ac0a0

Conversation

@github-actions
Copy link
Contributor

🤖 This is an automated pull request from Repo Assist, an AI assistant.

Closes #3174

Root Cause

genOnelinerAttributes merged all AttributeListNodes into a single one-liner by collecting all attributes, taking the opening token from the first list and the closing token from the last. This silently dropped ContentBefore trivia (including #if/#endif directives) from all individual AttributeListNodes — only the outer MultipleAttributeListNode.ContentBefore was preserved via genNode n.

When NET5_0_OR_GREATER is defined, the Oak tree has two AttributeListNodes ([(Interface)] and [(Class)]) with the #endif directive stored in the second list's ContentBefore. The one-liner path dropped this directive, producing only 1 hash-fragment boundary instead of 2. The no-define variant (where both #if and #endif are on the outer node's ContentBefore) produced 2 boundaries. The mismatch caused mergeMultipleFormatResults to throw FormatException.

Fix

CodePrinter.fsgenOnelinerAttributes

Added detection: if any AttributeListNode at index ≥ 1 has a TriviaContent.Directive in its ContentBefore, fall back to rendering each list individually via genNode (which preserves per-list ContentBefore trivia). The lists are separated by sepNln and the last list is followed by sepSpace (not sepNln) so the type name follows on the same line.

CodePrinter.fsgenTypeDefn

Added directive detection for and type definitions. When attributes contain compiler directives, indent/unindent are forced around the attribute rendering and type name. This is required because attributes at column 0 after and\n are a parse error in F# — they must be indented. The indent also ensures that splitWhenHash (which uses TrimStart()) still detects the directives correctly, keeping fragment counts consistent across define variants.

Output

Input:

type X = int
and
#if NET5_0_OR_GREATER
    [(Interface)]
#endif
    [(Class)] Y = int

Formatted output (stable and idempotent):

type X = int

and
#if NET5_0_OR_GREATER
    [(Interface)]
#endif
    [(Class)] Y = int

Test Status

  • ✅ Build succeeded (0 warnings, 0 errors)
  • ✅ All 2738 tests pass (2737 existing + 1 new regression test)
  • ✅ Output is idempotent (re-formatting produces identical result)
  • ✅ Source code formatted with dotnet fantomas

Generated by Repo Assist ·

To install this agentic workflow, run

gh aw add githubnext/agentics/workflows/repo-assist.md@346204513ecfa08b81566450d7d599556807389f

…s in mutually recursive type (fixes #3174)

Root cause: genOnelinerAttributes merged all AttributeListNodes into a single
one-liner, dropping ContentBefore trivia (including #if/#endif directives) from
all but the MultipleAttributeListNode itself. When the define was active, extra
attribute lists existed and their per-list directives were silently dropped,
producing a different hash-fragment count than the no-define variant and causing
mergeMultipleFormatResults to throw a FormatException.

Fix (CodePrinter.fs):
- genOnelinerAttributes detects when any AttributeListNode at index ≥ 1 has a
  compiler directive in its ContentBefore, and falls back to rendering each list
  individually via genNode (preserving ContentBefore trivia) with sepNln between
  lists and sepSpace after the last, keeping the type name on the same line as
  the final attribute.
- genTypeDefn detects directive-containing attribute lists for 'and' type
  definitions and forces indent/unindent so the formatted output is valid F# in
  both define variants (column-0 attributes after 'and' are a parse error).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

#if in mutually recursive class definition throws

0 participants