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
19 changes: 19 additions & 0 deletions src/Fable.Transforms/Beam/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -5525,6 +5525,7 @@ let tryCall
| "System.Exception" ->
match info.CompiledName, thisArg, args with
| ".ctor", None, [ msg ] -> emitExpr r t [ msg ] "#{message => $0}" |> Some
| ".ctor", None, [ msg; inner ] -> emitExpr r t [ msg; inner ] "#{message => $0, inner_exception => $1}" |> Some
| ".ctor", None, [] ->
emitExpr r t [] "#{message => <<\"Exception of type 'System.Exception' was thrown.\">>}"
|> Some
Expand All @@ -5536,18 +5537,36 @@ let tryCall
[ c ]
"case erlang:is_reference($0) of true -> maps:get(message, erlang:get($0), $0); false -> maps:get(message, $0, $0) end"
|> Some
| "get_InnerException", Some c, _ ->
// Handle both map exceptions and reference-based class exceptions
emitExpr
r
t
[ c ]
"case erlang:is_reference($0) of true -> maps:get(inner_exception, erlang:get($0), undefined); false -> maps:get(inner_exception, $0, undefined) end"
|> Some
| _ -> None
// Built-in .NET exception types — all become #{message => Msg} maps in Erlang
| BuiltinSystemException _
| "System.Collections.Generic.KeyNotFoundException"
| "System.OperationCanceledException" ->
match info.CompiledName, thisArg, args with
| ".ctor", None, [ msg ] -> emitExpr r t [ msg ] "#{message => $0}" |> Some
| ".ctor", None, [ msg; second ] ->
match info.SignatureArgTypes with
// (message, paramName): paramName is not modelled, only the message is kept
| [ _; String ] -> emitExpr r t [ msg ] "#{message => $0}" |> Some
// (message, innerException)
| _ -> emitExpr r t [ msg; second ] "#{message => $0, inner_exception => $1}" |> Some
// (message, paramName, innerException)
| ".ctor", None, [ msg; _paramName; inner ] ->
emitExpr r t [ msg; inner ] "#{message => $0, inner_exception => $1}" |> Some
| ".ctor", None, [] ->
let typeName = info.DeclaringEntityFullName
let msg = $"Exception of type '%s{typeName}' was thrown."
emitExpr r t [] $"#{{message => <<\"%s{msg}\">>}}" |> 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
// System.Type (reflection) — type info is a map #{fullname => ..., generics => [...]}
| "System.Type" ->
Expand Down
1 change: 1 addition & 0 deletions src/Fable.Transforms/Dart/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2606,6 +2606,7 @@ let exceptions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr
Helper.ConstructorCall(e, t, args, ?loc = r) |> Some
| "get_Message", Some e -> Helper.InstanceCall(e, "toString", t, [], ?loc = r) |> Some
// | "get_StackTrace", Some e -> getFieldWith r t e "stack" |> Some
| "get_InnerException", Some e -> getFieldWith r t e "innerException" |> Some
| _ -> None

let unchecked (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) =
Expand Down
7 changes: 5 additions & 2 deletions src/Fable.Transforms/Python/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2789,9 +2789,12 @@ let exceptions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr
match i.DeclaringEntityFullName with
// | "System.Collections.Generic.KeyNotFoundException"
| BuiltinSystemException _ -> bclType com ctx r t i thisArg args
| _ -> Helper.ConstructorCall(makeIdentExpr "Exception", t, args, ?loc = r) |> Some
| _ ->
let e = makeImportLib com Any "ExceptionBase" "Types"
Helper.ConstructorCall(e, t, args, ?loc = r) |> Some
| "get_Message", Some e -> Helper.GlobalCall("str", t, [ thisArg.Value ], ?loc = r) |> Some
| "get_StackTrace", Some e -> getFieldWith r t e "stack" |> Some
| "get_InnerException", Some e -> getFieldWith r t e "inner_exception" |> Some
| _ -> None

let unchecked (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) =
Expand Down Expand Up @@ -4228,7 +4231,7 @@ let tryCall (com: ICompiler) (ctx: Context) r t (info: CallInfo) (thisArg: Expr

let tryBaseConstructor com ctx (ent: EntityRef) (argTypes: Lazy<Type list>) genArgs args =
match ent.FullName with
| Types.exception_ -> Some(makeIdentExpr ("Exception"), args)
| Types.exception_ -> Some(makeImportLib com Any "ExceptionBase" "Types", args)
| Types.attribute -> Some(makeImportLib com Any "Attribute" "Types", args)
| Types.dictionary ->
let args =
Expand Down
1 change: 1 addition & 0 deletions src/Fable.Transforms/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -3049,6 +3049,7 @@ let exceptions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr
|> Some
| "get_Message", Some e -> getFieldWith r t e "message" |> Some
| "get_StackTrace", Some e -> getFieldWith r t e "stack" |> Some
| "get_InnerException", Some e -> getFieldWith r t e "innerException" |> Some
| _ -> None

let unchecked (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) =
Expand Down
1 change: 1 addition & 0 deletions src/Fable.Transforms/Rust/Replacements.fs
Original file line number Diff line number Diff line change
Expand Up @@ -2411,6 +2411,7 @@ let exceptions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr
| ".ctor", None -> bclType com ctx r t i thisArg args
| "get_Message", Some ex -> makeInstanceCall r t i ex i.CompiledName args |> Some
| "get_StackTrace", Some ex -> makeInstanceCall r t i ex i.CompiledName args |> Some
| "get_InnerException", Some ex -> makeInstanceCall r t i ex i.CompiledName args |> Some
| _ -> None

let unchecked (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) =
Expand Down
21 changes: 12 additions & 9 deletions src/fable-library-dart/System.fs
Original file line number Diff line number Diff line change
Expand Up @@ -64,25 +64,28 @@ type TimeoutException(message: string) =
inherit Exception(message)
new() = TimeoutException(SR.Arg_TimeoutException)

type ArgumentException(message: string, paramName: string) =
type ArgumentException(message: string, paramName: string, innerException: exn) =
inherit
Exception(
if System.String.IsNullOrEmpty(paramName) then
message
else
message + SR.Arg_ParamName_Name + paramName + "')"
(if System.String.IsNullOrEmpty(paramName) then
message
else
message + SR.Arg_ParamName_Name + paramName + "')"),
innerException
)

new() = ArgumentException(SR.Arg_ArgumentException, "")
new(message) = ArgumentException(message, "")
new() = ArgumentException(SR.Arg_ArgumentException, "", null)
new(message) = ArgumentException(message, "", null)
new(message: string, paramName: string) = ArgumentException(message, paramName, null)
new(message: string, innerException: exn) = ArgumentException(message, "", innerException)
member _.ParamName = paramName

type ArgumentNullException(paramName: string, message: string) =
inherit ArgumentException(message, paramName)
inherit ArgumentException(message, paramName, null)
new(paramName) = ArgumentNullException(paramName, SR.ArgumentNull_Generic)
new() = ArgumentNullException("")

type ArgumentOutOfRangeException(paramName: string, message: string) =
inherit ArgumentException(message, paramName)
inherit ArgumentException(message, paramName, null)
new(paramName) = ArgumentOutOfRangeException(paramName, SR.Arg_ArgumentOutOfRangeException)
new() = ArgumentOutOfRangeException("")
3 changes: 2 additions & 1 deletion src/fable-library-dart/Types.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ final Expando<Object> _setComparerExpando = Expando<Object>('fable.setComparer')

class ExceptionBase implements Exception {
final String message;
const ExceptionBase([this.message = ""]);
final Object? innerException;
const ExceptionBase([this.message = "", this.innerException]);

@override
String toString() => this.message;
Expand Down
17 changes: 17 additions & 0 deletions src/fable-library-py/fable_library/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,22 @@ class Attribute:
...


class ExceptionBase(Exception):
"""Base class for .NET ``System.Exception`` and its subclasses.

Subclasses the built-in ``Exception`` so ``raise``/``except``/``isinstance``
keep working as before. Only the message is forwarded to the built-in
initializer, so ``str(exc)`` still returns the message even when an inner
exception is supplied (the built-in would otherwise stringify the whole
argument tuple). The inner exception is kept on a dedicated attribute so it
can be read back through ``System.Exception.InnerException``.
"""

def __init__(self, message: str | None = None, inner_exception: Exception | None = None) -> None:
super().__init__(message if message is not None else "")
self.inner_exception: Exception | None = inner_exception


# We don't use type aliases here because we need to do isinstance checks
IntegerTypes = int | byte | sbyte | int16 | uint16 | int32 | uint32 | int64 | uint64
FloatTypes = float | float32 | float64
Expand All @@ -50,6 +66,7 @@ class Attribute:
__all__ = [
"UNIT",
"Attribute",
"ExceptionBase",
"FSharpRef",
"FloatTypes",
"IntegerTypes",
Expand Down
53 changes: 29 additions & 24 deletions src/fable-library-rust/src/System.fs
Original file line number Diff line number Diff line change
Expand Up @@ -8,84 +8,89 @@ type Attribute() = class end

type Enum() = class end

type Exception(message: string) =
new() = Exception("")
[<AllowNullLiteral>]
type Exception(message: string, innerException: Exception) =
new() = Exception("", null)
new(message) = Exception(message, null)
member _.Message = message
member _.StackTrace = ""
member _.InnerException = innerException

interface System.Collections.IStructuralEquatable with
member x.Equals(y, comparer) = false
member x.GetHashCode(comparer) = 0

type SystemException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = SystemException(SR.Arg_SystemException)

type ApplicationException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = ApplicationException(SR.Arg_ApplicationException)

type ArithmeticException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = ArithmeticException(SR.Arg_ArithmeticException)

type DivideByZeroException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = DivideByZeroException(SR.Arg_DivideByZero)

type FormatException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = FormatException(SR.Arg_FormatException)

type IndexOutOfRangeException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = IndexOutOfRangeException(SR.Arg_IndexOutOfRangeException)

type InvalidOperationException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = InvalidOperationException(SR.Arg_InvalidOperationException)

type NotFiniteNumberException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = NotFiniteNumberException(SR.Arg_NotFiniteNumberException)

type NotImplementedException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = NotImplementedException(SR.Arg_NotImplementedException)

type NotSupportedException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = NotSupportedException(SR.Arg_NotSupportedException)

type NullReferenceException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = NullReferenceException(SR.Arg_NullReferenceException)

type OutOfMemoryException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = OutOfMemoryException(SR.Arg_OutOfMemoryException)

type OverflowException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = OverflowException(SR.Arg_OverflowException)

type RankException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = RankException(SR.Arg_RankException)

type StackOverflowException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = StackOverflowException(SR.Arg_StackOverflowException)

type TimeoutException(message: string) =
inherit Exception(message)
inherit Exception(message, null)
new() = TimeoutException(SR.Arg_TimeoutException)

type ArgumentException(message: string, paramName: string) =
inherit Exception(message)
type ArgumentException(message: string, paramName: string, innerException: Exception) =
inherit Exception(message, innerException)

new() = ArgumentException(SR.Arg_ArgumentException, "")
new(message) = ArgumentException(message, "")
new() = ArgumentException(SR.Arg_ArgumentException, "", null)
new(message) = ArgumentException(message, "", null)
new(message: string, paramName: string) = ArgumentException(message, paramName, null)
new(message: string, innerException: Exception) = ArgumentException(message, "", innerException)

member _.Message =
if System.String.IsNullOrEmpty(paramName) then
Expand All @@ -96,12 +101,12 @@ type ArgumentException(message: string, paramName: string) =
member _.ParamName = paramName

type ArgumentNullException(paramName: string, message: string) =
inherit ArgumentException(message, paramName)
inherit ArgumentException(message, paramName, null)
new(paramName) = ArgumentNullException(paramName, SR.ArgumentNull_Generic)
new() = ArgumentNullException("")

type ArgumentOutOfRangeException(paramName: string, message: string) =
inherit ArgumentException(message, paramName)
inherit ArgumentException(message, paramName, null)
new(paramName) = ArgumentOutOfRangeException(paramName, SR.Arg_ArgumentOutOfRangeException)
new() = ArgumentOutOfRangeException("")

Expand Down
24 changes: 15 additions & 9 deletions src/fable-library-ts/System.fs
Original file line number Diff line number Diff line change
Expand Up @@ -64,25 +64,31 @@ type TimeoutException(message: string) =
inherit Exception(message)
new() = TimeoutException(SR.Arg_TimeoutException)

type ArgumentException(message: string, paramName: string) =
type ArgumentException(message: string, paramName: string, innerException: exn | null) =
inherit
Exception(
if System.String.IsNullOrEmpty(paramName) then
message
else
message + SR.Arg_ParamName_Name + paramName + "')"
(if System.String.IsNullOrEmpty(paramName) then
message
else
message + SR.Arg_ParamName_Name + paramName + "')"),
innerException
)

new() = ArgumentException(SR.Arg_ArgumentException, "")
new(message) = ArgumentException(message, "")
// Use Unchecked.defaultof rather than a `null` literal for the absent inner
// exception: Fable erases nullable reference annotations, so a `null` literal
// would be emitted against a non-nullable parameter type and fail type checking.
new() = ArgumentException(SR.Arg_ArgumentException, "", Unchecked.defaultof<exn>)
new(message) = ArgumentException(message, "", Unchecked.defaultof<exn>)
new(message: string, paramName: string) = ArgumentException(message, paramName, Unchecked.defaultof<exn>)
new(message: string, innerException: exn | null) = ArgumentException(message, "", innerException)
member _.ParamName = paramName

type ArgumentNullException(paramName: string, message: string) =
inherit ArgumentException(message, paramName)
inherit ArgumentException(message, paramName, Unchecked.defaultof<exn>)
new(paramName) = ArgumentNullException(paramName, SR.ArgumentNull_Generic)
new() = ArgumentNullException("")

type ArgumentOutOfRangeException(paramName: string, message: string) =
inherit ArgumentException(message, paramName)
inherit ArgumentException(message, paramName, Unchecked.defaultof<exn>)
new(paramName) = ArgumentOutOfRangeException(paramName, SR.Arg_ArgumentOutOfRangeException)
new() = ArgumentOutOfRangeException("")
8 changes: 7 additions & 1 deletion src/fable-library-ts/Util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,9 +80,15 @@ export interface ICollection<T> extends IEnumerable<T> {
export class Exception {
public message: string;
public stack?: string;
// Typed non-nullable to match how Fable models .NET reference types: nullability
// annotations are erased, so consumers (and `get_InnerException`) see `Exception`.
// At runtime this is `undefined` when no inner exception was provided, just like
// a `defaultOf()` value, which is fine for code that reads `.InnerException`.
public innerException!: Exception;

constructor(msg?: string) {
constructor(msg?: string, innerException?: Exception) {
this.message = msg ?? "";
this.innerException = innerException as Exception;
}

toString() {
Expand Down
12 changes: 12 additions & 0 deletions tests/Beam/MiscTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -733,6 +733,18 @@ let ``test try-with with unmatched exception type reraises`` () =
| _ -> caught <- "other"
caught |> equal "arg"

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

[<Fact>]
let ``test Exception InnerException is null when not provided`` () =
let ex = System.ArgumentException("no inner")
isNull (box ex.InnerException) |> equal true

// -- General / Misc --

[<Fact>]
Expand Down
6 changes: 6 additions & 0 deletions tests/Dart/src/TypeTests.fs
Original file line number Diff line number Diff line change
Expand Up @@ -608,6 +608,12 @@ let tests() =
let z = MyOptionalClass(?arg3 = Some 2)
(z.P1, z.P2, z.P3) |> equal (1.0, "1", 2)

testCase "ArgumentException with message and inner exception works" <| fun () ->
let inner = exn "the inner cause"
let ex = System.ArgumentException("outer message", inner)
ex.Message |> equal "outer message"
ex.InnerException.Message |> equal "the inner cause"

// testCase "Can implement interface optional properties" <| fun () ->
// let veryOptionalValue = VeryOptionalClass() :> VeryOptionalInterface
// veryOptionalValue.Bar |> equal (Some 3)
Expand Down
Loading
Loading