Repository navigation
Open
Open
Pass a typed compile request from the build system to the compiler, and validate user compiler flags#9303
Feature
Copy link
Description
Problem
The build system and the compiler talk through argv strings, even though the build system runs the compiler in the same process.
rewatch-ocaml/compiler_args.mlbuilds bsc's argv as a string list. In-process builds hand that list toRescript_compiler_driver.run_request, which parses it again with the flag table incommand_line_flags.- Nothing checks this contract at compile time. A misspelled or misordered flag only fails when a module is compiled.
- User flags are not checked at all.
Config_decode.compiler_flagssplits thecompiler-flagsentries ofrescript.jsonon spaces and passes them through. An unknown flag or a bad warning spec makes every module of the package fail with the same error, instead of one error pointing atrescript.json. - A user can pass flags that the build system sets itself (
-bs-ns,-o,-I,-bs-package-output, …). They silently conflict with the build system's own. - Values go through string quoting, which is fragile for Windows paths.
Proposal
Give a compile a typed record, with argv as its serialized form:
type compile_request = {
source: string;
namespace: string option;
alias_maps: string list;
include_dirs: string list;
package_name: string;
package_outputs: package_output list;
read_cmi: bool;
runtime_path: string;
project_root: string;
warnings: Warnings.spec;
open_modules: string list;
ppx: string list;
(* … one field per flag the build system or users set *)
}
val of_argv : string list -> (compile_request, error) result
val to_argv : compile_request -> string list
val apply : compile_request -> unit (* sets the request-local compiler state *)- One flag table.
command_line_flagsstays the only list of flags. Each entry updates a field of the record instead of a global ref. - In-process builds build the record and call
apply. Nothing is printed or parsed. - External bsc keeps its command line:
of_argv, thenapply. Both paths run the sameapply. rescript compiler-argsand--verboseprintto_argv, so a printed command still reproduces the compile.- User flags from
compiler-flagsare parsed withof_argvwhen the config is loaded. Unknown flags, bad values (warning specs go through the compiler's own parser) and flags reserved for the build system become config errors with the field's location. - File-level
@@config({flags: [...]})uses the sameof_argv, applied on top of the package's request for that file. - Parse requests (
parser_arguments) get the same treatment with their own smaller record.
Values that are text by nature, such as a ppx command or a module name to open, stay strings, but as typed fields, not free argv. bsc's debug-only flags can stay argv-only.
Steps
- Validate user flags at config load by running the existing flag parser on them in a fresh request state. Reject flags the build system owns. This is a small change that already gives located errors.
- Introduce the record, with
of_argv/to_argv/applyand a round-trip test,of_argv (to_argv r) = Ok r. - Pass the record in-process from
rewatch-ocaml, and generate argv only forcompiler-argsand--verbose.
Related
#9301 adds -bs-open-alias-map next to -bs-ns. With this change, both become fields (namespace, alias_maps) instead of flags whose effects overlap.
Metadata
Metadata
Assignees
Labels
No labels
Activity
cknitt commentedon Nov 14, 2026
Design for the typed compile request
Kinds of flags
Each entry of bsc's flag table (
command_line_flags) gets a kind. Where a flag may appear depends on its kind:compiler-flags@@config-I,-o,-bs-read-cmi,-bs-ns,-bs-package-name,-bs-package-output,-bs-project-root,-runtime-path,-bs-ast,-no-alias-deps,-absname,-color,-bs-gentype-*-bs-jsx,-bs-jsx-module,-bs-jsx-preserve,-bs-source-map*,-enable-experimental,-ppx,-bs-gentyperescript.jsonfield-open,-w,-warn-error,-bs-g,-bs-no-version-header,-bs-(no-)cross-module-opt,-bs-no-check-div-by-zero,-bs-noassertfalse,-noassert,-nostdlib,-nopervasives,-make-runtime,-bs-no-bin-annot,-(no-)keep-locs,-short-paths,-bs-no-builtin-ppx,-bs-cmi,-bs-cmj,-check-lam,-debug-ir-dparsetree,-dtypedtree,-drawlambda,-draw-coercions,-dsource,-bs-loc,-only-parse,-bs-syntax-only,-bs-cmi-only,-ignore-parse-errors,-editor-mode,-as-pp,-bs-test-ast-conversion,-verbose-e,-v,-version,-warn-help,-format,-reprint-source,-help@@configkeeps config-owned and debug flags because tests and projects use them per file, such as-bs-jsxand-bs-test-ast-conversion.The record
A module
Compile_requestholds every flag except the commands. Lists keep command-line order (-w,-open,-I); for a pair such as-bs-cross-module-optand-bs-no-cross-module-opt, the last one wins. Related flags are grouped:jsx,source_map,gentype,codegen,stdlib, anddebug(bools).t -> tort -> string -> t) instead of global state. Values are checked when parsed: jsx version, source-map mode, gentype module, and warning specs (through the warning parser on backed-up state).to_argvandof_argv.ClflagsandJs_configkeep their request-local fields;applyfills them.Who uses what
of_argvwith every kind allowed, then the command orapply. No behaviour change.compiler-flagsare parsed into it with the rules above.compiler-args,--verbose, compiler log andcompiler-info.json: printto_argv. The editor's JSON format is unchanged.@@config:of_argv ~base:currentwith the@@configrules, thenapply.Pull requests
compiler-flagsat build start (as it is now).of_argv/to_argv/applyand the round-trip test, with bsc's command line using them. No behaviour change.compiler-flagsparsed into the record (replaces Check compiler-flags when a build starts #9304's check, adds the warning), and the@@configrules.-bs-open-alias-mapbecomes thealias_mapsfield.