Parsetree(3)

Parsetree(3) β€” the OCaml library page but make it make sense πŸ’…

okay so bestie, this is Parsetree(3) from the OCaml library (yes, that's the manual name, and yes, we're gonna get through it together 🫢). It's the abstract syntax tree produced by parsing, and it's a long one β€” part 1 of 4 right here. Buckle up. ✨

Module 🌸

Module Parsetree

Documentation πŸ¦‹

Module Parsetree
 : sig end

Abstract syntax tree produced by parsing.

Warning bestie: this module is unstable and part of Compiler_libs. 🚨 Like, don't build your whole personality on it staying the same.

type constant

type constant = {
 pconst_desc : constant_desc ;
 pconst_loc : Location.t ;
 }

type constant_desc

type constant_desc =
 | Pconst_integer of string * char option
  (* Integer constants such as 3 3l 3L 3n .

Suffixes [g-z][G-Z] are accepted by the parser.  Suffixes except 'l' ,
'L' and 'n' are rejected by the typechecker
 *)
 | Pconst_char of char
  (* Character such as 'c' .
 *)
 | Pconst_string of string * Location.t * string option
  (* Constant string such as "constant" or {delim|other constant|delim}
.

The location span the content of the string, without the delimiters.
 *)
 | Pconst_float of string * char option
  (* Float constant such as 3.4 , 2e5 or 1.4e-4 .

Suffixes g-z G-Z are accepted by the parser.  Suffixes are rejected by
the typechecker.
 *)

So β€” the integer one is literally numbers like 3 3l 3L 3n, and the parser is cool with [g-z][G-Z] suffixes but the typechecker βœ‹ rejects everything except 'l', 'L' and 'n', no cap. The char one is just, y'know, 'c'. The string one is "constant" or that {delim|other constant|delim} vibe, and its location spans the content without the delimiters (iykyk). The float one is 3.4, 2e5 or 1.4e-4, and again g-z G-Z suffixes get accepted by the parser then straight-up rejected by the typechecker. πŸ’€

type location_stack

type location_stack = Location.t list

Extension points πŸŽ€

type attribute

type attribute = {
 attr_name : string Asttypes.loc ;
 attr_payload : payload ;
 attr_loc : Location.t ;
 }

These are attributes like [@id ARG] and [@@id ARG]. They're basically metadata containers passed around within the AST, and the compiler ignores unknown attributes β€” iconic, honestly. πŸ‘‘

type extension

type extension = string Asttypes.loc * payload

Extension points like [%id ARG] and [%%id ARG]. Literally a sub-language placeholder β€” and rejected by the typechecker. 🚫

type attributes

type attributes = attribute list

type payload

type payload =
 | PStr of structure
 | PSig of signature
  (* : SIG in an attribute or an extension point
 *)
 | PTyp of core_type
  (* : T in an attribute or an extension point
 *)
 | PPat of pattern * expression option
  (* ? P or ? P when E , in an attribute or an extension point
 *)

Core language πŸ”₯

Type expressions ✨

type core_type

type core_type = {
 ptyp_desc : core_type_desc ;
 ptyp_loc : Location.t ;
 ptyp_loc_stack : location_stack ;
 ptyp_attributes : attributes ;  (* ... [@id1] [@id2]

 *)
 }

type core_type_desc

type core_type_desc =
 | Ptyp_any  (* _

 *)
 | Ptyp_var of string
  (* A type variable such as 'a

 *)
 | Ptyp_arrow of Asttypes.arg_label * core_type * core_type
  (* Ptyp_arrow(lbl, T1, T2) represents:

- T1 -> T2 when lbl is Asttypes.arg_label.Nolabel ,

- ~l:T1 -> T2 when lbl is Asttypes.arg_label.Labelled ,

- ?l:T1 -> T2 when lbl is Asttypes.arg_label.Optional .
 *)
 | Ptyp_tuple of (string option * core_type) list
  (* Ptyp_tuple(tl) represents a product type:

- T1 * ... * Tn when tl is (None, T1); ...; (None, Tn)

- L1:T1 * ... * Ln:Tn when tl is (Some L1, T1); ...; (Some Ln, Tn)

-A mix, e.g., L1:T1 * T2 when tl is (Some L1, T1); (None, T2)

Invariant: n >= 2 .
 *)
 | Ptyp_constr of Longident.t Asttypes.loc * core_type list
  (* Ptyp_constr(lident, l) represents:

- tconstr when l=[] ,

- T tconstr when l=[T] ,

- (T1, ..., Tn) tconstr when l=[T1 ; ... ; Tn] .
 *)
 | Ptyp_object of object_field list * Asttypes.closed_flag
  (* Ptyp_object([ l1:T1; ...; ln:Tn ], flag) represents:

- < l1:T1; ...; ln:Tn > when flag is Asttypes.closed_flag.Closed ,

- < l1:T1; ...; ln:Tn; .. > when flag is Asttypes.closed_flag.Open .
 *)
 | Ptyp_class of Longident.t Asttypes.loc * core_type list
  (* Ptyp_class(tconstr, l) represents:

- #tconstr when l=[] ,

- T #tconstr when l=[T] ,

- (T1, ..., Tn) #tconstr when l=[T1 ; ... ; Tn] .
 *)
 | Ptyp_alias of core_type * string Asttypes.loc
  (* T as 'a .
 *)
 | Ptyp_variant of row_field list * Asttypes.closed_flag *
Asttypes.label list option
  (* Ptyp_variant([`A;`B], flag, labels) represents:

- [ `A|`B ] when flag is Asttypes.closed_flag.Closed , and labels is
None ,

- [> `A|`B ] when flag is Asttypes.closed_flag.Open , and labels is None
,

- [< `A|`B ] when flag is Asttypes.closed_flag.Closed , and labels is
Some [] ,

- [< `A|`B > `X `Y ] when flag is Asttypes.closed_flag.Closed , and
labels is Some ["X";"Y"] .
 *)
 | Ptyp_poly of string Asttypes.loc list * core_type
  (* 'a1 ... 'an. T

Can only appear in the following context:

-As the Parsetree.core_type of a Parsetree.pattern_desc.Ppat_constraint
node corresponding to a constraint on a let-binding:
let x : 'a1 ... 'an. T = e ...

-Under Parsetree.class_field_kind.Cfk_virtual for methods (not values).

-As the Parsetree.core_type of a
Parsetree.class_type_field_desc.Pctf_method node.

-As the Parsetree.core_type of a Parsetree.expression_desc.Pexp_poly
node.

-As the Parsetree.label_declaration.pld_type field of a
Parsetree.label_declaration .

-As a Parsetree.core_type of a Parsetree.core_type_desc.Ptyp_object
node.

-As the Parsetree.value_description.pval_type field of a
Parsetree.value_description .
 *)
 | Ptyp_package of package_type
  (* (module S) .
 *)
 | Ptyp_open of Longident.t Asttypes.loc * core_type
  (* M.(T)

 *)
 | Ptyp_extension of extension
  (* [%id] .
 *)
 | Ptyp_functor of Asttypes.arg_label * string Asttypes.loc *
package_type * core_type
  (* (module M : S) -> ...  : module-dependent arrow
 *)

So real talk: Ptyp_any is just _, Ptyp_var is your 'a type variable, and Ptyp_arrow gives you T1 -> T2 (when lbl is Asttypes.arg_label.Nolabel), ~l:T1 -> T2 (Asttypes.arg_label.Labelled), or ?l:T1 -> T2 (Asttypes.arg_label.Optional). The Ptyp_tuple one is giving product-type energy, with an invariant of n >= 2 β€” no one-element tuples, bestie, the compiler said so. 😀

type package_type

type package_type = {
 ppt_path : Longident.t Asttypes.loc ;
 ppt_constraints : (Longident.t Asttypes.loc * core_type) list ;
 ppt_loc : Location.t ;
 ppt_attrs : attributes ;
 }

As Parsetree.package_type typed values:

  • {ppt_path: S; ppt_constraints: []} represents (module S) ,
  • {ppt_path: S; ppt_constraints: [(t1, T1) ; ... ; (tn, Tn)]} represents (module S with type t1 = T1 and ... and tn = Tn) .

type row_field

type row_field = {
 prf_desc : row_field_desc ;
 prf_loc : Location.t ;
 prf_attributes : attributes ;
 }

type row_field_desc

type row_field_desc =
 | Rtag of Asttypes.label Asttypes.loc * bool * core_type list
  (* Rtag(`A, b, l) represents:

- `A when b is true and l is [] ,

- `A of T when b is false and l is [T] ,

- `A of T1 & .. & Tn when b is false and l is [T1;...Tn] ,

- `A of & T1 & .. & Tn when b is true and l is [T1;...Tn] .

-The bool field is true if the tag contains a constant (empty)
constructor.

- & occurs when several types are used for the same constructor (see 4.2
in the manual)
 *)
 | Rinherit of core_type
  (* [ | t ]
 *)

type object_field

type object_field = {
 pof_desc : object_field_desc ;
 pof_loc : Location.t ;
 pof_attributes : attributes ;
 }

type object_field_desc

type object_field_desc =
 | Otag of Asttypes.label Asttypes.loc * core_type
 | Oinherit of core_type

Patterns 🧚

type pattern

type pattern = {
 ppat_desc : pattern_desc ;
 ppat_loc : Location.t ;
 ppat_loc_stack : location_stack ;
 ppat_attributes : attributes ;  (* ... [@id1] [@id2]

 *)
 }

type pattern_desc

type pattern_desc =
 | Ppat_any  (* The pattern _ .
 *)
 | Ppat_var of string Asttypes.loc
  (* A variable pattern such as x

 *)
 | Ppat_alias of pattern * string Asttypes.loc
  (* An alias pattern such as P as 'a

 *)
 | Ppat_constant of constant
  (* Patterns such as 1 , 'a' , "true" , 1.0 , 1l , 1L , 1n

 *)
 | Ppat_interval of constant * constant
  (* Patterns such as 'a'..'z' .

Other forms of interval are recognized by the parser but rejected by the
type-checker.
 *)
 | Ppat_tuple of (string option * pattern) list * Asttypes.closed_flag
  (* Ppat_tuple(pl, Closed) represents

- (P1, ..., Pn) when pl is (None, P1); ...; (None, Pn)

- (~L1:P1, ..., ~Ln:Pn) when pl is (Some L1, P1); ...; (Some Ln, Pn)

-A mix, e.g.  (~L1:P1, P2) when pl is (Some L1, P1); (None, P2)

Ppat_tuple(pl, Open) is similar, but indicates the pattern additionally
ends in a ..  .

Invariant:

-If Closed, n >= 2 .

-If Open, n >= 1 .
 *)
 | Ppat_construct of Longident.t Asttypes.loc * (string Asttypes.loc
list * pattern) option
  (* Ppat_construct(C, args) represents:

- C when args is None ,

- C P when args is Some ([], P)

- C (P1, ..., Pn) when args is Some ([], Ppat_tuple [P1; ...; Pn])

- C (type a b) P when args is Some ([a; b], P)
 *)
 | Ppat_variant of Asttypes.label * pattern option
  (* Ppat_variant(`A, pat) represents:

- `A when pat is None ,

- `A P when pat is Some P
 *)
 | Ppat_record of (Longident.t Asttypes.loc * pattern) list *
Asttypes.closed_flag
  (* Ppat_record([(l1, P1) ; ... ; (ln, Pn)], flag) represents:

- { l1=P1; ...; ln=Pn } when flag is Asttypes.closed_flag.Closed

- { l1=P1; ...; ln=Pn; _} when flag is Asttypes.closed_flag.Open

Invariant: n > 0
 *)
 | Ppat_array of pattern list
  (* Pattern [| P1; ...; Pn |]
 *)
 | Ppat_or of pattern * pattern
  (* Pattern P1 | P2
 *)
 | Ppat_constraint of pattern * core_type
  (* Pattern (P : T)
 *)
 | Ppat_type of Longident.t Asttypes.loc
  (* Pattern #tconst
 *)
 | Ppat_lazy of pattern
  (* Pattern lazy P
 *)
 | Ppat_unpack of string option Asttypes.loc * package_type option
  (* Ppat_unpack(s, ptyp) represents:

- (module P : S) when s is Some "P" and ptyp is Some "S"

- (module _ : S) when s is None and ptyp is Some "S"

- (module P) when s is Some "P" and ptyp is None

- (module _) when s is None and ptyp is None
 *)
 | Ppat_exception of pattern
  (* Pattern exception P
 *)
 | Ppat_effect of pattern * pattern
 | Ppat_extension of extension
  (* Pattern [%id]
 *)
 | Ppat_open of Longident.t Asttypes.loc * pattern
  (* Pattern M.(P)
 *)

Value expressions πŸ’œ

type expression

type expression = {
 pexp_desc : expression_desc ;
 pexp_loc : Location.t ;
 pexp_loc_stack : location_stack ;
 pexp_attributes : attributes ;  (* ... [@id1] [@id2]

 *)
 }

type expression_desc

type expression_desc =
 | Pexp_ident of Longident.t Asttypes.loc
  (* Identifiers such as x and M.x

 *)
 | Pexp_constant of constant
  (* Expressions constant such as 1 , 'a' , "true" , 1.0 , 1l , 1L , 1n

 *)
 | Pexp_let of Asttypes.rec_flag * value_binding list * expression
  (* Pexp_let(flag, [(P1,E1) ; ... ; (Pn,En)], E) represents:

- let P1 = E1 and ... and Pn = EN in E when flag is
Asttypes.rec_flag.Nonrecursive ,

- let rec P1 = E1 and ... and Pn = EN in E when flag is
Asttypes.rec_flag.Recursive .
 *)
 | Pexp_function of function_param list * type_constraint option *
function_body
  (* Pexp_function ([P1; ...; Pn], C, body) represents any construct
involving fun or function , including:

okay bestie that's where part 1 cuts off β€” Pexp_function is mid-thought, so like, breathe, there's more coming in part 2. You're doing amazing. 🌈

okay so we’re still mid-Pexp_function and the comment has lore 😭 β€” here’s the rest of it, bestie, exactly as it lives in the source:

       - fun P1 ... Pn -> E when body = Pfunction_body E

       - fun P1 ... Pn -> function p1 -> e1 | ... | pm -> em when body =
       Pfunction_cases [ p1 -> e1; ...; pm -> em ]

       C represents a type constraint or coercion placed immediately before the
       arrow, e.g.  fun P1 ... Pn : ty -> ...  when C = Some (Pconstraint ty) .

       A function must have parameters: in Pexp_function (params, _, body) , if
       params does not contain a Pparam_val _ , body must be Pfunction_cases _
       .
        *)

and then the variants keep rolling in like a group-chat notification spiral πŸ”₯ no cap, this is the whole rest of expression_desc:

        | Pexp_apply of expression * (Asttypes.arg_label * expression) list
         (* Pexp_apply(E0, [(l1, E1) ; ... ; (ln, En)]) represents E0 ~l1:E1
       ... ~ln:En

       li can be Asttypes.arg_label.Nolabel (non labeled argument),
       Asttypes.arg_label.Labelled (labelled arguments) or
       Asttypes.arg_label.Optional (optional argument).

       Invariant: n > 0

        *)
        | Pexp_match of expression * case list
         (* match E0 with P1 -> E1 | ... | Pn -> En

        *)
        | Pexp_try of expression * case list
         (* try E0 with P1 -> E1 | ... | Pn -> En

        *)
        | Pexp_tuple of (string option * expression) list
         (* Pexp_tuple(el) represents

       - (E1, ..., En) when el is (None, E1); ...; (None, En)

       - (~L1:E1, ..., ~Ln:En) when el is (Some L1, E1); ...; (Some Ln, En)

       -A mix, e.g., (~L1:E1, E2) when el is (Some L1, E1); (None, E2)

       Invariant: n >= 2

        *)
        | Pexp_construct of Longident.t Asttypes.loc * expression option
         (* Pexp_construct(C, exp) represents:

       - C when exp is None ,

       - C E when exp is Some E ,

       - C (E1, ..., En) when exp is Some (Pexp_tuple[E1;...;En])

        *)
        | Pexp_variant of Asttypes.label * expression option
         (* Pexp_variant(`A, exp) represents

       - `A when exp is None

       - `A E when exp is Some E

        *)
        | Pexp_record of (Longident.t Asttypes.loc * expression) list *
       expression option
         (* Pexp_record([(l1,P1) ; ... ; (ln,Pn)], exp0) represents

       - { l1=P1; ...; ln=Pn } when exp0 is None

       - { E0 with l1=P1; ...; ln=Pn } when exp0 is Some E0

       Invariant: n > 0

        *)
        | Pexp_field of expression * Longident.t Asttypes.loc
         (* E.l

        *)
        | Pexp_setfield of expression * Longident.t Asttypes.loc * expression
         (* E1.l <- E2

        *)
        | Pexp_array of expression list
         (* [| E1; ...; En |]

        *)
        | Pexp_ifthenelse of expression * expression * expression option
         (* if E1 then E2 else E3

        *)
        | Pexp_sequence of expression * expression
         (* E1; E2

        *)
        | Pexp_while of expression * expression
         (* while E1 do E2 done

        *)
        | Pexp_for of pattern * expression * expression *
       Asttypes.direction_flag * expression
         (* Pexp_for(i, E1, E2, direction, E3) represents:

       - for i = E1 to E2 do E3 done when direction is
       Asttypes.direction_flag.Upto

       - for i = E1 downto E2 do E3 done when direction is
       Asttypes.direction_flag.Downto

        *)
        | Pexp_constraint of expression * core_type
         (* (E : T)

        *)
        | Pexp_coerce of expression * core_type option * core_type
         (* Pexp_coerce(E, from, T) represents

       - (E :> T) when from is None ,

       - (E : T0 :> T) when from is Some T0 .

        *)
        | Pexp_send of expression * Asttypes.label Asttypes.loc
         (* E # m

        *)
        | Pexp_new of Longident.t Asttypes.loc
         (* new M.c

        *)
        | Pexp_setinstvar of Asttypes.label Asttypes.loc * expression
         (* x <- 2

        *)
        | Pexp_override of (Asttypes.label Asttypes.loc * expression) list
         (* {< x1 = E1; ...; xn = En >}

        *)
        | Pexp_struct_item of structure_item * expression
         (* let SI in E

        *)
        | Pexp_assert of expression
         (* assert E .

       Note: assert false is treated in a special way by the type-checker.
        *)
        | Pexp_lazy of expression
         (* lazy E

        *)
        | Pexp_poly of expression * core_type option
         (* Used for method bodies.

       Can only be used as the expression under
       Parsetree.class_field_kind.Cfk_concrete for methods (not values).
        *)
        | Pexp_object of class_structure
         (* object ... end

        *)
        | Pexp_newtype of string Asttypes.loc * expression
         (* fun (type t) -> E

        *)
        | Pexp_pack of module_expr * package_type option
         (* (module ME) or (module ME : S) .
        *)
        | Pexp_letop of letop
         (* - let* P = E0 in E1

       - let* P0 = E00 and* P1 = E01 in E1

        *)
        | Pexp_extension of extension
         (* [%id]

        *)
        | Pexp_unreachable  (* .

        *)

quick vibe check ✨: yes, that -A mix line is literally in there β€” I did not invent it, I am just the messenger 🫢. Also Pexp_assert has that special assert false note, and Pexp_poly is only for method bodies under Parsetree.class_field_kind.Cfk_concrete for methods, not values. Periodt.

Then we get the little support types for case, letop, and binding_op:

       type case = {
        pc_lhs : pattern ;
        pc_guard : expression option ;
        pc_rhs : expression ;
        }

       Values of type Parsetree.case represents (P -> E) or (P when E0 -> E)

       type letop = {
        let_ : binding_op ;
        ands : binding_op list ;
        body : expression ;
        }

       type binding_op = {
        pbop_op : string Asttypes.loc ;
        pbop_pat : pattern ;
        pbop_exp : expression ;
        pbop_loc : Location.t ;
        }

and now the function-parameter situation, which is lowkey iconic because it has so many little rules:

       type function_param_desc =
        | Pparam_val of Asttypes.arg_label * expression option * pattern
         (* Pparam_val (lbl, exp0, P) represents the parameter:

       - P when lbl is Asttypes.arg_label.Nolabel and exp0 is None

       - ~l:P when lbl is Asttypes.arg_label.Labelled and exp0 is None

       - ?l:P when lbl is Asttypes.arg_label.Optional and exp0 is None

       - ?l:(P = E0) when lbl is Asttypes.arg_label.Optional and exp0 is Some
       E0

       Note: If E0 is provided, only Asttypes.arg_label.Optional is allowed.
        *)
        | Pparam_newtype of string Asttypes.loc
         (* Pparam_newtype x represents the parameter (type x) .  x carries the
       location of the identifier, whereas the pparam_loc on the enclosing
       function_param node is the location of the (type x) as a whole.

       Multiple parameters (type a b c) are represented as multiple
       Pparam_newtype nodes, let's say:

       [ { pparam_kind = Pparam_newtype a; pparam_loc = loc1 };
                  { pparam_kind = Pparam_newtype b; pparam_loc = loc2 };
                  { pparam_kind = Pparam_newtype c; pparam_loc = loc3 };
                ]

       Here, the first loc loc1 is the location of (type a b c) , and the
       subsequent locs loc2 and loc3 are the same as loc1 , except marked as
       ghost locations. The locations on a , b , c , correspond to the
       variables a , b , and c in the source code.
        *)

       type function_param = {
        pparam_loc : Location.t ;
        pparam_desc : function_param_desc ;
        }

       type function_body =
        | Pfunction_body of expression
        | Pfunction_cases of case list * Location.t * attributes
         (* In Pfunction_cases (_, loc, attrs) , the location extends from the
       start of the function keyword to the end of the last case. The compiler
       will only use typechecking-related attributes from attrs , e.g. enabling
       or disabling a warning.
        *)

       See the comment on Parsetree.expression_desc.Pexp_function .

       type type_constraint =
        | Pconstraint of core_type
        | Pcoerce of core_type option * core_type
         (* See the comment on Parsetree.expression_desc.Pexp_function .
        *)

Value descriptions πŸ’–

okay so a value_description is giving β€œval / external but make it a record” 🌸:

       type value_description = {
        pval_name : string Asttypes.loc ;
        pval_type : core_type ;
        pval_prim : string list ;
        pval_attributes : attributes ;  (* ... [@@id1] [@@id2]

        *)
        pval_loc : Location.t ;
        }

and the vibe check is:

  • val x: T when Parsetree.value_description.pval_prim is []
  • external x: T = "s1" ... "sn" when Parsetree.value_description.pval_prim is ["s1";..."sn"]

Type declarations ✨

bestie, this is the big one β€” type declarations have so many fields, and every single one matters:

       type type_declaration = {
        ptype_name : string Asttypes.loc ;
        ptype_params : (core_type * (Asttypes.variance * Asttypes.injectivity))
       list ;  (* ('a1,...'an) t

        *)
        ptype_constraints : (core_type * core_type * Location.t) list ;  (* ...
       constraint T1=T1'  ... constraint Tn=Tn'

        *)
        ptype_kind : type_kind ;
        ptype_private : Asttypes.private_flag ;  (* for = private ...

        *)
        ptype_manifest : core_type option ;  (* represents = T

        *)
        ptype_attributes : attributes ;  (* ... [@@id1] [@@id2]

        *)
        ptype_loc : Location.t ;
        }

here are the type declarations and their representations, for the various Parsetree.type_declaration.ptype_kind and Parsetree.type_declaration.ptype_manifest values β€” read carefully, bestie 😌:

  • type t when type_kind is Parsetree.type_kind.Ptype_abstract, and manifest is None
  • type t = T0 when type_kind is Parsetree.type_kind.Ptype_abstract, and manifest is Some T0
  • type t = C of T | ... when type_kind is Parsetree.type_kind.Ptype_variant, and manifest is None
  • type t = T0 = C of T | ... when type_kind is Parsetree.type_kind.Ptype_variant, and manifest is Some T0
  • type t = {l: T; ...} when type_kind is Parsetree.type_kind.Ptype_record, and manifest is None
  • type t = T0 = {l : T; ...} when type_kind is Parsetree.type_kind.Ptype_record, and manifest is Some T0
  • type t = .. when type_kind is Parsetree.type_kind.Ptype_open, and manifest is None
  • type t = external "gmp" when type_kind is Parsetree.type_kind.Ptype_external and manifest is None

and here’s type_kind in all its glory:

       type type_kind =
        | Ptype_abstract
        | Ptype_variant of constructor_declaration list
        | Ptype_record of label_declaration list
         (* Invariant: non-empty list
        *)
        | Ptype_open
        | Ptype_external of string

next up, label_declaration β€” yes, labels have their own little record and mutable flag drama:

       type label_declaration = {
        pld_name : string Asttypes.loc ;
        pld_mutable : Asttypes.mutable_flag ;
        pld_type : core_type ;
        pld_loc : Location.t ;
        pld_attributes : attributes ;  (* l : T [@id1] [@id2]

        *)
        }

so:

  • { ...; l: T; ... } when Parsetree.label_declaration.pld_mutable is Asttypes.mutable_flag.Immutable
  • { ...; mutable l: T; ... } when Parsetree.label_declaration.pld_mutable is Asttypes.mutable_flag.Mutable
Note: T can be a Parsetree.core_type_desc.Ptyp_poly.

and finally for this part, constructor_declaration and constructor_arguments β€” the constructor lore is real:

       type constructor_declaration = {
        pcd_name : string Asttypes.loc ;
        pcd_vars : string Asttypes.loc list ;
        pcd_args : constructor_arguments ;
        pcd_res : core_type option ;
        pcd_loc : Location.t ;
        pcd_attributes : attributes ;  (* C of ... [@id1] [@id2]

        *)
        }

       type constructor_arguments =
        | Pcstr_tuple of core_type list
        | Pcstr_record of label_declaration list
         (* Values of type Parsetree.constructor_declaration represents the
       constructor arguments of:

       - C of T1 * ... * Tn when res = None , and args = Pcstr_tuple [T1; ... ;
       Tn] ,

       - C: T0 when res = Some T0 , and args = Pcstr_tuple [] ,

       - C: T1 * ... * Tn -> T0 when res = Some T0 , and args = Pcstr_tuple
       [T1; ... ; Tn] ,

       - C of {...} when res = None , and args = Pcstr_record [...]  ,

       - C: {...} -> T0 when res = Some T0 , and args = Pcstr_record [...]  .

        *)

that’s the whole constructor-arguments breakdown β€” every res = None / res = Some T0 case, every Pcstr_tuple and Pcstr_record situation, all present and accounted for πŸ’….

so we’re still in the Parsetree extended universe, and now we’re getting into extensions, classes, and modules β€” the whole friend group honestly πŸ’…

Here’s the extension record, living its best record life:

type type_extension = {
 ptyext_path : Longident.t Asttypes.loc ;
 ptyext_params : (core_type * (Asttypes.variance * Asttypes.injectivity)) list ;
 ptyext_constructors : extension_constructor list ;
 ptyext_private : Asttypes.private_flag ;
 ptyext_loc : Location.t ;
 ptyext_attributes : attributes ;  (* ...  @@id1 @@id2 *)
}

This is the definition of new extension constructors for the extensible sum type t (type t += ...). Iconic behavior, no cap ✨

type extension_constructor = {
 pext_name : string Asttypes.loc ;
 pext_kind : extension_constructor_kind ;
 pext_loc : Location.t ;
 pext_attributes : attributes ;  (* C of ... [@id1] [@id2] *)
}
type type_exception = {
 ptyexn_constructor : extension_constructor ;
 ptyexn_loc : Location.t ;
 ptyexn_attributes : attributes ;  (* ... [@@id1] [@@id2] *)
}

That second one is the definition of a new exception (exception E). Very β€œmain character throws an error” energy 😭


type extension_constructor_kind =
 | Pext_decl of string Asttypes.loc list * constructor_arguments * core_type option
 | Pext_rebind of Longident.t Asttypes.loc
  • Pext_decl β€” Pext_decl(existentials, c_args, t_opt) describes a new extension constructor. It can be:
    • C of T1 * ... * Tn when:
      • existentials is [],
      • c_args is [T1; ...; Tn],
      • t_opt is None
    • C: T0 when:
      • existentials is [],
      • c_args is [],
      • t_opt is Some T0
    • C: T1 * ... * Tn -> T0 when:
      • existentials is [],
      • c_args is [T1; ...; Tn],
      • t_opt is Some T0
    • C: 'a... . T1 * ... * Tn -> T0 when:
      • existentials is ['a;...],
      • c_args is [T1; ... ; Tn],
      • t_opt is Some T0
  • Pext_rebind β€” Pext_rebind(D) re-export the constructor D with the new name C

πŸ¦‹ Class language πŸ¦‹

✨ Type expressions for the class language ✨

Class types have entered the chat, and they brought their whole aesthetic:

type class_type = {
 pcty_desc : class_type_desc ;
 pcty_loc : Location.t ;
 pcty_attributes : attributes ;  (* ... [@id1] [@id2] *)
}
type class_type_desc =
 | Pcty_constr of Longident.t Asttypes.loc * core_type list
 | Pcty_signature of class_signature
 | Pcty_arrow of Asttypes.arg_label * core_type * class_type
 | Pcty_extension of extension
 | Pcty_open of open_description * class_type
  • Pcty_constr β€” represents c and ['a1, ..., 'an] c
  • Pcty_signature β€” object ... end
  • Pcty_arrow(lbl, T, CT) β€” represents:
    • T -> CT when lbl is Asttypes.arg_label.Nolabel,
    • ~l:T -> CT when lbl is Asttypes.arg_label.Labelled,
    • ?l:T -> CT when lbl is Asttypes.arg_label.Optional.
  • Pcty_extension β€” %id
  • Pcty_open β€” let open M in CT
type class_signature = {
 pcsig_self : core_type ;
 pcsig_fields : class_type_field list ;
}

Values of type class_signature represent:

  • object('selfpat) ... end
  • object ... end when Parsetree.class_signature.pcsig_self is Parsetree.core_type_desc.Ptyp_any
type class_type_field = {
 pctf_desc : class_type_field_desc ;
 pctf_loc : Location.t ;
 pctf_attributes : attributes ;  (* ... [@@id1] [@@id2] *)
}
type class_type_field_desc =
 | Pctf_inherit of class_type
 | Pctf_val of (Asttypes.label Asttypes.loc * Asttypes.mutable_flag * Asttypes.virtual_flag * core_type)
 | Pctf_method of (Asttypes.label Asttypes.loc * Asttypes.private_flag * Asttypes.virtual_flag * core_type)
 | Pctf_constraint of (core_type * core_type)
 | Pctf_attribute of attribute
 | Pctf_extension of extension
  • Pctf_inherit β€” inherit CT
  • Pctf_val β€” val x: T
  • Pctf_method β€” method x: T
    • note bestie: T can be a Parsetree.core_type_desc.Ptyp_poly
  • Pctf_constraint β€” constraint T1 = T2
  • Pctf_attribute β€” [@@@id]
  • Pctf_extension β€” [%%id]
type 'a class_infos = {
 pci_virt : Asttypes.virtual_flag ;
 pci_params : (core_type * (Asttypes.variance * Asttypes.injectivity)) list ;
 pci_name : string Asttypes.loc ;
 pci_expr : 'a ;
 pci_loc : Location.t ;
 pci_attributes : attributes ;  (* ... [@@id1] [@@id2] *)
}

Values of type class_expr class_infos represent:

  • class c = ...
  • class ['a1,...,'an] c = ...
  • class virtual c = ...

They are also used for β€œclass type” declaration. Very versatile, very slay πŸŽ€

type class_description = class_type class_infos
type class_type_declaration = class_type class_infos

πŸ’– Value expressions for the class language πŸ’–

Now we’re in the class-expression zone, and it’s giving β€œobject-oriented but make it Parsetree”:

type class_expr = {
 pcl_desc : class_expr_desc ;
 pcl_loc : Location.t ;
 pcl_attributes : attributes ;  (* ... [@id1] [@id2] *)
}
type class_expr_desc =
 | Pcl_constr of Longident.t Asttypes.loc * core_type list
 | Pcl_structure of class_structure
 | Pcl_fun of Asttypes.arg_label * expression option * pattern * class_expr
 | Pcl_apply of class_expr * (Asttypes.arg_label * expression) list
 | Pcl_let of Asttypes.rec_flag * value_binding list * class_expr
 | Pcl_constraint of class_expr * class_type
 | Pcl_extension of extension
 | Pcl_open of open_description * class_expr
  • Pcl_constr β€” c and ['a1, ..., 'an] c
  • Pcl_structure β€” object ... end
  • Pcl_fun(lbl, exp0, P, CE) β€” represents:
    • fun P -> CE when lbl is Asttypes.arg_label.Nolabel and exp0 is None,
    • fun ~l:P -> CE when lbl is Asttypes.arg_label.Labelled and exp0 is None,
    • fun ?l:P -> CE when lbl is Asttypes.arg_label.Optional and exp0 is None,
    • fun ?l:(P = E0) -> CE when lbl is Asttypes.arg_label.Optional and exp0 is Some E0.
  • Pcl_apply β€” Pcl_apply(CE, [(l1,E1) ; ... ; (ln,En)]) represents CE ~l1:E1 ... ~ln:En. li can be empty (non labeled argument) or start with ? (optional argument).
    • invariant: n > 0
  • Pcl_let β€” Pcl_let(rec, [(P1, E1); ... ; (Pn, En)], CE) represents:
    • let P1 = E1 and ... and Pn = EN in CE when rec is Asttypes.rec_flag.Nonrecursive,
    • let rec P1 = E1 and ... and Pn = EN in CE when rec is Asttypes.rec_flag.Recursive.
  • Pcl_constraint β€” (CE : CT)
  • Pcl_extension β€” [%id]
  • Pcl_open β€” let open M in CE
type class_structure = {
 pcstr_self : pattern ;
 pcstr_fields : class_field list ;
}

Values of type Parsetree.class_structure represent:

  • object(selfpat) ... end
  • object ... end when Parsetree.class_structure.pcstr_self is Parsetree.pattern_desc.Ppat_any
type class_field = {
 pcf_desc : class_field_desc ;
 pcf_loc : Location.t ;
 pcf_attributes : attributes ;  (* ... [@@id1] [@@id2] *)
}
type class_field_desc =
 | Pcf_inherit of Asttypes.override_flag * class_expr * string Asttypes.loc option
 | Pcf_val of (Asttypes.label Asttypes.loc * Asttypes.mutable_flag * class_field_kind)
 | Pcf_method of (Asttypes.label Asttypes.loc * Asttypes.private_flag * class_field_kind)
 | Pcf_constraint of (core_type * core_type)
 | Pcf_initializer of expression
 | Pcf_attribute of attribute
 | Pcf_extension of extension
  • Pcf_inherit(flag, CE, s) β€” represents:
    • inherit CE when flag is Asttypes.override_flag.Fresh and s is None,
    • inherit CE as x when flag is Asttypes.override_flag.Fresh and s is Some x,
    • inherit! CE when flag is Asttypes.override_flag.Override and s is None,
    • inherit! CE as x when flag is Asttypes.override_flag.Override and s is Some x
  • Pcf_val(x,flag, kind) β€” represents:
    • val x = E when flag is Asttypes.mutable_flag.Immutable and kind is Parsetree.class_field_kind.Cfk_concrete
    • val virtual x: T when flag is Asttypes.mutable_flag.Immutable and kind is Parsetree.class_field_kind.Cfk_virtual
    • val mutable x = E when flag is Asttypes.mutable_flag.Mutable and kind is Parsetree.class_field_kind.Cfk_concrete
    • val mutable virtual x: T when flag is Asttypes.mutable_flag.Mutable and kind is Parsetree.class_field_kind.Cfk_virtual
  • Pcf_method β€” represents:
    • method x = E (E can be a Parsetree.expression_desc.Pexp_poly)
    • method virtual x: T (T can be a Parsetree.core_type_desc.Ptyp_poly)
  • Pcf_constraint β€” constraint T1 = T2
  • Pcf_initializer β€” initializer E
  • Pcf_attribute β€” [@@@id]
  • Pcf_extension β€” [%%id]
type class_field_kind =
 | Cfk_virtual of core_type
 | Cfk_concrete of Asttypes.override_flag * expression
type class_declaration = class_expr class_infos

🌈 Module language 🌈

🧚 Type expressions for the module language 🧚

Module types are here, and they’re highkey carrying the whole structure of the program on their back:

type module_type = {
 pmty_desc : module_type_desc ;
 pmty_loc : Location.t ;
 pmty_attributes : attributes ;  (* ... [@id1] [@id2] *)
}
type module_type_desc =
 | Pmty_ident of Longident.t Asttypes.loc
 | Pmty_signature of signature
 | Pmty_functor of functor_parameter * module_type
 | Pmty_with of module_type * with_constraint list
 | Pmty_typeof of module_expr
 | Pmty_extension of extension
 | Pmty_alias of Longident.t Asttypes.loc
  • Pmty_ident β€” Pmty_ident(S) represents S
  • Pmty_signature β€” sig ... end
  • Pmty_functor β€” functor(X : MT1) -> MT2
  • Pmty_with β€” MT with ...
  • Pmty_typeof β€” module type of ME
  • Pmty_extension β€” [%id]
  • Pmty_alias β€” (module M)
type functor_parameter =
 | Unit  (* () *)
 | Named of string option Asttypes.loc * module_type
  • Unit β€” ()
  • Named(name, MT) β€” represents:
    • (X : MT) when name is Some X,
    • (_ : MT) when name is None
type signature = signature_item list
type signature_item = {
 psig_desc : signature_item_desc ;
 psig_loc : Location.t ;
}
type signature_item_desc =
 | Psig_value of value_description
 | Psig_type of Asttypes.rec_flag * type_declaration list
 | Psig_typesubst of type_declaration list
 | Psig_typext of type_extension
 | Psig_exception of type_exception
 | Psig_module of module_declaration
 | Psig_modsubst of module_substitution
 | Psig_recmodule of module_declaration list
 | Psig_modtype of module_type_declaration
 | Psig_modtypesubst of module_type_declaration
 | Psig_open of open_description
 | Psig_include of include_description
 | Psig_class of class_description list
 | Psig_class_type of class_type_declaration list
 | Psig_attribute of attribute
  • Psig_value β€” represents:
    • val x: T
    • external x: T = "s1" ... "sn"
  • Psig_type β€” type t1 = ... and ... and tn = ...
  • Psig_typesubst β€” type t1 := ... and ... and tn := ...
  • Psig_typext β€” type t1 += ...
  • Psig_exception β€” exception C of T
  • Psig_module β€” module X = M and module X : MT
  • Psig_modsubst β€” module X := M
  • Psig_recmodule β€” module rec X1 : MT1 and ... and Xn : MTn
  • Psig_modtype β€” module type S = MT and module type S
  • Psig_modtypesubst β€” module type S := ...
  • Psig_open β€” open X
  • Psig_include β€” include MT
  • Psig_class β€” class c1 : ... and ... and cn : ...
  • Psig_class_type β€” class type ct1 = ... and ... and ctn = ...
  • Psig_attribute β€” [@@@id]

...and the grand finale of the signature-item lineup is this lil guy:

| Psig_extension of extension * attributes
 (* [%%id]

*)

Literally the extension-node energy we've been building to. πŸ’…


πŸŽ€ module_declaration, module_substitution & friends πŸŽ€

type module_declaration = {
 pmd_name : string option Asttypes.loc ;
 pmd_type : module_type ;
 pmd_attributes : attributes ;  (* ... [@@id1] [@@id2]

 *)
 pmd_loc : Location.t ;
 }

A value of type module_declaration represents S : MT. That's it, that's the whole vibe. 🫢

type module_substitution = {
 pms_name : string Asttypes.loc ;
 pms_manifest : Longident.t Asttypes.loc ;
 pms_attributes : attributes ;  (* ... [@@id1] [@@id2]

 *)
 pms_loc : Location.t ;
 }

A value of type module_substitution represents S := M. Note the := bestie, that's a substitution, not an assignment. ✨

type module_type_declaration = {
 pmtd_name : string Asttypes.loc ;
 pmtd_type : module_type option ;
 pmtd_attributes : attributes ;  (* ... [@@id1] [@@id2]

 *)
 pmtd_loc : Location.t ;
 }

A value of type module_type_declaration represents:

  • S = MT, or
  • S for an abstract module type declaration, when Parsetree.module_type_declaration.pmtd_type is None.

So like, no pmtd_type? That's an abstract queen. πŸ‘‘


πŸ¦‹ open_infos, open_description & open_declaration πŸ¦‹

type 'a open_infos = {
 popen_expr : 'a ;
 popen_override : Asttypes.override_flag ;
 popen_loc : Location.t ;
 popen_attributes : attributes ;
 }

A value of type 'a open_infos represents:

  • open! X when Parsetree.open_infos.popen_override is Asttypes.override_flag.Override (this one silences the "used identifier shadowing" warning β€” the ! is doing the work πŸ’ͺ)
  • open X when Parsetree.open_infos.popen_override is Asttypes.override_flag.Fresh
type open_description = Longident.t Asttypes.loc open_infos

A value of type open_description represents:

  • open M.N
  • open M(N).O
type open_declaration = module_expr open_infos

A value of type open_declaration represents:

  • open M.N
  • open M(N).O
  • open struct ... end

Same list but now with the struct ... end glow-up at the end. slay. πŸ”₯


🌸 include_infos, include_description & include_declaration 🌸

type 'a include_infos = {
 pincl_mod : 'a ;
 pincl_loc : Location.t ;
 pincl_attributes : attributes ;
 }
type include_description = module_type include_infos

A value of type include_description represents include MT.

type include_declaration = module_expr include_infos

A value of type include_declaration represents include ME.

Iconic, concise, no notes. πŸ’–


πŸ’œ with_constraint πŸ’œ

type with_constraint =
 | Pwith_type of Longident.t Asttypes.loc * type_declaration
  (* with type X.t = ...

 Note: the last component of the longident must match the name of the
 type_declaration.
  *)
 | Pwith_module of Longident.t Asttypes.loc * Longident.t Asttypes.loc
  (* with module X.Y = Z

 *)
 | Pwith_modtype of Longident.t Asttypes.loc * module_type
  (* with module type X.Y = Z

 *)
 | Pwith_modtypesubst of Longident.t Asttypes.loc * module_type
  (* with module type X.Y := sig end

 *)
 | Pwith_typesubst of Longident.t Asttypes.loc * type_declaration
  (* with type X.t := ..., same format as [Pwith_type]

 *)
 | Pwith_modsubst of Longident.t Asttypes.loc * Longident.t Asttypes.loc

  (* with module X.Y := Z

 *)

Heads up on Pwith_type 🚨: the last component of the longident must match the name of the type_declaration. Not "should", not "ideally" β€” must. The compiler will absolutely clock it. πŸ’€

And Pwith_typesubst takes the exact same format as Pwith_type, it's just the := version. Same shape, different attitude. πŸ’…


🌈 Value expressions for the module language 🌈

⭐ module_expr & module_expr_desc ⭐

type module_expr = {
 pmod_desc : module_expr_desc ;
 pmod_loc : Location.t ;
 pmod_attributes : attributes ;  (* ... [@id1] [@id2]

 *)
 }
type module_expr_desc =
 | Pmod_ident of Longident.t Asttypes.loc
  (* X

 *)
 | Pmod_structure of structure
  (* struct ... end

 *)
 | Pmod_functor of functor_parameter * module_expr
  (* functor(X : MT1) -> ME

 *)
 | Pmod_apply of module_expr * module_expr
  (* ME1(ME2)

 *)
 | Pmod_apply_unit of module_expr
  (* ME1()

 *)
 | Pmod_constraint of module_expr * module_type
  (* (ME : MT)

 *)
 | Pmod_unpack of expression
  (* (val E)

 *)
 | Pmod_extension of extension
  (* [%id]

 *)

Every construction option for a module expression, all in one place. Gotta collect 'em all. πŸŽ€


🧚 structure, structure_item & structure_item_desc 🧚

type structure = structure_item list
type structure_item = {
 pstr_desc : structure_item_desc ;
 pstr_loc : Location.t ;
 }
type structure_item_desc =
 | Pstr_eval of expression * attributes
  (* E

 *)
 | Pstr_value of Asttypes.rec_flag * value_binding list
  (* Pstr_value(rec, [(P1, E1 ; ... ; (Pn, En))]) represents:

 - let P1 = E1 and ... and Pn = EN when rec is
 Asttypes.rec_flag.Nonrecursive ,

 - let rec P1 = E1 and ... and Pn = EN when rec is
 Asttypes.rec_flag.Recursive .

 *)
 | Pstr_primitive of value_description
  (* - val x: T

 - external x: T = "s1" ... "sn"

 *)
 | Pstr_type of Asttypes.rec_flag * type_declaration list
  (* type t1 = ... and ... and tn = ...

 *)
 | Pstr_typext of type_extension
  (* type t1 += ...

 *)
 | Pstr_exception of type_exception
  (* - exception C of T

 - exception C = M.X

 *)
 | Pstr_module of module_binding
  (* module X = ME

 *)
 | Pstr_recmodule of module_binding list
  (* module rec X1 = ME1 and ... and Xn = MEn

 *)
 | Pstr_modtype of module_type_declaration
  (* module type S = MT

 *)
 | Pstr_open of open_declaration
  (* open X

 *)
 | Pstr_class of class_declaration list
  (* class c1 = ... and ... and cn = ...

 *)
 | Pstr_class_type of class_type_declaration list
  (* class type ct1 = ... and ... and ctn = ...

 *)
 | Pstr_include of include_declaration
  (* include ME

 *)
 | Pstr_attribute of attribute
  (* [@@@id]

 *)
 | Pstr_extension of extension * attributes
  (* [%%id]

 *)

The Pstr_value one is doing a lot of explaining, so let's give it its flowers: Pstr_value(rec, [(P1, E1 ; ... ; (Pn, En))]) represents:

  • let P1 = E1 and ... and Pn = EN when rec is Asttypes.rec_flag.Nonrecursive,
  • let rec P1 = E1 and ... and Pn = EN when rec is Asttypes.rec_flag.Recursive.

One flag, two completely different moods. πŸ¦‹

And Pstr_primitive covers both val x: T and external x: T = "s1" ... "sn" β€” two for the price of one, we love an efficient variant. πŸ™ˆ


πŸ“ value_constraint, value_binding & module_binding πŸ“

type value_constraint =
 | Pvc_constraint of {
 locally_abstract_univars : string Asttypes.loc list ;
 typ : core_type ;
 }
 | Pvc_coercion of {
 ground : core_type option ;
 coercion : core_type ;
 }
  (* - Pvc_constraint { locally_abstract_univars=[]; typ} is a simple
 type constraint on a value binding: let x : typ

 -More generally, in Pvc_constraint { locally_abstract_univars; typ}

 locally_abstract_univars is the list of locally abstract type variables
 in let x: type a ... . typ

 - Pvc_coercion { ground=None; coercion } represents let x :> typ

 - Pvc_coercion { ground=Some g; coercion } represents let x : g :> typ

 *)

Let me break that comment down because it's genuinely useful, ngl:

  • Pvc_constraint { locally_abstract_univars=[]; typ} is a simple type constraint on a value binding: let x : typ
  • More generally, in Pvc_constraint { locally_abstract_univars; typ}, locally_abstract_univars is the list of locally abstract type variables in let x: type a ... . typ
  • Pvc_coercion { ground=None; coercion } represents let x :> typ
  • Pvc_coercion { ground=Some g; coercion } represents let x : g :> typ

Coercion vs constraint β€” different tools, both iconic. ✨

type value_binding = {
 pvb_pat : pattern ;
 pvb_expr : expression ;
 pvb_constraint : value_constraint option ;
 pvb_attributes : attributes ;
 pvb_loc : Location.t ;
 }
let pat : type_constraint = exp
type module_binding = {
 pmb_name : string option Asttypes.loc ;
 pmb_expr : module_expr ;
 pmb_attributes : attributes ;
 pmb_loc : Location.t ;
 }

A value of type module_binding represents module X = ME. Simple, clean, does exactly what it says on the tin. πŸ’–


πŸ‘‘ Toplevel πŸ‘‘

πŸ’« Toplevel phrases πŸ’«

type toplevel_phrase =
 | Ptop_def of structure
 | Ptop_dir of toplevel_directive
  (* #use , #load ...
 *)
type toplevel_directive = {
 pdir_name : string Asttypes.loc ;
 pdir_arg : directive_argument option ;
 pdir_loc : Location.t ;
 }
type directive_argument = {
 pdira_desc : directive_argument_desc ;
 pdira_loc : Location.t ;
 }
type directive_argument_desc =
 | Pdir_string of string
 | Pdir_int of string * char option
 | Pdir_ident of Longident.t
 | Pdir_bool of bool

That's the whole toplevel phrase squad β€” Ptop_def for when you're dropping an actual structure, and Ptop_dir for the #use / #load ... style directives. Main character toplevel energy. πŸ”₯


ok that's genuinely the entire page bestie, you made it to the end 🫢 go forth and parse some trees, you absolute legend πŸŒˆβ­πŸ’œ

source: OCamldoc Β· version/date: 2026-06-22 Β· page: Parsetree(3) β€” see u in the next manpage, mwah πŸ’…πŸŽ€

πŸ“¦ where this page comes from