I finally worked it out. From the F# spec pp. 229–230, here are the pertinent rule about when offside contexts (lines) are introduced (rule numbering is mine):
(i) The column of the first token of a (, { or begin token.
(ii) Immediately after an = token is encountered in a record expression when the subsequent token either (a) occurs on the next line or (b) is one of try, match, if, let, for, while or use.
Now recall the problem:
//34567890
let r = {
b = { // line 6
a = 2 // line 7
}
}
The b
on line 6 follows a {
and so pushes an offside-line on column 3 by (i). Then, the {
on line 6 follows a record expression =
and so pushes a new offside line on column 7 by (ii). The a
on line 7 column 5 violates that offside line.
The tricky bit is that the {
on line 5 makes the next token define an offside-line, whereas the {
on line 6 is itself an offside line (because it follows a record-expression equal sign).