Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix printing of bindings with polymorphic type annotations and attributes #9778

Merged

Conversation

mrmr1993
Copy link
Contributor

This fixes a bug that bit @mobileink recently, where printing the code
generated by ppx_deriving.eq and attempting to re-parse it resulted in a
parse error. This bug manifested where a let binding has a polymorphic
type 'a. _ and an attribute on the bound expression.

To reproduce:

  • create testfile with
 #load_rec "parsing/pprintast.cmo";;
 #load_rec "parsing/lexer.cmo";;
 #load_rec "parsing/parser.cmo";;

let phrase =
  {ocaml|
let rec equal :
          'a.    ('a -> 'a -> Ppx_deriving_runtime.bool) -> 'a t -> 'a t
          -> Ppx_deriving_runtime.bool =
  (let open! Ppx_deriving_runtime in
  fun poly_a (_ : unit) (_ : unit) -> true) [@ocaml.warning "-A"]
  [@@ocaml.warning "-39"]
|ocaml};;

let parse_roundtrip str =
  Pprintast.string_of_structure
    (Parser.implementation Lexer.token (Lexing.from_string str));;

let phrase_roundtrip = parse_roundtrip phrase;;

parse_roundtrip phrase_roundtrip;;
  • start the toplevel with TOPFLAGS="-I parsing -I utils" make runtop
  • run #use "testfile"

On trunk, this fails with an error at
parse_roundtrip phrase_roundtrip, where the parser chokes on the
let (equal : 'a. ...) = ... construction. With this change, the
printer and parser round-trip successfully.

This fixes a bug that bit @mobileink recently, where printing the code
generated by ppx_deriving.eq and attempting to re-parse it resulted in a
parse error. This bug manifested where a let binding has a polymorphic
type `'a. _` and an attribute on the bound expression.

To reproduce:
* create `testfile` with
```ocaml
 #load_rec "parsing/pprintast.cmo";;
 #load_rec "parsing/lexer.cmo";;
 #load_rec "parsing/parser.cmo";;

let phrase =
  {ocaml|
let rec equal :
          'a.    ('a -> 'a -> Ppx_deriving_runtime.bool) -> 'a t -> 'a t
          -> Ppx_deriving_runtime.bool =
  (let open! Ppx_deriving_runtime in
  fun poly_a (_ : unit) (_ : unit) -> true) [@ocaml.warning "-A"]
  [@@ocaml.warning "-39"]
|ocaml};;

let parse_roundtrip str =
  Pprintast.string_of_structure
    (Parser.implementation Lexer.token (Lexing.from_string str));;

let phrase_roundtrip = parse_roundtrip phrase;;

parse_roundtrip phrase_roundtrip;;
```
* start the toplevel with `TOPFLAGS="-I parsing -I utils" make runtop`
* run `#use "testfile"`

On `trunk`, this fails with an error at
`parse_roundtrip phrase_roundtrip`, where the parser chokes on the
`let (equal : 'a. ...) = ...` construction. With this change, the
printer and parser round-trip successfully.
@mrmr1993 mrmr1993 force-pushed the feature/pprintast_binding_expression_attributes branch from f65dbf6 to 39c4e15 Compare July 18, 2020 10:59
Copy link
Contributor

@nojb nojb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did a quick reading to the code, but couldn't figure out what was being fixed. Could you post the output of Pprintast "before" and "after" this change for your sample? It would help reviewing the PR.

Also, it may be a good idea adding a regression test with your sample (although the best thing would be to have a systematic test of Pprintast on a large set of files, but that is orthogonal to the present PR).

parsing/pprintast.ml Show resolved Hide resolved
@mrmr1993
Copy link
Contributor Author

Before:

let rec (equal :
          'a .
            ('a -> 'a -> Ppx_deriving_runtime.bool) ->
              'a t -> 'a t -> Ppx_deriving_runtime.bool)
  =
  ((let open! Ppx_deriving_runtime in
      fun poly_a -> fun (_ : unit) -> fun (_ : unit) -> true)
  [@ocaml.warning "-A"])[@@ocaml.warning "-39"]

After:

let rec equal
  : 'a .
      ('a -> 'a -> Ppx_deriving_runtime.bool) ->
        'a t -> 'a t -> Ppx_deriving_runtime.bool
  =
  ((let open! Ppx_deriving_runtime in
      fun poly_a -> fun (_ : unit) -> fun (_ : unit) -> true)
  [@ocaml.warning "-A"])[@@ocaml.warning "-39"]

The brackets in the trunk version cause it to be parsed as a pattern type annotation, but polymorphic types aren't allowed as pattern type annotations.

@nojb
Copy link
Contributor

nojb commented Jul 19, 2020

Thanks, it is clearer now. Sorry for the naive question, but do you know of a case where the parentheses removed by this patch are actually needed?

@mrmr1993
Copy link
Contributor Author

do you know of a case where the parentheses removed by this patch are actually needed?

They would be needed if there was an annotation that spans the name and the type signature, e.g.

let (x : int) [@hi] = 1

The Pprintast output for that AST has something of the lisp about it.

let (((x : int))[@hi ]) = 1

For bare patterns, I think only let (module M) = ... in ... and let (exception _) = ... in ... require parenthesis, and the exception case is rejected outright by the compiler.

@nojb
Copy link
Contributor

nojb commented Jul 19, 2020

See also #7200

Copy link
Contributor

@nojb nojb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The patch LGTM. The only thing missing is to add a test for it. This is easy: just add your sample fragment to the file testsuite/tests/parsetree/source.ml. The test testsuite/tests/parsetree/test.ml checks that this file can be re-parsed after being parsed and printed with Pprintast.

@mrmr1993
Copy link
Contributor Author

Done, thanks!

Copy link
Contributor

@nojb nojb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM (once CI passes).

Changes Outdated Show resolved Hide resolved
Changes Outdated Show resolved Hide resolved
@nojb nojb merged commit dd7ddf9 into ocaml:trunk Jul 20, 2020
@nojb
Copy link
Contributor

nojb commented Jul 20, 2020

Merged, thanks!

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