Record field names are namespaced by module, so this is telling the compiler to access the lex_curr_p field of the lexbuf record value, as defined in the Lexing module. This tells the compiler where to find the record type definition and thus the correct type of the expression.
In “recent” versions of OCaml, we can use records with constructors, like this:
type alfa = Alfa of { field1: string; field2: int }
…which you normally reference by pattern matching…
let Alfa r = ... in r.field1 r.field2
…where the compiler prevents leaking the variable r from its scope because it doesn’t have a type that can be universally quantified.
Older versions of OCaml did not allow constructors for records, and this older form is used in your example, when the names of the fields in every record of a module are all in the module namespace. Hence, in the standard Lexing module all the fields in the lexbuf record type have names that begin with lex_ to disambiguate them from the fields in the other record types, e.g. the position type.
Accordingly, the way you read the expression buf.Lexing.lex_curr_p is that buf is a variable with the type of the record defined in the Lexing module with a field named lex_curr_p.
If this syntax feels weird and unwieldy, then you’re not alone. I try not to use it anymore, because the newer record types with constructors have fields with their own namespace, which feels to me a lot more like other programming languages. However, the standard Lexing module is from an earlier age, when we still had a Republic and the Jedi still policed the galaxy.
I actually find namespacing record fields by modules quite elegant and very consistent with the rest of the language, where almost everything else (including variant constructors) is namespaced by module.
I’m not sure when the module-qualified record field access was introduced, but I don’t think using or not using it has anything to do with the introduction of nested record types.
Since OCaml 4.01, you may also use type-based disambiguation if you prefer not to use qualified field names: the field lex_curr_p is available as soon as the value is known to be of type Lexing.lexbuf, and you may add a type annotation to ensure that.
let test (lexbuf : Lexing.lexbuf) =
let pos = lexbuf.lex_curr_p in
There are good replies here. I just want to add a note that it might help to think of this as a “path” – a way to uniquely specify a field, which can involve specifying the module where the record (and field) are defined.