Join GitHub today
GitHub is home to over 40 million developers working together to host and review code, manage projects, and build software together.Sign up
Warn when undentation of operators causes ambiguity when following a match or if-else block #806
Warn for undentation ambiguities caused by operators
Note: this is undentation, or indentation-relaxation, not indentation.
I propose we raise a warning when undentation relaxation for operators will lead to ambiguities. This warning can be raised when the parser detects that after undenting an operator for the width of the operator, would make it belong to the previous block instead of the last statement. The warning could be:
The issue discussed here is notoriously hard to spot in a code base. I've been bitten by it numerous times over the cause of many years F# programming, and I am presently unsure whether my code silently has bugs caused by the indentation-relaxation rules for operators.
It is well established that F# is a whitespace-sensitive language, where generally related statements must start at the same indentation level.
For operators, there's an exception, where the parser will allow the operator to be undented until the rh-side of the operator is at the same line as the previous line. When the operator changes type, this is not a problem and wrongful use will lead to errors:
// this example works let f v = match v with | None ->  | Some i -> [1..i] |> Seq.sum // this example works let f v = match v with | None ->  | Some i -> [1..i] |> Seq.sum // this example errors: let f v = match v with | None ->  | Some i -> [1..i] |> Seq.sum // caused by `Seq.sum` starting on the same level as `[1..i]` // error: The type ''a list' does not match the type 'int'
In the examples above, it is clear, because the type inference will raise an exception (though the exception by itself will be hard to understand as it will be about the type, not the undentation error).
If we change this a little and create an operator for logging, it becomes must harder to detect:
// this example uses the default of 4 spaces for indentation let (<%>) x (s: string) = Console.WriteLine s; x let f v = match v with | None ->  | Some i -> [1..i] <%> "logging" // only hit when match is Some, never hits for None // it becomes even harder to spot in an if-else statement // operators like these are common in FParsec and other libraries let (<??>) x (s: string) = Console.WriteLine s; x let f v = if condition then  else [1..i] <%%> "Tested condition"
The problem is most apparent with operators that don't change type, because otherwise the chance of detection is much larger as it will likely raise a compile-time error.
The existing way of approaching this problem in F# is: to live with it and spot such errors visually (and as multiple reports have shown, this can be rather hard, even for seasoned developers).
Pros and Cons
The advantages are:
Estimated cost (XS, S, M, L, XL, XXL): XS (I really hope that's true)
Affidavit (please submit!)
Please tick this by placing a cross in the box:
Please tick all that apply:
If this is going to be accepted and someone can point me to the location where the indentation-relaxation of operators takes place, I'll gladly take a look to see if I can have that code raise a warning for ambiguous scenarios.
To answer that last line, I think we can do one of two things:
Not sure whether either of above options is easy or hard to detect in practice and whether corner cases exist that are not covered by such a simple rule.
(side note, @cartermp, I don't know how relevant these labels are, but I don't think my suggestion has to do with either type-checking or inference. I'm not trying to suggest a change in either. If anything, it is probably syntax related, or improving user experience, or the F# tokenizer/parser)
These undentations are bad in general surely. That's what the ambiguities show. Rather than warning only when bad undentations cause ambiguities, we should warn whenever they are made. That would be a lot clearer to users and also simpler for the compiler.
This code is currently explicitly allowed in the F# language spec:
let x = expr + expr let x = expr |> f expr
I don't see why though. Is this considered good style? The behaviour in this thread seems to refute this.
@charlesroddie I don't think anybody considers it good style. At best it's mentioned in some text books. But in practice, I see everyone align on the start of the operator.
The only exceptions to the rule are, however, any form of parens, brackets, curlies. Here, the first item starting with an opening paren, bracket or curly, is undented one character, so that the actual code inside the brackets aligns.
Most bracket constructs use one character, but there's also