fsharp / fsharp/fslang-suggestions
Rework incorrect indentation recovery
- Dominant language
- No language data
- Stars
- 373
- Forks
- 21
- PR merge metrics
- No merged PRs in 30d
Description
F# has indentation-based syntax. It's implemented via offside rules in a component called `LexFilter`. LexFilter works between lexing and parsing stages and keeps track of the indentation blocks and inserts additional `begin`- and `end`-like tokens for the parser.
To help newcomers write F# code, LexFilter has a recovery rule that is used when a token is insufficiently indented. This rule produces a warning and starts a new indentation block anyway:
While the warning is good, the insertion of the block causes many problems for the parsing and analysis when writing code. Consider a case where a user is writing a new type member:

There are subsequent definitions and expressions, and all of them are parsed and analyzed correctly.
As soon as the user types `=` in, the LexFilter begins to expect an indented member body below, doesn't find it, and applies the recovery rule:

That makes all the code below parsed incorrectly as the body of the property. Very often it makes the parser fail to parse the file at all. In the example above, the lenses below the edited place are gone. They will reappear when you type an expression after `=`, making the code move up and down and blink. There's an unexpected error about attributes inside an expression. The `P` type is inferred incorrectly.
The bigger a file gets, the more unexpected parser/analysis errors appear, because neither the parser nor the type checker expect arbitrary code below to be parsed as the indented block. This leads to blinking highlightings, moving code in editor (because of appearing/disappearing lenses/hints), and features like Code Completion or Parameter Info (Signature Help) become significantly less precise, which is crucial when editing code.
There're many cases where it breaks deep inside expressions, like in the binary expression here:
```fsharp
let a =
1 +
let b = 1
```
**I propose we** rework how wrong indentation is handled. It's possible to change LexFilter, so it doesn't start a new indentation block with the wrong indentation, and to introduce new parser recovery rules, so it would show errors about missing expressions/definitions and continue parsing.
## Pros and Cons
**The advantages of making this adjustment to F# are**
Doing that will improve parser recovery, which in turn make the analysis significantly more stable and precise. It can improve the editing experience a lot, since there will be much less blinking, code jumping, and features that rely on inferred types will be much more stable.
**The disadvantages of making this adjustment to F# are**
This is a breaking change, and some code that can be compiled today will no longer compile. Such code produces warnings today and should be adjusted to have proper indentation. I expect such code to be extremely rare and mostly produced by generators (so hopefully only generators will need updates).
## Extra information
**Estimated cost (XS, S, M, L, XL, XXL):** M-L
I've made a small prototype with an additional parser rule for `let` bindings, and it shows that this recovery works as expected, with an additional error produced, see [this gist](https://gist.github.com/auduchinok/aa0ee96259b684e6983cf89dd8a014d5). The error texts can be improved separately. The main thing it shows is that the subsequent definitions are parsed correctly.
Since it's a breaking change, we should only introduce it in a new language version. It's possible to make it into F# 8 now, so the tooling will be able to use the improvements in the fall, when the new language version is released. If we don't ship it in F# 8, the closest date when improvements could be used would probably be ~November 2024, i.e. when .NET 9 is released.
## Affidavit (please submit!)
Please tick this by placing a cross in the box:
* [x] This is not a question (e.g. like one you might ask on [stackoverflow](http://stackoverflow.com)) and I have searched stackoverflow for discussions of this issue
* [x] I have [searched both open and closed suggestions on this site](http://github.com/fsharp/fslang-suggestions/issues) and believe this is not a duplicate
* [ ] This is not something which has obviously "already been decided" in previous versions of F#. If you're questioning a fundamental design decision that has obviously already been taken (e.g. "Make F# untyped") then please don't submit it.
Please tick all that apply:
* [ ] This is not a breaking change to the F# language design
* [x] I or my company would be willing to help implement and/or test this
## For Readers
If you would like to see this issue implemented, please click the :+1: emoji on this issue. These counts are used to generally order the suggestions by engagement.
Contributor guide
No contributing guide indexed for this repository
Research direction
Start with the LexFilter component and the prototype linked in the issue, then trace the parser recovery rules for indentation-sensitive constructs such as let bindings. Done means incorrectly indented tokens no longer create a new indentation block, later definitions remain correctly parsed and analyzed, and the behavior is covered for the new language version.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- fsharp
- Domain
- compilers
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Mostly clear
- Newbie friendliness
- 25/100