Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Conversation

Maelan
Copy link
Contributor

@Maelan Maelan commented Nov 9, 2018

Until now, named parameters were highlighted in the following situations:

  • ?name accounts for these constructions:
    • simple optional λ-binders: fun ?name -> expr
    • optional parameter passing: f ?name, f ?name:expr
    • optional parameters in function types: ?name:typ -> typ
  • ?(name …) accounts for these:
    • optional λ-binders with typing annotations and/or default values: ?(name : typ), ?(name = expr), ?(name : typ = expr).
  • ~name accounts for these:
    • simple non-optional λ-binders: fun ~name -> expr
    • non-optional parameter passing: f ~name, f ~name:expr

This commit adds highlighting in the following situation:

  • ~(name …) accounts for these:
    • non-optional λ-binders with typing annotations: ?(name : typ).

A related wish

What is still missing is highlighting non-optional parameters in function types: name:typ -> typ. Highlighting these is desirable to me, particularly for .mli files, so I have been thinking about it. This is mostly impossible because the syntax has no recognizable ~, so we essentially need to match anything of the shape name :, which of course has countless conflicts (beware, below follow many lists with many bullets — I really want this):

  • named parameters: occurrences already mentioned above (not a problem)
  • records: { name : typ }, { r with name : typ } (I would tend to consider it an opportune side effect)
  • value declarations: val name : typ
  • typing annotations on bindings:
    • fun: fun name : typ -> expr, fun pat name : typ -> expr
    • let, external: similar
    • val (for objects): similar
    • method: similar
    • record fields: see above
    • maybe others I could not think of
  • typing annotations for expressions and patterns: (name : typ)
  • coercions: (name :> typ), (expr : name :> typ)
  • list constructions: name :: expr
  • assignation operators: name := expr

We can filter out many of these

  • by looking at the character just after : and rule out >, : and = (those cannot be the beginning of a type expression);
  • by looking at the last non-blank character before the name, and rule out anything alphanumeric (including keywords like with, let…);
  • by allowing no space between the name and :; this is not complete but relies on a sensible coding style;
  • by enabling the highlight rule only for .mli files?

Alternatively, we may enumerate which tokens of the grammar can precede the type expression name:typ -> typ:

  • :
  • :>
  • of (valid for polymorphic variants but not for ADTs, apparently)
  • & — as in [< `A of & name:int -> int & name:bool -> bool ] (idem)
  • =
  • . — as in 'a . typexpr
  • ->
  • (
  • , — as in ( typexpr , typexpr ) t and [ typexpr , typexpr ] classpath
  • [ — as in[ typexpr ] classpath
  • constraint typexpr

(

However, we need not consider the following contexts:

  • typexpr * typexpr (because of precedence between * and ->, this would be a syntax error)
  • [ typexpr | typexpr ] (this would cause a type error)

)

Of these tokens, we may restrict the match to the most common ones, ie. =, : and -> — I am unsure about (. But even with the first three, false positives remain:

(fun name -> name : int -> int)    (* uncommon pattern, I think *)
(expr = name : typ -> typ)    (* well, to make it typecheck, you have to do it on purpose *)
(* … probably others I could not think of right now … *)

To me, that’s good enough for realistic code.

In any case, we will never get rid of the ambiguity between the type expression ( name : int -> bool ) (where name is of type int) and the value expression ( name : int -> bool ) (where name is of type int->bool (!!!)).

type t   =  ( name : int -> bool ) ;;  (* here “name” is of type int *)
type t   =  { name : int -> bool } ;;  (* here “name” is of type int->bool *)
fun name -> ( name : int -> bool ) ;;  (* here “name” is of type int->bool *)

@copy copy merged commit 0692782 into ocaml:master Nov 11, 2018
@copy
Copy link
Collaborator

copy commented Nov 11, 2018

Thanks, nice analysis.

I like your idea for matching non-optional parameters in function types. A few comments:

by enabling the highlight rule only for .mli files?

That's probably a good starting point, and could be extended by matching only in regions for module signatures. A region for type expressions might also be possible.

All these changes in the already very complex syntax definition make me think if it could be possible to generate the syntax definition from OCaml's parser definition (parser.mly). vim's region matching seems quite powerful (with contains, ALLBUT, nextgroup, etc.), but I'm not familiar enough with parsers to know if it's possible or just a silly idea.

@Maelan
Copy link
Contributor Author

Maelan commented Nov 11, 2018

by enabling the highlight rule only for .mli files?

That's probably a good starting point, and could be extended by matching only in regions for module signatures.

Good idea. That should be straightforward with existing code.

A region for type expressions might also be possible.

I may be wrong, but I do not think it is possible without a complete rewriting of the linter (see below). One issue is that type expressions have no ending delimiter. They don’t really have a starting delimiter, either, as they can be introduced by : but also by :>, =, of… But now that I am thinking, maybe a fair approximation would be feasible, matching anything from a fixed set of starting delimiters, e·g·:

  • :
  • :>
  • type … =
  • of
  • constraint

to a fixed set of ending delimiters (with zero-width matching), e·g·:

  • val
  • let
  • external
  • module
  • class
  • constraint
  • method (?)
  • exception
  • in ( because of let exception E of typ in expr )
  • and
  • end
  • )
  • ;;
  • EOF

Some constructs would be part of the matchgroup which approximates type expressions, such as :> ( in (expr : typ :> typ) ) and | A of (for GADT definitions)… And some care would be needed to avoid matching module signatures introduced by :. Another issue is =.

constraint typ = typ       (* here `=` is followed by a type *)
type t = u = | A of int    (* here too *)
let x : typ = expr         (* but here `=` is followed by an expression *)
module M  :  MSIG with type t = typ  =  Mod  (* here `=` is followed by a module expression *)

A solution might be to distinguish a matchgroup ocamlType which doesn’t accept =, and a matchgroup ocamlTypeEq which accepts both of ocamlType and =. The latter would only be introduced by constraint, type … = and other constructs which accept a type equation.

Another troublesome case:

fun x : int -> x    (* here `->` ends the type expression! *)

It remains to see what amount of changes this would represent, which type expressions we would miss and, more importantly, which false positives we would get.

All these changes in the already very complex syntax definition make me think if it could be possible to generate the syntax definition from OCaml's parser definition (parser.mly). vim's region matching seems quite powerful (with contains, ALLBUT, nextgroup, etc.), but I'm not familiar enough with parsers to know if it's possible or just a silly idea.

That’s a good question. It may be worth asking to some Vim experts. I do not know what exactly is the expressing power of Vim’s linting engine. If powerful enough, my guess would be that the linter generated from parser.mly would be much bigger than the current one, and that writing it would be a massive and highly error-prone task for a human programmer (except if there is a menhir-to-vimscript translator out somewhere, which I doubt). And it might be much slower.

A huge benefit would be to tell apart expressions, patterns and types. I think that it is the only serious source of imprecision in the current linter.

@copy
Copy link
Collaborator

copy commented Nov 12, 2018

Leonidas on IRC pointed me to the tree-sitter project (OCaml implementation here), for which integration is being worked on here.

@Maelan
Copy link
Contributor Author

Maelan commented Nov 12, 2018

How impressive, that looks very nice indeed!

Still, we should keep improving the regex-based linter, as this integration work is specific to Neovim (for now?) (But I wouldn’t be shocked if at some point, the OCaml community favored Neovim over Vim.)

@copy
Copy link
Collaborator

copy commented Nov 12, 2018

Still, we should keep improving the regex-based linter, as this integration work is specific to Neovim (for now?) (But I wouldn’t be shocked if at some point, the OCaml community favored Neovim over Vim.)

Absolutely, and your work is very much appreciated.

@Maelan Maelan mentioned this pull request May 19, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants