From 6274b732e02c843db6d67f23a73f50f34fadee99 Mon Sep 17 00:00:00 2001 From: Mangel Maxime Date: Mon, 15 Jun 2026 21:41:14 +0200 Subject: [PATCH] fix(all): align invalidArg error message with .NET format --- src/Fable.Transforms/Beam/Replacements.fs | 10 ++++--- src/Fable.Transforms/Dart/Replacements.fs | 2 +- src/Fable.Transforms/Python/Replacements.fs | 2 +- src/Fable.Transforms/Replacements.fs | 2 +- tests/Beam/ExceptionTests.fs | 6 ++++ tests/Dart/src/MiscTests.fs | 5 ++++ tests/Dart/src/Tests.Util.fs | 31 +++++++++++++++++++++ tests/Js/Main/MiscTests.fs | 5 ++++ tests/Python/TestMisc.fs | 6 ++++ 9 files changed, 62 insertions(+), 7 deletions(-) diff --git a/src/Fable.Transforms/Beam/Replacements.fs b/src/Fable.Transforms/Beam/Replacements.fs index 0215b2f49e..be0961e2e2 100644 --- a/src/Fable.Transforms/Beam/Replacements.fs +++ b/src/Fable.Transforms/Beam/Replacements.fs @@ -120,14 +120,16 @@ let private operators | "FailWith", [ msg ] | "InvalidOp", [ msg ] -> makeThrow r _t msg |> Some | "InvalidArg", [ argName; msg ] -> - let msg = add (add msg (Value(StringConstant "\\nParameter name: ", None))) argName + let msg = + add msg (add (add (Value(StringConstant " (Parameter '", None)) argName) (Value(StringConstant "')", None))) + makeThrow r _t msg |> Some | "Raise", [ arg ] -> makeThrow r _t arg |> Some | "NullArg", [ arg ] -> let msg = add - (Value(StringConstant "Value cannot be null.", None)) - (add (Value(StringConstant "\\nParameter name: ", None)) arg) + (Value(StringConstant "Value cannot be null. (Parameter '", None)) + (add arg (Value(StringConstant "')", None))) makeThrow r _t msg |> Some | "IsNull", [ arg ] -> emitExpr r _t [ arg ] "($0 =:= undefined)" |> Some @@ -150,7 +152,7 @@ let private operators r _t [ argName; arg ] - "case $1 of undefined -> erlang:error({badarg, <<\"Value cannot be null. Parameter name: \", $0/binary>>}); _ -> $1 end" + "case $1 of undefined -> erlang:error({badarg, <<\"Value cannot be null. (Parameter '\", $0/binary, \"')\">>}); _ -> $1 end" |> Some // Lock — no-op in Erlang (processes are isolated), just call the action | "Lock", [ _lockObj; action ] -> CurriedApply(action, [ Value(UnitConstant, None) ], _t, r) |> Some diff --git a/src/Fable.Transforms/Dart/Replacements.fs b/src/Fable.Transforms/Dart/Replacements.fs index 5761eb9326..e9744b646b 100644 --- a/src/Fable.Transforms/Dart/Replacements.fs +++ b/src/Fable.Transforms/Dart/Replacements.fs @@ -1020,7 +1020,7 @@ let operators (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o | "FailWith", [ msg ] | "InvalidOp", [ msg ] -> makeThrow r t (error com msg) |> Some | "InvalidArg", [ argName; msg ] -> - let msg = add (add msg (str "\\nParameter name: ")) argName + let msg = add msg (add (add (str " (Parameter '") argName) (str "')")) makeThrow r t (error com msg) |> Some | "Raise", [ arg ] -> makeThrow r t arg |> Some | "Reraise", _ -> Extended(Throw(None, t), r) |> Some diff --git a/src/Fable.Transforms/Python/Replacements.fs b/src/Fable.Transforms/Python/Replacements.fs index 217e3f12e5..7225f246e9 100644 --- a/src/Fable.Transforms/Python/Replacements.fs +++ b/src/Fable.Transforms/Python/Replacements.fs @@ -1194,7 +1194,7 @@ let operators (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o | "FailWith", [ msg ] | "InvalidOp", [ msg ] -> makeThrow r t (error com msg) |> Some | "InvalidArg", [ argName; msg ] -> - let msg = add (add msg (str "\\nParameter name: ")) argName + let msg = add msg (add (add (str " (Parameter '") argName) (str "')")) makeThrow r t (error com msg) |> Some | "Raise", [ arg ] -> makeThrow r t arg |> Some | "Reraise", _ -> diff --git a/src/Fable.Transforms/Replacements.fs b/src/Fable.Transforms/Replacements.fs index a085664a4b..d8f178c070 100644 --- a/src/Fable.Transforms/Replacements.fs +++ b/src/Fable.Transforms/Replacements.fs @@ -1393,7 +1393,7 @@ let operators (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o | "FailWith", [ msg ] | "InvalidOp", [ msg ] -> makeThrow r t (error com msg) |> Some | "InvalidArg", [ argName; msg ] -> - let msg = add (add msg (str "\\nParameter name: ")) argName + let msg = add msg (add (add (str " (Parameter '") argName) (str "')")) makeThrow r t (error com msg) |> Some | "Raise", [ arg ] -> makeThrow r t arg |> Some | "Reraise", _ -> diff --git a/tests/Beam/ExceptionTests.fs b/tests/Beam/ExceptionTests.fs index 8ae63f7efd..55b6e145e4 100644 --- a/tests/Beam/ExceptionTests.fs +++ b/tests/Beam/ExceptionTests.fs @@ -116,3 +116,9 @@ let ``test nested try-catch with custom exceptions`` () = [] let ``test custom exception in throwsAnyError`` () = throwsAnyError (fun () -> raise (MyError "boom")) + +[] +let ``test invalidArg formats the message like .NET`` () = + throwsError "This is invalid (Parameter 'arg')" (fun () -> + invalidArg "arg" "This is invalid" + ) diff --git a/tests/Dart/src/MiscTests.fs b/tests/Dart/src/MiscTests.fs index 3c99d633fc..c5513fd1c6 100644 --- a/tests/Dart/src/MiscTests.fs +++ b/tests/Dart/src/MiscTests.fs @@ -124,3 +124,8 @@ let tests() = // throwsAnyError (fun () -> // nullArgCheck "str" null // ) + + testCase "invalidArg formats the message like .NET" <| fun () -> + throwsError "This is invalid (Parameter 'arg')" (fun () -> + invalidArg "arg" "This is invalid" + ) diff --git a/tests/Dart/src/Tests.Util.fs b/tests/Dart/src/Tests.Util.fs index c6f79ec51d..9091e038e1 100644 --- a/tests/Dart/src/Tests.Util.fs +++ b/tests/Dart/src/Tests.Util.fs @@ -18,3 +18,34 @@ let testList (msg: string) (tests: unit list) = () let equal (expected: 'T) (actual: 'T) = Test.expect(actual, Test.equals(expected)) let notEqual (expected: 'T) (actual: 'T) = Test.expect(actual, Test.isNot(Test.equals(expected))) let throwsAnyError (f: unit -> 'a): unit = Test.expect(f, Test.throwsException) + +let private run (f: unit -> 'a) = + try + f() + |> Ok + with e -> + Error e.Message + +module private RunResult = + let wasError expected actual = + match actual with + | Error _ when System.String.IsNullOrEmpty expected -> () + | Error actual -> equal (Error expected) (Error actual) + | Ok value -> equal (Error expected) (Ok value) + + let wasErrorContaining expected actual = + match actual with + | Error _ when System.String.IsNullOrEmpty expected -> () + | Error (actual: string) when actual.Contains expected -> () + | Error actual -> equal (Error expected) (Error actual) + | Ok value -> equal (Error expected) (Ok value) + +let throwsError (expected: string) (f: unit -> 'a): unit = + run f + |> RunResult.wasError expected + +/// While `throwsError` tests exception message for equality, +/// this checks if error message CONTAINS passed message +let throwsErrorContaining (expected: string) (f: unit -> 'a): unit = + run f + |> RunResult.wasErrorContaining expected diff --git a/tests/Js/Main/MiscTests.fs b/tests/Js/Main/MiscTests.fs index 5755089220..943ff3b9c3 100644 --- a/tests/Js/Main/MiscTests.fs +++ b/tests/Js/Main/MiscTests.fs @@ -1150,6 +1150,11 @@ let tests = nullArgCheck "str" null ) + testCase "invalidArg formats the message like .NET" <| fun () -> + throwsError "This is invalid (Parameter 'arg')" (fun () -> + invalidArg "arg" "This is invalid" + ) + testCase "Pattern matching optimization works (switch statement)" <| fun () -> let mutable x = "" let i = 4 diff --git a/tests/Python/TestMisc.fs b/tests/Python/TestMisc.fs index 22002a9ab7..d9e2a8a62b 100644 --- a/tests/Python/TestMisc.fs +++ b/tests/Python/TestMisc.fs @@ -1127,6 +1127,12 @@ let ``test nullArgCheck throws exception if argument is null`` () = nullArgCheck "str" null ) +[] +let ``test invalidArg formats the message like .NET`` () = + throwsError "This is invalid (Parameter 'arg')" (fun () -> + invalidArg "arg" "This is invalid" + ) + # nowarn "26" // This rule will never be matched (code 26) []