diff --git a/cli/bin/grain.js b/cli/bin/grain.js index 6b7683d14..ca3a211cf 100755 --- a/cli/bin/grain.js +++ b/cli/bin/grain.js @@ -153,6 +153,12 @@ class GrainCommand extends commander.Command { "--verbose", "print critical information at various stages of compilation" ); + cmd.forwardOption( + "--ignore-warnings ", + "compiler warnings to ignore", + list, + [] + ); return cmd; } } diff --git a/compiler/src/utils/config.re b/compiler/src/utils/config.re index d8a70df04..ad2650145 100644 --- a/compiler/src/utils/config.re +++ b/compiler/src/utils/config.re @@ -398,6 +398,61 @@ let import_memory = false, ); +type ignore_warning = + | IgnoreAll + | LetRecNonFunction + | AmbiguousName + | StatementType + | NonreturningStatement + | AllClausesGuarded + | PartialMatch + | UnusedMatch + | UnusedPat + | NonClosedRecordPattern + | UnreachableCase + | ShadowConstructor + | NoCmiFile + | FuncWasmUnsafe + | FromNumberLiteral + | UselessRecordSpread + | PrintUnsafe + | ToStringUnsafe + | ArrayIndexNonInteger; + +let ignore_warnings = + opt( + ~names=["ignore-warnings"], + ~conv= + Cmdliner.Arg.( + list( + enum([ + ("all", IgnoreAll), + ("letRecNonFunction", LetRecNonFunction), + ("ambiguousName", AmbiguousName), + ("statementType", StatementType), + ("nonreturningStatement", NonreturningStatement), + ("allClausesGuarded", AllClausesGuarded), + ("partialMatch", PartialMatch), + ("unusedMatch", UnusedMatch), + ("unusedPat", UnusedPat), + ("nonClosedRecordPattern", NonClosedRecordPattern), + ("unreachableCase", UnreachableCase), + ("shadowConstructor", ShadowConstructor), + ("noCmiFile", NoCmiFile), + ("funcWasmUnsafe", FuncWasmUnsafe), + ("fromNumberLiteral", FromNumberLiteral), + ("uselessRecordSpread", UselessRecordSpread), + ("printUnsafe", PrintUnsafe), + ("toStringUnsafe", ToStringUnsafe), + ("arrayIndexNonInteger", ArrayIndexNonInteger), + ]), + ) + ), + ~doc="Compiler warnings to ignore", + ~digestible=NotDigestible, + [], + ); + type compilation_mode = | Normal /* Standard compilation with regular bells and whistles */ | Runtime /* GC doesn't exist yet, allocations happen in runtime heap */; diff --git a/compiler/src/utils/config.rei b/compiler/src/utils/config.rei index bab3999d1..6f247b05a 100644 --- a/compiler/src/utils/config.rei +++ b/compiler/src/utils/config.rei @@ -5,6 +5,27 @@ type compilation_mode = | Normal /* Standard compilation with regular bells and whistles */ | Runtime /* GC doesn't exist yet, allocations happen in runtime heap */; +type ignore_warning = + | IgnoreAll + | LetRecNonFunction + | AmbiguousName + | StatementType + | NonreturningStatement + | AllClausesGuarded + | PartialMatch + | UnusedMatch + | UnusedPat + | NonClosedRecordPattern + | UnreachableCase + | ShadowConstructor + | NoCmiFile + | FuncWasmUnsafe + | FromNumberLiteral + | UselessRecordSpread + | PrintUnsafe + | ToStringUnsafe + | ArrayIndexNonInteger; + /** The Grain stdlib directory, based on the current configuration */ let stdlib_directory: unit => option(string); @@ -82,6 +103,10 @@ let maximum_memory_pages: ref(option(int)); let import_memory: ref(bool); +/** Compiler warnings to ignore */ + +let ignore_warnings: ref(list(ignore_warning)); + /** Whether this module should be compiled in runtime mode */ let compilation_mode: ref(compilation_mode); diff --git a/compiler/src/utils/warnings.re b/compiler/src/utils/warnings.re index fd513fddf..d250a6a8d 100644 --- a/compiler/src/utils/warnings.re +++ b/compiler/src/utils/warnings.re @@ -167,9 +167,9 @@ let message = } | UselessRecordSpread => "this record spread is useless as all of the record's fields are overridden." | PrintUnsafe(typ) => - "it looks like you are using `print` on an unsafe Wasm value here.\nThis is generally unsafe and will cause errors. Use `DebugPrint.print`" + "it looks like you are using `print` on an unsafe Wasm value here.\nThis is generally unsafe and will cause errors. Use `DebugPrint.print" ++ typ - ++ " from the `runtime/debugPrint` module instead." + ++ "` from the `runtime/debugPrint` module instead." | ToStringUnsafe(typ) => "it looks like you are using `toString` on an unsafe Wasm value here.\nThis is generally unsafe and will cause errors. Use `DebugPrint.toString`" ++ typ @@ -196,7 +196,40 @@ let backup = () => current^; let restore = x => current := x; -let is_active = x => current^.active[number(x)]; +let ignore_warning = warning => { + let config_warning = + switch (warning) { + | LetRecNonFunction(_) => Some(Config.LetRecNonFunction) + | AmbiguousName(_) => Some(Config.AmbiguousName) + | StatementType => Some(Config.StatementType) + | NonreturningStatement => Some(Config.NonreturningStatement) + | AllClausesGuarded => Some(Config.AllClausesGuarded) + | PartialMatch(_) => Some(Config.PartialMatch) + | UnusedMatch => Some(Config.UnusedMatch) + | UnusedPat => Some(Config.UnusedPat) + | NonClosedRecordPattern(_) => Some(Config.NonClosedRecordPattern) + | UnreachableCase => Some(Config.UnreachableCase) + | ShadowConstructor(_) => Some(Config.ShadowConstructor) + | NoCmiFile(_) => Some(Config.NoCmiFile) + | FuncWasmUnsafe(_) => Some(Config.FuncWasmUnsafe) + | FromNumberLiteral(_) => Some(Config.FromNumberLiteral) + | UselessRecordSpread => Some(Config.UselessRecordSpread) + | PrintUnsafe(_) => Some(Config.PrintUnsafe) + | ToStringUnsafe(_) => Some(Config.ToStringUnsafe) + | ArrayIndexNonInteger(_) => Some(Config.ArrayIndexNonInteger) + // TODO(#681): Look into reenabling these + | NotPrincipal(_) + | NameOutOfScope(_) + | FragileMatch(_) + | UnusedExtension => None + }; + + List.mem(Config.IgnoreAll, Config.ignore_warnings^) + || Option.map(x => List.mem(x, Config.ignore_warnings^), config_warning) + |> Option.value(~default=false); +}; + +let is_active = x => current^.active[number(x)] && !ignore_warning(x); let is_error = x => current^.error[number(x)]; let nerrors = ref(0); diff --git a/compiler/test/runner.re b/compiler/test/runner.re index b1b64cff2..00666b265 100644 --- a/compiler/test/runner.re +++ b/compiler/test/runner.re @@ -269,21 +269,22 @@ let makeCompileErrorRunner = }; let makeWarningRunner = - (test, ~module_header=module_header, name, prog, warning) => { + (test, ~config_fn=?, ~module_header=module_header, name, prog, warning) => { test(name, ({expect}) => { Config.preserve_all_configs(() => { Config.print_warnings := false; - ignore @@ compile(name, module_header ++ prog); + ignore @@ compile(name, ~config_fn?, module_header ++ prog); expect.ext.warning.toHaveTriggered(warning); }) }); }; -let makeNoWarningRunner = (test, ~module_header=module_header, name, prog) => { +let makeNoWarningRunner = + (test, ~config_fn=?, ~module_header=module_header, name, prog) => { test(name, ({expect}) => { Config.preserve_all_configs(() => { Config.print_warnings := false; - ignore @@ compile(name, module_header ++ prog); + ignore @@ compile(name, ~config_fn?, module_header ++ prog); expect.ext.warning.toHaveTriggeredNoWarnings(); }) }); diff --git a/compiler/test/suites/ignore_warnings.re b/compiler/test/suites/ignore_warnings.re new file mode 100644 index 000000000..be8ad005d --- /dev/null +++ b/compiler/test/suites/ignore_warnings.re @@ -0,0 +1,72 @@ +open Grain_tests.TestFramework; +open Grain_tests.Runner; +open Grain_utils; + +let {describe} = + describeConfig |> withCustomMatchers(customMatchers) |> build; + +describe("ignore warnings", ({test, testSkip}) => { + let assertWarning = makeWarningRunner(test); + let assertNoWarning = makeNoWarningRunner(test); + + let assertWarningFlag = (name, code, config_warning, expected_warning) => { + assertWarning( + ~config_fn=() => {Config.ignore_warnings := []}, + name, + code, + expected_warning, + ); + + assertNoWarning( + ~config_fn=() => {Config.ignore_warnings := [config_warning]}, + name, + code, + ); + }; + + assertWarningFlag( + "warning_match", + {| + match (true) { + true => void + } + |}, + Config.PartialMatch, + Warnings.PartialMatch("false"), + ); + + assertWarningFlag( + "warning_match_all_ignored", + {| + match (true) { + true => void + } + |}, + Config.IgnoreAll, + Warnings.PartialMatch("false"), + ); + + assertWarningFlag( + "warning_useless_record_spread", + {| + record R { x: Number } + let r = { x: 1 } + let r2 = { ...r, x: 2 } + |}, + Config.UselessRecordSpread, + Warnings.UselessRecordSpread, + ); + + assertWarningFlag( + "warning_print_unsafe", + {| + @unsafe + let f = () => { + let a = 1n + print(a) + } + |}, + Config.PrintUnsafe, + Warnings.PrintUnsafe("I32"), + ); +});