Skip to content

Formatter prints module(await (await M): S), which the parser rejects #8819

Closed

Description

@cristianoc

Found by adversarial experiments on semantic analysts.

What is broken

Formatting a typed first-class module pack whose operand is a nested or attributed await produces source the parser rejects.

The printer keeps the general form (module(...): module(S)) only when the packed module is a direct Pmod_constraint:
https://github.com/rescript-lang/rescript/blob/8d51b9ce7/compiler/syntax/src/res_printer.ml#L3690-L3706

Every other packed module goes through the shorthand module(<mod>: S):
https://github.com/rescript-lang/rescript/blob/8d51b9ce7/compiler/syntax/src/res_printer.ml#L3707-L3728

Pmod_await puts its operand in parentheses when the operand is another await, is attributed, or is a constraint:
https://github.com/rescript-lang/rescript/blob/8d51b9ce7/compiler/syntax/src/res_printer.ml#L6095-L6115

After await, a parenthesised module followed by : is read as a functor:
https://github.com/rescript-lang/rescript/blob/8d51b9ce7/compiler/syntax/src/res_core.ml#L441-L465
https://github.com/rescript-lang/rescript/blob/8d51b9ce7/compiler/syntax/src/res_core.ml#L6650-L6661

So module(await (await M): S) parses as await applied to a functor (await M): S => ..., and the parse fails.

Reproduction

pack.res:

module type S = {}
module M = {}
let x = (module(await (await M)): module(S))
./_build/default/compiler/bsc/rescript_compiler_main.exe -nopervasives pack.res   # exit 0, emits `let x = M;`
./_build/default/compiler/syntax/cli/res_cli.exe pack.res > out.res
cat out.res | tail -1
./_build/default/compiler/syntax/cli/res_cli.exe out.res

Observed:

let x = module(await (await M): S)

  Syntax error!
  out.res:3:23-27
  Did you forget a `)` here?

Expected: output that parses, for example let x = (module(await (await M)): module(S)).

The same happens for an attributed operand: let x = (module(await (@a M)): module(S)) formats to let x = module(await (@a M): S), which also fails to parse.

The only typed-pack fixture covers a direct constraint:
https://github.com/rescript-lang/rescript/blob/8d51b9ce7/tests/syntax_tests/data/printer/modExpr/parens.res#L6

Introduced by

23a4197 (#8738) for nested await, and ac96983 (#8735) for attributed operands. Before those changes the formatter dropped the inner await or attribute instead, so its output still parsed.

Impact

Formatting a file that compiles turns it into one that no longer parses. The next compile, or the next format, fails.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions