Skip to content

Commit c9b3ee2

Browse files
authored
feat(all): support Exception.InnerException across all targets (#4677)
1 parent 9431881 commit c9b3ee2

16 files changed

Lines changed: 157 additions & 46 deletions

File tree

src/Fable.Transforms/Beam/Replacements.fs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5525,6 +5525,7 @@ let tryCall
55255525
| "System.Exception" ->
55265526
match info.CompiledName, thisArg, args with
55275527
| ".ctor", None, [ msg ] -> emitExpr r t [ msg ] "#{message => $0}" |> Some
5528+
| ".ctor", None, [ msg; inner ] -> emitExpr r t [ msg; inner ] "#{message => $0, inner_exception => $1}" |> Some
55285529
| ".ctor", None, [] ->
55295530
emitExpr r t [] "#{message => <<\"Exception of type 'System.Exception' was thrown.\">>}"
55305531
|> Some
@@ -5536,18 +5537,36 @@ let tryCall
55365537
[ c ]
55375538
"case erlang:is_reference($0) of true -> maps:get(message, erlang:get($0), $0); false -> maps:get(message, $0, $0) end"
55385539
|> Some
5540+
| "get_InnerException", Some c, _ ->
5541+
// Handle both map exceptions and reference-based class exceptions
5542+
emitExpr
5543+
r
5544+
t
5545+
[ c ]
5546+
"case erlang:is_reference($0) of true -> maps:get(inner_exception, erlang:get($0), undefined); false -> maps:get(inner_exception, $0, undefined) end"
5547+
|> Some
55395548
| _ -> None
55405549
// Built-in .NET exception types — all become #{message => Msg} maps in Erlang
55415550
| BuiltinSystemException _
55425551
| "System.Collections.Generic.KeyNotFoundException"
55435552
| "System.OperationCanceledException" ->
55445553
match info.CompiledName, thisArg, args with
55455554
| ".ctor", None, [ msg ] -> emitExpr r t [ msg ] "#{message => $0}" |> Some
5555+
| ".ctor", None, [ msg; second ] ->
5556+
match info.SignatureArgTypes with
5557+
// (message, paramName): paramName is not modelled, only the message is kept
5558+
| [ _; String ] -> emitExpr r t [ msg ] "#{message => $0}" |> Some
5559+
// (message, innerException)
5560+
| _ -> emitExpr r t [ msg; second ] "#{message => $0, inner_exception => $1}" |> Some
5561+
// (message, paramName, innerException)
5562+
| ".ctor", None, [ msg; _paramName; inner ] ->
5563+
emitExpr r t [ msg; inner ] "#{message => $0, inner_exception => $1}" |> Some
55465564
| ".ctor", None, [] ->
55475565
let typeName = info.DeclaringEntityFullName
55485566
let msg = $"Exception of type '%s{typeName}' was thrown."
55495567
emitExpr r t [] $"#{{message => <<\"%s{msg}\">>}}" |> Some
55505568
| "get_Message", Some c, _ -> emitExpr r t [ c ] "maps:get(message, $0, $0)" |> Some
5569+
| "get_InnerException", Some c, _ -> emitExpr r t [ c ] "maps:get(inner_exception, $0, undefined)" |> Some
55515570
| _ -> None
55525571
// System.Type (reflection) — type info is a map #{fullname => ..., generics => [...]}
55535572
| "System.Type" ->

src/Fable.Transforms/Dart/Replacements.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2606,6 +2606,7 @@ let exceptions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr
26062606
Helper.ConstructorCall(e, t, args, ?loc = r) |> Some
26072607
| "get_Message", Some e -> Helper.InstanceCall(e, "toString", t, [], ?loc = r) |> Some
26082608
// | "get_StackTrace", Some e -> getFieldWith r t e "stack" |> Some
2609+
| "get_InnerException", Some e -> getFieldWith r t e "innerException" |> Some
26092610
| _ -> None
26102611

26112612
let unchecked (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) =

src/Fable.Transforms/Python/Replacements.fs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2789,9 +2789,12 @@ let exceptions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr
27892789
match i.DeclaringEntityFullName with
27902790
// | "System.Collections.Generic.KeyNotFoundException"
27912791
| BuiltinSystemException _ -> bclType com ctx r t i thisArg args
2792-
| _ -> Helper.ConstructorCall(makeIdentExpr "Exception", t, args, ?loc = r) |> Some
2792+
| _ ->
2793+
let e = makeImportLib com Any "ExceptionBase" "Types"
2794+
Helper.ConstructorCall(e, t, args, ?loc = r) |> Some
27932795
| "get_Message", Some e -> Helper.GlobalCall("str", t, [ thisArg.Value ], ?loc = r) |> Some
27942796
| "get_StackTrace", Some e -> getFieldWith r t e "stack" |> Some
2797+
| "get_InnerException", Some e -> getFieldWith r t e "inner_exception" |> Some
27952798
| _ -> None
27962799

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

42294232
let tryBaseConstructor com ctx (ent: EntityRef) (argTypes: Lazy<Type list>) genArgs args =
42304233
match ent.FullName with
4231-
| Types.exception_ -> Some(makeIdentExpr ("Exception"), args)
4234+
| Types.exception_ -> Some(makeImportLib com Any "ExceptionBase" "Types", args)
42324235
| Types.attribute -> Some(makeImportLib com Any "Attribute" "Types", args)
42334236
| Types.dictionary ->
42344237
let args =

src/Fable.Transforms/Replacements.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3049,6 +3049,7 @@ let exceptions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr
30493049
|> Some
30503050
| "get_Message", Some e -> getFieldWith r t e "message" |> Some
30513051
| "get_StackTrace", Some e -> getFieldWith r t e "stack" |> Some
3052+
| "get_InnerException", Some e -> getFieldWith r t e "innerException" |> Some
30523053
| _ -> None
30533054

30543055
let unchecked (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) =

src/Fable.Transforms/Rust/Replacements.fs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2411,6 +2411,7 @@ let exceptions (com: ICompiler) (ctx: Context) r t (i: CallInfo) (thisArg: Expr
24112411
| ".ctor", None -> bclType com ctx r t i thisArg args
24122412
| "get_Message", Some ex -> makeInstanceCall r t i ex i.CompiledName args |> Some
24132413
| "get_StackTrace", Some ex -> makeInstanceCall r t i ex i.CompiledName args |> Some
2414+
| "get_InnerException", Some ex -> makeInstanceCall r t i ex i.CompiledName args |> Some
24142415
| _ -> None
24152416

24162417
let unchecked (com: ICompiler) (ctx: Context) r t (i: CallInfo) (_: Expr option) (args: Expr list) =

src/fable-library-dart/System.fs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,25 +64,28 @@ type TimeoutException(message: string) =
6464
inherit Exception(message)
6565
new() = TimeoutException(SR.Arg_TimeoutException)
6666

67-
type ArgumentException(message: string, paramName: string) =
67+
type ArgumentException(message: string, paramName: string, innerException: exn) =
6868
inherit
6969
Exception(
70-
if System.String.IsNullOrEmpty(paramName) then
71-
message
72-
else
73-
message + SR.Arg_ParamName_Name + paramName + "')"
70+
(if System.String.IsNullOrEmpty(paramName) then
71+
message
72+
else
73+
message + SR.Arg_ParamName_Name + paramName + "')"),
74+
innerException
7475
)
7576

76-
new() = ArgumentException(SR.Arg_ArgumentException, "")
77-
new(message) = ArgumentException(message, "")
77+
new() = ArgumentException(SR.Arg_ArgumentException, "", null)
78+
new(message) = ArgumentException(message, "", null)
79+
new(message: string, paramName: string) = ArgumentException(message, paramName, null)
80+
new(message: string, innerException: exn) = ArgumentException(message, "", innerException)
7881
member _.ParamName = paramName
7982

8083
type ArgumentNullException(paramName: string, message: string) =
81-
inherit ArgumentException(message, paramName)
84+
inherit ArgumentException(message, paramName, null)
8285
new(paramName) = ArgumentNullException(paramName, SR.ArgumentNull_Generic)
8386
new() = ArgumentNullException("")
8487

8588
type ArgumentOutOfRangeException(paramName: string, message: string) =
86-
inherit ArgumentException(message, paramName)
89+
inherit ArgumentException(message, paramName, null)
8790
new(paramName) = ArgumentOutOfRangeException(paramName, SR.Arg_ArgumentOutOfRangeException)
8891
new() = ArgumentOutOfRangeException("")

src/fable-library-dart/Types.dart

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ final Expando<Object> _setComparerExpando = Expando<Object>('fable.setComparer')
77

88
class ExceptionBase implements Exception {
99
final String message;
10-
const ExceptionBase([this.message = ""]);
10+
final Object? innerException;
11+
const ExceptionBase([this.message = "", this.innerException]);
1112

1213
@override
1314
String toString() => this.message;

src/fable-library-py/fable_library/types.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,22 @@ class Attribute:
4242
...
4343

4444

45+
class ExceptionBase(Exception):
46+
"""Base class for .NET ``System.Exception`` and its subclasses.
47+
48+
Subclasses the built-in ``Exception`` so ``raise``/``except``/``isinstance``
49+
keep working as before. Only the message is forwarded to the built-in
50+
initializer, so ``str(exc)`` still returns the message even when an inner
51+
exception is supplied (the built-in would otherwise stringify the whole
52+
argument tuple). The inner exception is kept on a dedicated attribute so it
53+
can be read back through ``System.Exception.InnerException``.
54+
"""
55+
56+
def __init__(self, message: str | None = None, inner_exception: Exception | None = None) -> None:
57+
super().__init__(message if message is not None else "")
58+
self.inner_exception: Exception | None = inner_exception
59+
60+
4561
# We don't use type aliases here because we need to do isinstance checks
4662
IntegerTypes = int | byte | sbyte | int16 | uint16 | int32 | uint32 | int64 | uint64
4763
FloatTypes = float | float32 | float64
@@ -50,6 +66,7 @@ class Attribute:
5066
__all__ = [
5167
"UNIT",
5268
"Attribute",
69+
"ExceptionBase",
5370
"FSharpRef",
5471
"FloatTypes",
5572
"IntegerTypes",

src/fable-library-rust/src/System.fs

Lines changed: 29 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -8,84 +8,89 @@ type Attribute() = class end
88

99
type Enum() = class end
1010

11-
type Exception(message: string) =
12-
new() = Exception("")
11+
[<AllowNullLiteral>]
12+
type Exception(message: string, innerException: Exception) =
13+
new() = Exception("", null)
14+
new(message) = Exception(message, null)
1315
member _.Message = message
1416
member _.StackTrace = ""
17+
member _.InnerException = innerException
1518

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

2023
type SystemException(message: string) =
21-
inherit Exception(message)
24+
inherit Exception(message, null)
2225
new() = SystemException(SR.Arg_SystemException)
2326

2427
type ApplicationException(message: string) =
25-
inherit Exception(message)
28+
inherit Exception(message, null)
2629
new() = ApplicationException(SR.Arg_ApplicationException)
2730

2831
type ArithmeticException(message: string) =
29-
inherit Exception(message)
32+
inherit Exception(message, null)
3033
new() = ArithmeticException(SR.Arg_ArithmeticException)
3134

3235
type DivideByZeroException(message: string) =
33-
inherit Exception(message)
36+
inherit Exception(message, null)
3437
new() = DivideByZeroException(SR.Arg_DivideByZero)
3538

3639
type FormatException(message: string) =
37-
inherit Exception(message)
40+
inherit Exception(message, null)
3841
new() = FormatException(SR.Arg_FormatException)
3942

4043
type IndexOutOfRangeException(message: string) =
41-
inherit Exception(message)
44+
inherit Exception(message, null)
4245
new() = IndexOutOfRangeException(SR.Arg_IndexOutOfRangeException)
4346

4447
type InvalidOperationException(message: string) =
45-
inherit Exception(message)
48+
inherit Exception(message, null)
4649
new() = InvalidOperationException(SR.Arg_InvalidOperationException)
4750

4851
type NotFiniteNumberException(message: string) =
49-
inherit Exception(message)
52+
inherit Exception(message, null)
5053
new() = NotFiniteNumberException(SR.Arg_NotFiniteNumberException)
5154

5255
type NotImplementedException(message: string) =
53-
inherit Exception(message)
56+
inherit Exception(message, null)
5457
new() = NotImplementedException(SR.Arg_NotImplementedException)
5558

5659
type NotSupportedException(message: string) =
57-
inherit Exception(message)
60+
inherit Exception(message, null)
5861
new() = NotSupportedException(SR.Arg_NotSupportedException)
5962

6063
type NullReferenceException(message: string) =
61-
inherit Exception(message)
64+
inherit Exception(message, null)
6265
new() = NullReferenceException(SR.Arg_NullReferenceException)
6366

6467
type OutOfMemoryException(message: string) =
65-
inherit Exception(message)
68+
inherit Exception(message, null)
6669
new() = OutOfMemoryException(SR.Arg_OutOfMemoryException)
6770

6871
type OverflowException(message: string) =
69-
inherit Exception(message)
72+
inherit Exception(message, null)
7073
new() = OverflowException(SR.Arg_OverflowException)
7174

7275
type RankException(message: string) =
73-
inherit Exception(message)
76+
inherit Exception(message, null)
7477
new() = RankException(SR.Arg_RankException)
7578

7679
type StackOverflowException(message: string) =
77-
inherit Exception(message)
80+
inherit Exception(message, null)
7881
new() = StackOverflowException(SR.Arg_StackOverflowException)
7982

8083
type TimeoutException(message: string) =
81-
inherit Exception(message)
84+
inherit Exception(message, null)
8285
new() = TimeoutException(SR.Arg_TimeoutException)
8386

84-
type ArgumentException(message: string, paramName: string) =
85-
inherit Exception(message)
87+
type ArgumentException(message: string, paramName: string, innerException: Exception) =
88+
inherit Exception(message, innerException)
8689

87-
new() = ArgumentException(SR.Arg_ArgumentException, "")
88-
new(message) = ArgumentException(message, "")
90+
new() = ArgumentException(SR.Arg_ArgumentException, "", null)
91+
new(message) = ArgumentException(message, "", null)
92+
new(message: string, paramName: string) = ArgumentException(message, paramName, null)
93+
new(message: string, innerException: Exception) = ArgumentException(message, "", innerException)
8994

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

98103
type ArgumentNullException(paramName: string, message: string) =
99-
inherit ArgumentException(message, paramName)
104+
inherit ArgumentException(message, paramName, null)
100105
new(paramName) = ArgumentNullException(paramName, SR.ArgumentNull_Generic)
101106
new() = ArgumentNullException("")
102107

103108
type ArgumentOutOfRangeException(paramName: string, message: string) =
104-
inherit ArgumentException(message, paramName)
109+
inherit ArgumentException(message, paramName, null)
105110
new(paramName) = ArgumentOutOfRangeException(paramName, SR.Arg_ArgumentOutOfRangeException)
106111
new() = ArgumentOutOfRangeException("")
107112

src/fable-library-ts/System.fs

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,25 +64,31 @@ type TimeoutException(message: string) =
6464
inherit Exception(message)
6565
new() = TimeoutException(SR.Arg_TimeoutException)
6666

67-
type ArgumentException(message: string, paramName: string) =
67+
type ArgumentException(message: string, paramName: string, innerException: exn | null) =
6868
inherit
6969
Exception(
70-
if System.String.IsNullOrEmpty(paramName) then
71-
message
72-
else
73-
message + SR.Arg_ParamName_Name + paramName + "')"
70+
(if System.String.IsNullOrEmpty(paramName) then
71+
message
72+
else
73+
message + SR.Arg_ParamName_Name + paramName + "')"),
74+
innerException
7475
)
7576

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

8086
type ArgumentNullException(paramName: string, message: string) =
81-
inherit ArgumentException(message, paramName)
87+
inherit ArgumentException(message, paramName, Unchecked.defaultof<exn>)
8288
new(paramName) = ArgumentNullException(paramName, SR.ArgumentNull_Generic)
8389
new() = ArgumentNullException("")
8490

8591
type ArgumentOutOfRangeException(paramName: string, message: string) =
86-
inherit ArgumentException(message, paramName)
92+
inherit ArgumentException(message, paramName, Unchecked.defaultof<exn>)
8793
new(paramName) = ArgumentOutOfRangeException(paramName, SR.Arg_ArgumentOutOfRangeException)
8894
new() = ArgumentOutOfRangeException("")

0 commit comments

Comments
 (0)