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

feat(compiler)!: Remove arbitrary per-file compiler flags, add acceptable options as module attributes #1804

Merged
merged 6 commits into from Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 0 additions & 4 deletions cli/bin/grain.js
Expand Up @@ -108,10 +108,6 @@ class GrainCommand extends commander.Command {
cmd.forwardOption("--import-memory", "import the memory from `env.memory`");
cmd.option("--dir <dir...>", "directory to preopen");
cmd.option("--env <env...>", "WASI environment variables");
cmd.forwardOption(
"--compilation-mode <mode>",
"compilation mode (advanced use only)"
);
cmd.forwardOption(
"--elide-type-info",
"don't include runtime type information used by toString/print"
Expand Down
31 changes: 10 additions & 21 deletions compiler/src/compile.re
Expand Up @@ -88,25 +88,6 @@ let log_state = state =>
prerr_string("\n\n");
};

let apply_inline_flags = (prog: Parsetree.parsed_program) => {
switch (prog.comments) {
| [Block({cmt_content, cmt_loc}), ..._] =>
Grain_utils.Config.apply_inline_flags(
~on_error=
err => {
switch (err) {
| `Help =>
raise(InlineFlagsError(cmt_loc, Cannot_use_help_or_version))
| `Message(msg) =>
raise(InlineFlagsError(cmt_loc, Cannot_parse_inline_flags(msg)))
}
},
cmt_content,
)
| _ => ()
};
};

let next_state = (~is_root_file=false, {cstate_desc, cstate_filename} as cs) => {
let cstate_desc =
switch (cstate_desc) {
Expand Down Expand Up @@ -145,7 +126,15 @@ let next_state = (~is_root_file=false, {cstate_desc, cstate_filename} as cs) =>
cleanup();
Parsed(parsed);
| Parsed(p) =>
apply_inline_flags(p);
let has_attr = name =>
List.exists(
attr => attr.Asttypes.attr_name.txt == name,
p.attributes,
);
Grain_utils.Config.apply_attribute_flags(
~no_pervasives=has_attr("noPervasives"),
~runtime_mode=has_attr("runtimeMode"),
);
if (is_root_file) {
Grain_utils.Config.set_root_config();
};
Expand Down Expand Up @@ -266,7 +255,7 @@ let compile_wasi_polyfill = () => {
switch (Grain_utils.Config.wasi_polyfill^) {
| Some(file) =>
Grain_utils.Config.preserve_config(() => {
Grain_utils.Config.compilation_mode := Some("runtime");
Grain_utils.Config.compilation_mode := Grain_utils.Config.Runtime;
let cstate = {
cstate_desc: Initial(InputFile(file)),
cstate_filename: Some(file),
Expand Down
34 changes: 30 additions & 4 deletions compiler/src/formatting/fmt.re
Expand Up @@ -3992,10 +3992,36 @@ let print_program = (fmt, parsed_program) => {
};

group @@
fmt.print_comment_range(
fmt,
enclosing_start_location(parsed_program.prog_loc),
parsed_program.module_name.loc,
concat_map(
~lead=
first =>
fmt.print_comment_range(
fmt,
enclosing_start_location(parsed_program.prog_loc),
first.attr_loc,
),
~sep=
(prev, next) =>
fmt.print_comment_range(
fmt,
~none=hardline,
~lead=space,
~trail=hardline,
prev.Asttypes.attr_loc,
next.attr_loc,
),
~trail=
prev =>
fmt.print_comment_range(
fmt,
~none=hardline,
~lead=space,
~trail=hardline,
Copy link
Member

Choose a reason for hiding this comment

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

I'm pretty sure that the ~none and/or ~trail being hardline here are causing your extra newline in the attributes.

Copy link
Member Author

Choose a reason for hiding this comment

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

Doesn't look like that fixed it; I think it's the fact that print_comment_range inserts phatom_hardline ++ phantom_hardline since line_delta > 1 between the comment and module header

prev.Asttypes.attr_loc,
parsed_program.prog_core_loc,
),
~f=(~final, a) => fmt.print_attribute(fmt, a),
parsed_program.attributes,
)
++ string("module ")
++ string(parsed_program.module_name.txt)
Expand Down
19 changes: 10 additions & 9 deletions compiler/src/parsing/driver.re
Expand Up @@ -150,6 +150,11 @@ let parse = (~name=?, lexbuf, source): Parsetree.parsed_program => {
let read_imports = (program: Parsetree.parsed_program) => {
open Parsetree_iter;

let module_has_attr = name =>
List.exists(
attr => attr.Asttypes.attr_name.txt == name,
program.attributes,
);
let implicit_opens =
List.map(
o => {
Expand All @@ -159,15 +164,11 @@ let read_imports = (program: Parsetree.parsed_program) => {
| Grain_utils.Config.Gc_mod => Location.mknoloc("runtime/gc.gr")
}
},
switch (program.comments) {
| [Block({cmt_content}), ..._] =>
Grain_utils.Config.with_inline_flags(
~on_error=_ => (),
cmt_content,
Grain_utils.Config.get_implicit_opens,
)
| _ => Grain_utils.Config.get_implicit_opens()
},
Grain_utils.Config.with_attribute_flags(
~no_pervasives=module_has_attr("noPervasives"),
~runtime_mode=module_has_attr("runtimeMode"),
Grain_utils.Config.get_implicit_opens,
),
);
let found_includes = ref([]);

Expand Down
36 changes: 36 additions & 0 deletions compiler/src/parsing/parser.messages
Expand Up @@ -24,6 +24,42 @@ program: EOL YIELD
## In state 3, spurious reduction of production nonempty_list(eol) -> EOL
## In state 6, spurious reduction of production eols -> nonempty_list(eol)
##
program: AT LIDENT TYPE
##
## Ends in an error in state: 49.
##
## program -> option(attribute) . module_header eos toplevel_stmts EOF [ # ]
## program -> option(attribute) . module_header option(eos) EOF [ # ]
##
## The known suffix of the stack is as follows:
## option(attribute)
##
## WARNING: This example involves spurious reductions.
## This implies that, although the LR(1) items shown above provide an
## accurate view of the past (what has been recognized so far), they
## may provide an INCOMPLETE view of the future (what was expected next).
phated marked this conversation as resolved.
Show resolved Hide resolved
## In state 34, spurious reduction of production loption(attribute_arguments) ->
## In state 45, spurious reduction of production attribute -> AT id_str loption(attribute_arguments)
## In state 873, spurious reduction of production option(attribute) -> attribute
##
program: EOL AT LIDENT TYPE
##
## Ends in an error in state: 866.
##
## program -> eols option(attribute) . module_header eos toplevel_stmts EOF [ # ]
## program -> eols option(attribute) . module_header option(eos) EOF [ # ]
##
## The known suffix of the stack is as follows:
## eols option(attribute)
##
## WARNING: This example involves spurious reductions.
## This implies that, although the LR(1) items shown above provide an
## accurate view of the past (what has been recognized so far), they
## may provide an INCOMPLETE view of the future (what was expected next).
## In state 34, spurious reduction of production loption(attribute_arguments) ->
## In state 45, spurious reduction of production attribute -> AT id_str loption(attribute_arguments)
## In state 873, spurious reduction of production option(attribute) -> attribute
##

Expected a module header, e.g. `module Main`.

Expand Down
4 changes: 2 additions & 2 deletions compiler/src/parsing/parser.mly
Expand Up @@ -744,5 +744,5 @@ module_header:
| MODULE UIDENT { mkstr $loc($2) $2 }

program:
| opt_eols module_header eos toplevel_stmts EOF { make_program ~loc:(to_loc $sloc) $2 $4 }
| opt_eols module_header eos? EOF { make_program ~loc:(to_loc $sloc) $2 [] }
| opt_eols attributes module_header eos toplevel_stmts EOF { make_program ~loc:(to_loc $sloc) ~core_loc:(to_loc (fst $loc($3), snd $loc)) ~attributes:$2 $3 $5 }
| opt_eols attributes module_header eos? EOF { make_program ~loc:(to_loc $sloc) ~core_loc:(to_loc (fst $loc($3), snd $loc)) ~attributes:$2 $3 [] }
11 changes: 9 additions & 2 deletions compiler/src/parsing/parser_header.re
Expand Up @@ -89,13 +89,20 @@ let make_include_alias = ident => {
};
};

let make_program = (~loc, module_name, statements) => {
let make_program = (~loc, ~core_loc, ~attributes, module_name, statements) => {
// Ensure the program loc starts at the beginning of the file even if
// there's whitespace or comments
let loc_start = {...loc.loc_start, pos_lnum: 1, pos_cnum: 0, pos_bol: 0};
let prog_loc = {...loc, loc_start};

{module_name, statements, comments: [], prog_loc};
{
attributes,
module_name,
statements,
comments: [],
prog_loc,
prog_core_loc: core_loc,
};
};

let parse_program = (program, token, lexbuf) => {
Expand Down
5 changes: 4 additions & 1 deletion compiler/src/parsing/parsetree.re
Expand Up @@ -693,9 +693,12 @@ type comment =

[@deriving (sexp, yojson)]
type parsed_program = {
attributes,
module_name: loc(string),
statements: list(toplevel_stmt),
comments: list(comment),
[@sexp_drop_if sexp_locs_disabled]
prog_loc: Location.t,
prog_loc: Location.t, // The full location of the program
[@sexp_drop_if sexp_locs_disabled]
prog_core_loc: Location.t // The core location, without attributes
};
8 changes: 1 addition & 7 deletions compiler/src/parsing/parsetree_iter.re
Expand Up @@ -3,8 +3,6 @@ open Parsetree;
type hooks = {
enter_location: Location.t => unit,
leave_location: Location.t => unit,
enter_attribute: attribute => unit,
leave_attribute: attribute => unit,
Copy link
Member

Choose a reason for hiding this comment

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

Can you put these back please? I don't see a reason to remove them

enter_parsed_program: parsed_program => unit,
leave_parsed_program: parsed_program => unit,
enter_include: include_declaration => unit,
Expand Down Expand Up @@ -74,11 +72,9 @@ let iter_ident = (hooks, id) => {

let iter_attribute =
(hooks, {Asttypes.attr_name, attr_args, attr_loc} as attr) => {
hooks.enter_attribute(attr);
iter_loc(hooks, attr_name);
List.iter(iter_loc(hooks), attr_args);
iter_location(hooks, attr_loc);
hooks.leave_attribute(attr);
};

let iter_attributes = (hooks, attrs) => {
Expand All @@ -87,6 +83,7 @@ let iter_attributes = (hooks, attrs) => {

let rec iter_parsed_program = (hooks, {statements} as program) => {
hooks.enter_parsed_program(program);
iter_attributes(hooks, program.attributes);
Copy link
Member

Choose a reason for hiding this comment

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

What's going on here?

Copy link
Member Author

Choose a reason for hiding this comment

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

What do you mean? It's iterating over the attributes in the same way done with expressions and toplevels

iter_toplevel_stmts(hooks, statements);
hooks.leave_parsed_program(program);
}
Expand Down Expand Up @@ -524,9 +521,6 @@ let default_hooks = {
enter_location: _ => (),
leave_location: _ => (),

enter_attribute: _ => (),
leave_attribute: _ => (),

enter_parsed_program: _ => (),
leave_parsed_program: _ => (),

Expand Down
2 changes: 0 additions & 2 deletions compiler/src/parsing/parsetree_iter.rei
Expand Up @@ -3,8 +3,6 @@ open Parsetree;
type hooks = {
enter_location: Location.t => unit,
leave_location: Location.t => unit,
enter_attribute: attribute => unit,
leave_attribute: attribute => unit,
enter_parsed_program: parsed_program => unit,
leave_parsed_program: parsed_program => unit,
enter_include: include_declaration => unit,
Expand Down
65 changes: 35 additions & 30 deletions compiler/src/parsing/well_formedness.re
Expand Up @@ -11,7 +11,7 @@ type wferr =
| RHSLetRecMayOnlyBeFunction(Location.t)
| NoLetRecMut(Location.t)
| RationalZeroDenominator(Location.t)
| UnknownAttribute(string, Location.t)
| UnknownAttribute(string, string, Location.t)
| InvalidAttributeArity(string, int, Location.t)
| AttributeDisallowed(string, Location.t)
| LoopControlOutsideLoop(string, Location.t)
Expand Down Expand Up @@ -49,8 +49,8 @@ let prepare_error =
errorf(~loc, "let rec may not be used with the `mut` keyword.")
| RationalZeroDenominator(loc) =>
errorf(~loc, "Rational numbers may not have a denominator of zero.")
| UnknownAttribute(attr, loc) =>
errorf(~loc, "Unknown attribute `%s`.", attr)
| UnknownAttribute(attr_context, attr, loc) =>
errorf(~loc, "Unknown %s attribute `%s`.", attr_context, attr)
| InvalidAttributeArity(attr, arity, loc) =>
switch (arity) {
| 0 => errorf(~loc, "Attribute `%s` expects no arguments.", attr)
Expand Down Expand Up @@ -300,35 +300,28 @@ type known_attribute = {
arity: int,
};

let known_attributes = [
{name: "disableGC", arity: 0},
{name: "unsafe", arity: 0},
{name: "externalName", arity: 1},
];

let valid_attributes = (errs, super) => {
let enter_attribute =
({Asttypes.attr_name: {txt, loc}, attr_args: args} as attr) => {
switch (List.find_opt(({name}) => name == txt, known_attributes)) {
| Some({arity}) when List.length(args) != arity =>
errs := [InvalidAttributeArity(txt, arity, loc), ...errs^]
| None => errs := [UnknownAttribute(txt, loc), ...errs^]
| _ => ()
};
super.enter_attribute(attr);
let disallowed_attributes = (errs, super) => {
let validate_against_known = (attrs, known_attributes, context) => {
List.iter(
({Asttypes.attr_name: {txt, loc}, attr_args: args}) => {
switch (List.find_opt(({name}) => name == txt, known_attributes)) {
| Some({arity}) when List.length(args) != arity =>
errs := [InvalidAttributeArity(txt, arity, loc), ...errs^]
| None => errs := [UnknownAttribute(context, txt, loc), ...errs^]
| _ => ()
}
},
attrs,
);
};

{
errs,
iter_hooks: {
...super,
enter_attribute,
},
};
};
let known_expr_attributes = [
{name: "disableGC", arity: 0},
{name: "unsafe", arity: 0},
{name: "externalName", arity: 1},
];

let disallowed_attributes = (errs, super) => {
let enter_expression = ({pexp_desc: desc, pexp_attributes: attrs} as e) => {
let enter_expression = ({pexp_attributes: attrs} as e) => {
switch (
List.find_opt(
({Asttypes.attr_name: {txt}}) => txt == "externalName",
Expand All @@ -345,8 +338,10 @@ let disallowed_attributes = (errs, super) => {
]
| None => ()
};
validate_against_known(attrs, known_expr_attributes, "expression");
super.enter_expression(e);
};

let enter_toplevel_stmt =
({ptop_desc: desc, ptop_attributes: attrs} as top) => {
switch (
Expand Down Expand Up @@ -399,15 +394,26 @@ let disallowed_attributes = (errs, super) => {
}
| None => ()
};
validate_against_known(attrs, known_expr_attributes, "top-level");
super.enter_toplevel_stmt(top);
};

let enter_parsed_program = ({attributes} as prog) => {
let known_module_attributes = [
{name: "runtimeMode", arity: 0},
{name: "noPervasives", arity: 0},
];
validate_against_known(attributes, known_module_attributes, "module");
super.enter_parsed_program(prog);
};

{
errs,
iter_hooks: {
...super,
enter_expression,
enter_toplevel_stmt,
enter_parsed_program,
},
};
};
Expand Down Expand Up @@ -842,7 +848,6 @@ let well_formedness_checks = [
only_functions_oh_rhs_letrec,
no_letrec_mut,
no_zero_denominator_rational,
valid_attributes,
disallowed_attributes,
no_loop_control_statement_outside_of_loop,
malformed_return_statements,
Expand Down