Skip to content

Prefer the function reading of a return-annotated let binding head - #248

Open
pcshrosbree wants to merge 1 commit into
ionide:mainfrom
pcshrosbree:fix/return-annotated-function-head
Open

pcshrosbree wants to merge 1 commit into
ionide:mainfrom
pcshrosbree:fix/return-annotated-function-head

Conversation

@pcshrosbree

@pcshrosbree pcshrosbree commented Oct 1, 2026 •

Copy link
Copy Markdown

Fixes the let case of #244. The member case is still open; see below.

let h (g: int -> int) : int = g 42 has two readings in this grammar:

  • a function_declaration_left (h with argument_patterns) followed by the return type;
  • a value_declaration_left whose pattern swallows the return type: h (g …) : int as an identifier pattern with a typed argument.

FSC's parser produces one SynPat.LongIdent head either way and lets the checker decide. This grammar, though, prefers the function reading: every head without a return annotation, and even let Some x = …, parses as function_declaration_left. With the annotation, both GLR versions survive to the =, and the static precedences (3 and 2) cannot rank them there. The winner depended on what followed: the value reading when the binding was last in the file, the function reading otherwise.

Change

prec.dynamic(1, …) on function_declaration_left, so the function reading wins whenever both versions survive. The parse tables are the same size, since dynamic precedence only breaks GLR ties.

Tests

New tests in test/corpus/function_defn.txt cover a return-annotated function as the last binding in the file: with one parameter (the issue's example), with two, and with private. All fail on main and parse cleanly with FCS (Fantomas.FCS 7.0.5).

One existing test changes its expected tree. multidimensional array types (let f (x: float[,]) : float[,,,] = / x) recorded the value reading for exactly this case. It now has the function head, with x: float[,] in argument_patterns and float[,,,] as the return type.

Not fixed here: members

member _.H (f: int -> int) : int = f 21 still folds the return type into the last argument, as typed_pattern (paren_pattern …) int. _method_defn has no return-type slot: its args are repeat1($._pattern), and a trailing : T parses as a typed pattern statically, not as a GLR tie that dynamic precedence could break. Adding an optional return type there changes nothing for the same reason. FSC's member arguments are atomic patterns, so the principled fix is atomic args. In this grammar, though, _atomic_pattern has other node shapes: a bare long_identifier, and parentheses without a paren_pattern node. That would rewrite the expected tree of nearly every member test, so I've left it for a separate change.

Corpus

  • check:baseline: no file changes its error count.
  • check:invalid: all recorded errors are still found.

Fixes the let case of ionide#244 (members are still open).
`let h (g: int -> int) : int = g 42` also parses as a value binding
whose pattern swallows the return type (`h (g ...) : int` as a
typed constructor pattern). Both GLR versions survive to the `=`, and
the static precedences of function_declaration_left (3) and
value_declaration_left (2) cannot rank them there, so the winner
depended on what followed the binding: a value_declaration_left as the
last binding in a file, a function_declaration_left otherwise.

A dynamic precedence on function_declaration_left makes the function
reading win whenever both survive, as every unannotated head already
parses. The parse tables are unchanged in size, and no corpus file
parses differently by error count.

This changes the expected tree of one existing test, "multidimensional
array types" (`let f (x: float[,]) : float[,,,] = x`), which recorded the
value-binding shape for exactly this case.
@pcshrosbree

Copy link
Copy Markdown
Author

This PR is one of a set; #255 maps them all, with their dependencies and merge order.

This branch has not been deployed

No deployments
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.

1 participant