Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 43 additions & 21 deletions src/Fable.Transforms/Beam/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -329,21 +329,25 @@ let private operators
| ("DefaultArg" | "DefaultValueArg"), [ opt; defValue ] ->
Helper.LibCall(com, "fable_option", "default_value", _t, [ opt; defValue ])
|> Some
| "FailWith", [ msg ]
| "InvalidOp", [ msg ] -> makeThrow r _t msg |> Some
| "FailWith", [ msg ] -> makeThrow r _t msg |> Some
| "InvalidOp", [ msg ] ->
makeThrow r _t (emitExpr r Any [ msg ] "#{exn_type => invalid_operation_exception, message => $0}")
|> Some
| "InvalidArg", [ argName; msg ] ->
let msg =
add msg (add (add (Value(StringConstant " (Parameter '", None)) argName) (Value(StringConstant "')", None)))

makeThrow r _t msg |> Some
makeThrow r _t (emitExpr r Any [ msg ] "#{exn_type => argument_exception, message => $0}")
|> Some
| "Raise", [ arg ] -> makeThrow r _t arg |> Some
| "NullArg", [ arg ] ->
let msg =
add
(Value(StringConstant "Value cannot be null. (Parameter '", None))
(add arg (Value(StringConstant "')", None)))

makeThrow r _t msg |> Some
makeThrow r _t (emitExpr r Any [ msg ] "#{exn_type => argument_null_exception, message => $0}")
|> Some
| "IsNull", [ arg ] -> emitExpr r _t [ arg ] "($0 =:= undefined)" |> Some
| "IsNotNull", [ arg ] -> emitExpr r _t [ arg ] "($0 =/= undefined)" |> Some
// Nullable active patterns and helpers
Expand Down Expand Up @@ -6223,25 +6227,43 @@ let tryCall
let typeTag =
typeName.Substring(typeName.LastIndexOf('.') + 1) |> sanitizeErlangName

let make msg inner =
match inner with
| None -> emitExpr r t [ msg ] $"#{{exn_type => %s{typeTag}, message => $0}}"
| Some inner ->
emitExpr r t [ msg; inner ] $"#{{exn_type => %s{typeTag}, message => $0, inner_exception => $1}}"

let withParamName msg paramName =
Helper.LibCall(com, "fable_utils", "argument_exception_message", String, [ msg; paramName ])

let isArgumentException = typeName = "System.ArgumentException"

// ArgumentNullException and ArgumentOutOfRangeException take (paramName, message), the others (message, paramName)
let paramNameFirst =
typeName = "System.ArgumentNullException"
|| typeName = "System.ArgumentOutOfRangeException"

let defaultMessage =
match typeName with
| "System.ArgumentException" -> "Value does not fall within the expected range."
| "System.ArgumentNullException" -> "Value cannot be null."
| "System.ArgumentOutOfRangeException" -> "Specified argument was out of the range of valid values."
| _ -> $"Exception of type '%s{typeName}' was thrown."

match info.CompiledName, thisArg, args with
| ".ctor", None, [ msg ] -> emitExpr r t [ msg ] $"#{{exn_type => %s{typeTag}, message => $0}}" |> Some
| ".ctor", None, [ msg; second ] ->
| ".ctor", None, [] -> make (makeStrConst defaultMessage) None |> Some
| ".ctor", None, [ paramName ] when paramNameFirst ->
make (withParamName (makeStrConst defaultMessage) paramName) None |> Some
| ".ctor", None, [ msg ] -> make msg None |> Some
| ".ctor", None, [ first; second ] ->
match info.SignatureArgTypes with
// (message, paramName): paramName is not modelled, only the message is kept
| [ _; String ] -> emitExpr r t [ msg ] $"#{{exn_type => %s{typeTag}, message => $0}}" |> Some
// (message, innerException)
| _ ->
emitExpr r t [ msg; second ] $"#{{exn_type => %s{typeTag}, message => $0, inner_exception => $1}}"
|> Some
// (message, paramName, innerException)
| ".ctor", None, [ msg; _paramName; inner ] ->
emitExpr r t [ msg; inner ] $"#{{exn_type => %s{typeTag}, message => $0, inner_exception => $1}}"
|> Some
| ".ctor", None, [] ->
let msg = $"Exception of type '%s{typeName}' was thrown."

emitExpr r t [] $"#{{exn_type => %s{typeTag}, message => <<\"%s{msg}\">>}}"
|> Some
| [ _; String ] when paramNameFirst -> make (withParamName second first) None |> Some
| [ _; String ] when isArgumentException -> make (withParamName first second) None |> Some
| [ _; String ] -> make first None |> Some
| _ -> make first (Some second) |> Some
| ".ctor", None, [ msg; paramName; inner ] when isArgumentException ->
make (withParamName msg paramName) (Some inner) |> Some
| ".ctor", None, [ msg; _; inner ] -> make msg (Some inner) |> Some
| "get_Message", Some c, _ -> emitExpr r t [ c ] "maps:get(message, $0, $0)" |> Some
| "get_InnerException", Some c, _ -> emitExpr r t [ c ] "maps:get(inner_exception, $0, undefined)" |> Some
| _ -> None
Expand Down
8 changes: 2 additions & 6 deletions src/Fable.Transforms/Dart/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1036,16 +1036,12 @@ let operators (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o
// Strings
| ("PrintFormatToString" | "PrintFormatToStringThen" | "PrintFormat" | "PrintFormatLine" | "PrintFormatToError" | "PrintFormatLineToError" | "PrintFormatThen" | "PrintFormatToStringThenFail" | "PrintFormatToStringBuilder" | "PrintFormatToStringBuilderThen"), // Printf.kbprintf
_ -> fsFormat com ctx r t i thisArg args
| ("Failure" | "FailurePattern" | "LazyPattern" | "Lock" | "NullArg" | "Using"), _ ->
| ("Failure" | "FailurePattern" | "InvalidArg" | "InvalidOp" | "LazyPattern" | "Lock" | "NullArg" | "Using"), _ ->
fsharpModule com ctx r t i thisArg args
| ("IsNull" | "IsNotNull" | "IsNullV" | "NonNull" | "NonNullV" | "NullMatchPattern" | "NullValueMatchPattern" | "NonNullQuickPattern" | "NonNullQuickValuePattern" | "WithNull" | "WithNullV" | "NullV" | "NullArgCheck"),
_ -> fsharpModule com ctx r t i thisArg args
// Exceptions
| "FailWith", [ msg ]
| "InvalidOp", [ msg ] -> makeThrow r t (error com msg) |> Some
| "InvalidArg", [ argName; msg ] ->
let msg = add msg (add (add (str " (Parameter '") argName) (str "')"))
makeThrow r t (error com msg) |> Some
| "FailWith", [ msg ] -> makeThrow r t (error com msg) |> Some
| "Raise", [ arg ] -> makeThrow r t arg |> Some
| "Reraise", _ -> Extended(Throw(None, t), r) |> Some
// Math functions
Expand Down
9 changes: 3 additions & 6 deletions src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1216,18 +1216,15 @@ let operators (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o
// Strings
| ("PrintFormatToString" | "PrintFormatToStringThen" | "PrintFormat" | "PrintFormatLine" | "PrintFormatToError" | "PrintFormatLineToError" | "PrintFormatThen" | "PrintFormatToStringThenFail" | "PrintFormatToStringBuilder" | "PrintFormatToStringBuilderThen"), // bprintf
_ -> fsFormat com ctx r t i thisArg args
| ("Failure" | "FailurePattern" | "LazyPattern" | "NullArg" | "Using"), _ -> fsharpModule com ctx r t i thisArg args
| ("Failure" | "FailurePattern" | "InvalidArg" | "InvalidOp" | "LazyPattern" | "NullArg" | "Using"), _ ->
fsharpModule com ctx r t i thisArg args
| ("IsNull" | "IsNotNull" | "IsNullV" | "NonNull" | "NonNullV" | "NullMatchPattern" | "NullValueMatchPattern" | "NonNullQuickPattern" | "NonNullQuickValuePattern" | "WithNull" | "WithNullV" | "NullV" | "NullArgCheck"),
_ -> fsharpModule com ctx r t i thisArg args
| "Lock", _ ->
Helper.LibCall(com, "util", "lock", t, args, i.SignatureArgTypes, ?loc = r)
|> Some
// Exceptions
| "FailWith", [ msg ]
| "InvalidOp", [ msg ] -> makeThrow r t (error com msg) |> Some
| "InvalidArg", [ argName; msg ] ->
let msg = add msg (add (add (str " (Parameter '") argName) (str "')"))
makeThrow r t (error com msg) |> Some
| "FailWith", [ msg ] -> makeThrow r t (error com msg) |> Some
| "Raise", [ arg ] -> makeThrow r t arg |> Some
| "Reraise", _ ->
match ctx.CaughtException with
Expand Down
8 changes: 2 additions & 6 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1562,16 +1562,12 @@ let operators (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr o
// Strings
| ("PrintFormatToString" | "PrintFormatToStringThen" | "PrintFormat" | "PrintFormatLine" | "PrintFormatToError" | "PrintFormatLineToError" | "PrintFormatThen" | "PrintFormatToStringThenFail" | "PrintFormatToStringBuilder" | "PrintFormatToStringBuilderThen"), // Printf.kbprintf
_ -> fsFormat com ctx r t i thisArg args
| ("Failure" | "FailurePattern" | "LazyPattern" | "Lock" | "NullArg" | "Using"), _ ->
| ("Failure" | "FailurePattern" | "InvalidArg" | "InvalidOp" | "LazyPattern" | "Lock" | "NullArg" | "Using"), _ ->
fsharpModule com ctx r t i thisArg args
| ("IsNull" | "IsNotNull" | "IsNullV" | "NonNull" | "NonNullV" | "NullMatchPattern" | "NullValueMatchPattern" | "NonNullQuickPattern" | "NonNullQuickValuePattern" | "WithNull" | "WithNullV" | "NullV" | "NullArgCheck"),
_ -> fsharpModule com ctx r t i thisArg args
// Exceptions
| "FailWith", [ msg ]
| "InvalidOp", [ msg ] -> makeThrow r t (error com msg) |> Some
| "InvalidArg", [ argName; msg ] ->
let msg = add msg (add (add (str " (Parameter '") argName) (str "')"))
makeThrow r t (error com msg) |> Some
| "FailWith", [ msg ] -> makeThrow r t (error com msg) |> Some
| "Raise", [ arg ] -> makeThrow r t arg |> Some
| "Reraise", _ ->
match ctx.CaughtException with
Expand Down
9 changes: 9 additions & 0 deletions src/fable-library-beam/fable_utils.erl
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
to_list/1,
enumerate_to_list/1,
aggregate_exception/2,
argument_exception_message/2,
exception_message/1,
is_exception_type/2,
new_byte_array/1,
Expand Down Expand Up @@ -67,6 +68,7 @@
-spec to_list(term()) -> list().
-spec enumerate_to_list(term()) -> list().
-spec aggregate_exception(binary(), term()) -> map().
-spec argument_exception_message(binary(), binary() | undefined) -> binary().
-spec exception_message(term()) -> binary().
-spec is_exception_type(term(), atom()) -> boolean().
-spec new_byte_array(list() | binary() | tuple()) -> tuple().
Expand Down Expand Up @@ -477,6 +479,13 @@ aggregate_exception(BaseMessage, ExceptionsValue) ->
inner_exceptions => new_ref(Exceptions)
}.

argument_exception_message(Message, undefined) ->
Message;
argument_exception_message(Message, <<>>) ->
Message;
argument_exception_message(Message, ParamName) ->
<<Message/binary, " (Parameter '", ParamName/binary, "')">>.

%% Built-in .NET exceptions carry a compact nominal tag. Follow the .NET exception
%% hierarchy here so a catch for a base exception also accepts its derived exceptions.
is_exception_type(Exception, Expected) when is_reference(Exception) ->
Expand Down
8 changes: 8 additions & 0 deletions src/fable-library-dart/FSharp.Core.fs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,14 @@ module Operators =
let nullArg (argumentName: string) =
raise (System.ArgumentNullException(argumentName))

[<CompiledName("InvalidArg")>]
let invalidArg (argumentName: string) (message: string) =
raise (System.ArgumentException(message, argumentName))

[<CompiledName("InvalidOp")>]
let invalidOp (message: string) =
raise (System.InvalidOperationException(message))

[<CompiledName("Using")>]
let using<'T, 'R when 'T :> System.IDisposable and 'T: null> (resource: 'T) (action: 'T -> 'R) =
try
Expand Down
8 changes: 8 additions & 0 deletions src/fable-library-ts/FSharp.Core.fs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,14 @@ module Operators =
let nullArg (argumentName: string) =
raise (System.ArgumentNullException(argumentName))

[<CompiledName("InvalidArg")>]
let invalidArg (argumentName: string) (message: string) =
raise (System.ArgumentException(message, argumentName))

[<CompiledName("InvalidOp")>]
let invalidOp (message: string) =
raise (System.InvalidOperationException(message))

[<CompiledName("Using")>]
let using<'T, 'R when 'T :> System.IDisposable and 'T: null> (resource: 'T) (action: 'T -> 'R) =
try
Expand Down
30 changes: 30 additions & 0 deletions tests/Beam/ExceptionTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -170,3 +170,33 @@ let ``test invalidArg formats the message like .NET`` () =
throwsError "This is invalid (Parameter 'arg')" (fun () ->
invalidArg "arg" "This is invalid"
)

[<Fact>]
let ``test invalidArg raises ArgumentException`` () =
let caught =
try
invalidArg "arg" "This is invalid"
with
| :? System.ArgumentException as e -> e.Message
| e -> "Unexpected exception: " + e.Message
caught |> equal "This is invalid (Parameter 'arg')"

[<Fact>]
let ``test invalidOp raises InvalidOperationException`` () =
let caught =
try
invalidOp "This is invalid"
with
| :? System.InvalidOperationException as e -> e.Message
| e -> "Unexpected exception: " + e.Message
caught |> equal "This is invalid"

[<Fact>]
let ``test nullArg raises ArgumentNullException`` () =
let caught =
try
nullArg "arg"
with
| :? System.ArgumentNullException as e -> e.Message
| e -> "Unexpected exception: " + e.Message
caught |> equal "Value cannot be null. (Parameter 'arg')"
32 changes: 32 additions & 0 deletions tests/Beam/MiscTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -768,6 +768,38 @@ let ``test ArgumentException with message and inner exception works`` () =
ex.Message |> equal "outer message"
ex.InnerException.Message |> equal "the inner cause"

[<Fact>]
let ``test ArgumentException default message matches .NET`` () =
let ex = System.ArgumentException()
ex.Message |> equal "Value does not fall within the expected range."

[<Fact>]
let ``test ArgumentException with message and parameter name works`` () =
let ex = System.ArgumentException("outer message", "arg")
ex.Message |> equal "outer message (Parameter 'arg')"

[<Fact>]
let ``test ArgumentException with message, parameter name and inner exception works`` () =
let inner = exn "the inner cause"
let ex = System.ArgumentException("outer message", "arg", inner)
ex.Message |> equal "outer message (Parameter 'arg')"
ex.InnerException.Message |> equal "the inner cause"

[<Fact>]
let ``test ArgumentNullException with parameter name works`` () =
let ex = System.ArgumentNullException("arg")
ex.Message |> equal "Value cannot be null. (Parameter 'arg')"

[<Fact>]
let ``test ArgumentNullException with parameter name and message works`` () =
let ex = System.ArgumentNullException("arg", "custom message")
ex.Message |> equal "custom message (Parameter 'arg')"

[<Fact>]
let ``test ArgumentOutOfRangeException with parameter name works`` () =
let ex = System.ArgumentOutOfRangeException("arg")
ex.Message |> equal "Specified argument was out of the range of valid values. (Parameter 'arg')"

[<Fact>]
let ``test Exception InnerException is null when not provided`` () =
let ex = System.ArgumentException("no inner")
Expand Down
18 changes: 18 additions & 0 deletions tests/Dart/src/MiscTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -155,6 +155,24 @@ let tests() =
invalidArg "arg" "This is invalid"
)

testCase "invalidArg raises ArgumentException" <| fun () ->
let caught =
try
invalidArg "arg" "This is invalid"
with
| :? System.ArgumentException as e -> e.Message
| e -> "Unexpected exception: " + e.Message
equal "This is invalid (Parameter 'arg')" caught

testCase "invalidOp raises InvalidOperationException" <| fun () ->
let caught =
try
invalidOp "This is invalid"
with
| :? System.InvalidOperationException as e -> e.Message
| e -> "Unexpected exception: " + e.Message
equal "This is invalid" caught

testCase "Compiler target flags have correct value per target" <| fun () ->
equal false Compiler.isJavaScript
equal false Compiler.isTypeScript
Expand Down
18 changes: 18 additions & 0 deletions tests/Js/Main/MiscTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@

let mutable mutableValue = 0

let rec recursive1 = delay (fun () -> recursive2())

Check warning on line 385 in tests/Js/Main/MiscTests.fs

View workflow job for this annotation

GitHub Actions / build-javascript (ubuntu-latest)

This and other recursive references to the object(s) being defined will be checked for initialization-soundness at runtime through the use of a delayed reference. This is because you are defining one or more recursive objects, rather than recursive functions. This warning may be suppressed by using '#nowarn "40"' or '--nowarn:40'.

Check warning on line 385 in tests/Js/Main/MiscTests.fs

View workflow job for this annotation

GitHub Actions / build-javascript (windows-latest)

This and other recursive references to the object(s) being defined will be checked for initialization-soundness at runtime through the use of a delayed reference. This is because you are defining one or more recursive objects, rather than recursive functions. This warning may be suppressed by using '#nowarn "40"' or '--nowarn:40'.
and recursive2 =
mutableValue <- 5
fun () -> mutableValue <- mutableValue * 2
Expand Down Expand Up @@ -1260,6 +1260,24 @@
invalidArg "arg" "This is invalid"
)

testCase "invalidArg raises ArgumentException" <| fun () ->
let caught =
try
invalidArg "arg" "This is invalid"
with
| :? System.ArgumentException as e -> e.Message
| e -> "Unexpected exception: " + e.Message
equal "This is invalid (Parameter 'arg')" caught

testCase "invalidOp raises InvalidOperationException" <| fun () ->
let caught =
try
invalidOp "This is invalid"
with
| :? System.InvalidOperationException as e -> e.Message
| e -> "Unexpected exception: " + e.Message
equal "This is invalid" caught

testCase "Pattern matching optimization works (switch statement)" <| fun () ->
let mutable x = ""
let i = 4
Expand All @@ -1268,7 +1286,7 @@
| 2 -> x <- "2"
| 3 | 4 -> x <- "3" // Multiple cases are allowed
// | 5 | 6 as j -> x <- string j // This prevents the optimization
| 4 -> x <- "4" // Unreachable cases are removed

Check warning on line 1289 in tests/Js/Main/MiscTests.fs

View workflow job for this annotation

GitHub Actions / build-javascript (ubuntu-latest)

This rule will never be matched

Check warning on line 1289 in tests/Js/Main/MiscTests.fs

View workflow job for this annotation

GitHub Actions / build-javascript (windows-latest)

This rule will never be matched
| _ -> x <- "?"
equal "3" x

Expand Down
20 changes: 20 additions & 0 deletions tests/Python/TestMisc.fs
Original file line number Diff line number Diff line change
Expand Up @@ -1277,6 +1277,26 @@
invalidArg "arg" "This is invalid"
)

[<Fact>]
let ``test invalidArg raises ArgumentException`` () =
let caught =
try
invalidArg "arg" "This is invalid"
with
| :? System.ArgumentException as e -> e.Message
| e -> "Unexpected exception: " + e.Message
caught |> equal "This is invalid (Parameter 'arg')"

[<Fact>]
let ``test invalidOp raises InvalidOperationException`` () =
let caught =
try
invalidOp "This is invalid"
with
| :? System.InvalidOperationException as e -> e.Message
| e -> "Unexpected exception: " + e.Message
caught |> equal "This is invalid"

# nowarn "26" // This rule will never be matched (code 26)

[<Fact>]
Expand All @@ -1288,7 +1308,7 @@
| 2 -> x <- "2"
| 3 | 4 -> x <- "3" // Multiple cases are allowed
// | 5 | 6 as j -> x <- string j // This prevents the optimization
| 4 -> x <- "4" // Unreachable cases are removed

Check warning on line 1311 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / analyzers (src/Fable.Transforms/Fable.Transforms.fsproj)

This rule will never be matched

Check warning on line 1311 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.15)

This rule will never be matched

Check warning on line 1311 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.14)

This rule will never be matched

Check warning on line 1311 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (ubuntu-latest, 3.13)

This rule will never be matched

Check warning on line 1311 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.14)

This rule will never be matched

Check warning on line 1311 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.13)

This rule will never be matched

Check warning on line 1311 in tests/Python/TestMisc.fs

View workflow job for this annotation

GitHub Actions / build-python (windows-latest, 3.15)

This rule will never be matched
| _ -> x <- "?"
equal "3" x

Expand Down
Loading