Changeset 30901eb6 for doc/proposals


Ignore:
Timestamp:
May 20, 2026, 12:27:15 PM (20 hours ago)
Author:
Peter A. Buhr <pabuhr@…>
Branches:
master
Children:
a9049dd
Parents:
cea0d0c
Message:

updated exception proposal from PAB

File:
1 edited

Legend:

Unmodified
Added
Removed
  • doc/proposals/exceptions-pab.md

    rcea0d0c r30901eb6  
    5959
    6060```
    61 exception int dual( int, double ) dual;         // Dual, return result or exit handler block, no initial stack unwinding
     61exception int dual( int, double ) dual;         // Dual, return result or exit handler block,
     62                                                                                        // no initial stack unwinding
    6263```
    6364
     
    6667
    6768```
    68         } catch( int fix( int i, float j ) ) {  // stack not unwound, i and j accessible, must return
    69                 return 42;                                                      // implies return to fix call, not return of nested function
     69        } catch( int fix( int i, float j ) ) {  // stack not unwound, i/j accessible, => must return
     70                return 42;                                                      // => return to fix call, not return of lexical function
    7071                break;                                                          // syntax error => must return
    71                 // fallthrough => syntax error
     72                // fallthrough => must return
    7273        }
    7374```
     
    7879```
    7980        } catch( void recover( int i ) ) {              // stack unwound, cannot return
    80                 break;                                                          // implies exit catch routine
    81                 return;                                                         // implies return from lexical function
    82                 // fallthrough => implies break
     81                break;                                                          // => exit catch routine
     82                return;                                                         // => return from lexical function
     83                // fallthrough => => break
    8384        }
    8485```
     
    8889```
    8990        } catch( int dual( int i, float j ) ) { // stack not unwound, can return
    90                 return 42;                                                      // implies return to fix call, not return of nested function
    91                 break;                                                          // implies exit catch body
    92                 // fallthrough => implies break
     91                return 42;                                                      // => return to fix call, not return of nested function
     92                break;                                                          // => exit catch body
     93                // fallthrough => break
    9394        }
    9495```
     
    9697In all cases, a handler can raise another exception.
    9798
    98 An exception function can have a static body, which define the action if the exception is not caught.
    99 If no body is specified, there are default actions added.
    100 This matches with `defaultResume` and `defaultTerminate` in current C&forall;/&mu;C++.
    101 Here a parameter name is necessary to access the raise argument(s).
    102 
    103 Resumption default:
    104 
    105 ```
    106 exception int resumpt( int i ) {                        // called if no handler found
    107         // return default correction or abort or raise another exception
    108         // default if not specified is to call UnhandledException at resumer or joiner.
    109 }
    110 ```
    111 
    112 Termination default:
    113 
    114 ```
    115 exception void termin( double d ) {                     // called if no handler found
    116         // abort or raise another exception
    117         // default if not specified is to call UnhandledException at resumer or joiner.
    118 }
    119 ```
    120 
    121 Dual default is the same as termination default, as having a default correction action is unlikely.
    122 
    123 A `throws` clause can be added to a regular function to indicate alternate outcomes.
    124 
    125 ```
    126 int foo(...) throws( int ex( int ), float ex( int ), char ex( double, double ) );
    127 ```
    128 
    129 The `throws` clause is *not* part of the function type, and hence, is not used for overloading.
    130 Functions with a `throws` clause cause are handled by a separate type-checking pass, which examines the statically call structure to determine if calls to `foo` are nested directly or indirectly within guarded blocks with matching catch clauses, e.g.:
    131 
    132 ```
    133 int bar( int i ) throws( int fixup( int ) );
    134 
    135 void foo(...) {
    136         ... i = bar( 3 ); ...                                   // call statically nested within handler for fixup
    137 }
    138 void baz() {
    139         try {
    140                 foo(...);
    141         } catch( int fixup( int ) ) {
    142                 ... return 42;
    143         }
    144 }
    145 ```
    146 
    147 If this type check fails, a warning is given, and a dynamic check is wrapped around the call to verify only the specified exception functions are raised.
    148 This dual approach allows all forms of reuse to exist, and is similar to checked/unchecked exceptions in Java.
    149 
    15099Resumption example:
    151100
    152101```
    153 exception int fix( int i, float f );
    154 
     102exception int fix( int, float );
    155103void foo() {
    156104        try {
    157105                for () {
    158                         if ( ... ) x = fix( 3, 5.4 );
     106                        ... if ( ... ) x = fix( 3, 5.4 ); ...
    159107                }
    160         } catch( int fix( int i, float j ) ) {
     108        } catch( int fix( int i, float f ) ) {
    161109                ... return 42;                                          // fix up problem and return to raise
    162110        }
     
    168116```
    169117exception void end_of_file( ifstream is );
    170 
    171118void foo() {
    172119        int i;
    173120        try {
    174121                for () {
    175                         sin | i;  // internally, does a call end_of_file( is ), which implicitly throws exception
     122                        sin | i;                        // internally, calls end_of_file( is ) to raise exception
    176123                        sout | i;
    177124                }
     
    185132
    186133```
    187 exception int dual( int i, float f ) dual;
    188 
     134exception int dual( int i, float ) dual;
    189135void baz() {
    190136        try {
     
    192138                        if ( ... ) x = dual( 3, 5.4 );
    193139                }
    194         } catch( int dual( int i, float j ) ) {
     140        } catch( int dual( int i, float f ) ) { // stack not unwound
    195141                if ( ... ) return 42;                           // return to raise calls
    196142                if ( ... ) break;                                       // exit catch body
    197                 // fallthrough                                          // implies break
    198         }
    199 }
    200 ```
    201 
    202 I'm not sure this polymorphism is doing anything, except suggesting a common pattern.
    203 A catch clause cannot be polymorphic because there is no RTTI matching.
    204 
    205 ```
    206 forall( T ) exception void arithmetic( T op1, T op2, int retcode ) noreturn;
    207 
     143                // fallthrough => break
     144        }
     145}
     146```
     147
     148
     149## Default Handler
     150
     151An exception function can have a static body, which defines the action if the exception is not caught.
     152If no body is specified, there are default actions added.
     153This matches with `defaultResume` and `defaultTerminate` in current C&forall;/&mu;C++.
     154Here a parameter name is necessary to access the raise argument(s).
     155
     156Resumption default:
     157
     158```
     159exception int resumpt( int i ) {                        // called if no handler found
     160        // return default correction or abort or raise another exception
     161        // default if not specified is to call UnhandledException at resumer or joiner.
     162}
     163```
     164
     165Termination default:
     166
     167```
     168exception void termin( double d ) {                     // called if no handler found
     169        // abort or raise another exception
     170        // default if not specified is to call UnhandledException at resumer or joiner.
     171}
     172```
     173
     174Dual default is the same as termination default, as having a default correction action is unlikely.
     175
     176
     177## Polymorphism
     178
     179A catch clause can be polymorphic using the RTTI information in a mangled name.
     180
     181```
     182forall( T | { T ?+?( T, T ); } )
     183exception T foo( T t ) { return t + t; }
     184
     185        try (
     186                i = foo( 3 );                                           // raise
     187        } catch( int foo( int t ) ) {                   // monomorphic handler
     188                return t + 3 / t;                                       // can do something
     189        } catch( forall( T | { T ?+?( T, T ) } ) T foo( T t ) ) { // polymorphic handler
     190                return t + t;                                           // cannot do much
     191        }
     192
     193```
     194
     195Matching is a pattern match on the mangled name (assuming there is enough information):
     196
     197```
     198_X3fooQ1_0_0_5__X16_operator_assignFBD0_BD0BD0__X12_constructorFv_BD0__X12_constructorFv_BD0BD0
     199        __X11_destructorFv_BD0__X13_operator_addFBD0_BD0BD0__FBD0_BD0__1
     200```
     201
     202
     203## Separate Compilation
     204
     205**test.hfa**
     206
     207```
     208typedef struct S {
     209        void (*f)( int );                                               // general function pointer
     210} S;
     211
     212void foo( int ) throws( E );                            // external
     213void bar( int ) throws( F );
     214
     215void mary( S & s );                                                     // external
     216void jane( S s );
     217```
     218
     219**test2.cfa**
     220
     221```
     222#include "test1.hfa"
     223#include "stdlib.h"
     224
     225void foo( int ) { printf( "foo\n" ); }
     226void bar( int ) { printf( "bar\n" ); }
     227
     228void mary( S & s ) {
     229        s.f = random() % 2 ? foo : bar;                 // randomly insert a function
     230}
     231
     232void jane( S s ) {
     233        s.f( 3 );                                                               // call field f
     234}
     235```
     236
     237**test1.cfa**
     238
     239```
     240#include "test1.hfa"
     241
     242int main() {
     243        S sfoo;
     244
     245        mary( sfoo );                                                   // initialize field f
     246
     247        try {
     248                jane( sfoo );                                           // calls sfoo.f in jane
     249        } catch( ... ) {}                                               // cannot statically check correct handler is present
     250}
     251```
     252
     253
     254## `throws` Clause
     255
     256A `throws` clause can be added to a regular function to indicate alternate outcomes.
     257
     258```
     259int foo(...) throws( int ex( int ), float ex( int ), char ex( double, double ) );
     260```
     261
     262The `throws` clause is *not* part of the function type, and hence, is *not* used for overloading.
     263Functions with a `throws` clause are handled by a separate type-checking pass, which examines the static call-structure to determine if calls are nested directly or indirectly within guarded blocks with matching catch clauses, e.g.:
     264
     265```
     266int bar( int i ) throws( int fixup( int ) ); // might call fixup directly or indirectly
     267
     268void foo(...) {
     269        ... i = bar( 3 ); ...                   // call statically nested within handler for fixup in baz
     270}
     271void baz() {
     272        try {
     273                foo(...);
     274        } catch( int fixup( int ) ) {                   // handler for bar
     275                ... return 42;
     276        }
     277}
     278```
     279
     280If this type check fails because of separate compilation, e.g., `baz` is in a different translation unit, a warning is given, and a dynamic check is wrapped around the call to `bar` to verify only the specified exception functions are raised.
     281This dual approach allows all forms of reuse to exist, and is similar to checked/unchecked exceptions in Java.
     282
     283
     284## Polymorphism Extension
     285
     286A new kind of polymorphic parameter is introduced, `throws`, listing the possible empty set of exceptions (alternate outcomes) that can be raised exception, but a possibly empty set of exceptions.
     287A set of exceptions can be used in an exception signature in the same way a single exception can be and are traced from callee to caller in the same way a single exception is.
     288They do not interact with throws or catches as those work on concrete types.
     289At the top and bottom of the polymorphic call stack concrete functions throw and catch the exception, passing through the polymorphic section.
     290
     291```
     292forall( T, [N], throws E )
     293        void apply( void op(T &) throws(E), array(T, N) & data ) throws(E) {
     294                for ( i; N ) op( data[i] );
     295}
     296```
     297Given the concrete usage:
     298
     299```
     300void op( int ) throws( void WWW( int ), Y, Z ) { ... }
     301array( int, 10 ) arr;
     302try {
     303         apply( op, arr ); // op(... ) => if (... ) WWW(...)
     304} catch( void WWW( int ) ) { ... // apply can raise from op
     305} catch( exception Y ) { ... // apply can raise from op
     306} catch( exception Z ) { ... // apply can raise from op
     307}
     308```
     309
     310The call to `apply` composes a list of all potential exceptions thrown by arguments.
     311
     312Imagine we passed around a set description (a list of type identities for each exception in the set) and at the edges of the polymorphic space we check for an unknown exception type against these sets.
     313The static checks should ensure that every exception falls into one set;
     314in fact if there is only one knows set we know it must belong to it.
     315Then we wrap it up with a little marker saying which set it is part of and which one in the set it is.
     316That gets passed through as an opaque exception until we return to monomorphic code, then it is unpacked and passed normally.
     317
     318
     319## More Examples
     320
     321```
    208322enum FloatExceptions ! { Invalid, ZeroDiv, Overflow, Underflow, Inexact };
    209323exception double arithmetic( double op1, double op2, int retcode ) {
     
    215329}
    216330
    217 void xxx() {
     331void foo() {
    218332        double x = 3.5;
    219333        try {
    220334                x = x / 0.0;
    221         } catch( arithmetic( double op1, double op2, int retcode ) ) {
    222                 if ( retcode == FloatExceptions.ZeroDiv ) ...
     335        } catch( double arithmetic( double op1, double op2, int retcode ) ) {
     336                if ( retcode == FloatExceptions.ZeroDiv ) ...   // analyze retcode
    223337        }
    224338
     
    226340        try {
    227341                i = i / 0;
    228         } catch( arithmetic( int op1, int op2, int retcode ) ) {
    229                 if ( retcode == IntExceptions.ZeroDiv ) ...
    230         }
    231 }
    232 ```
     342        } catch( double arithmetic( int op1, int op2, int retcode ) ) {
     343                if ( retcode == IntExceptions.ZeroDiv ) ...             // analyze retcode
     344        }
     345}
     346```
Note: See TracChangeset for help on using the changeset viewer.