Changeset 30901eb6 for doc/proposals
- Timestamp:
- May 20, 2026, 12:27:15 PM (20 hours ago)
- Branches:
- master
- Children:
- a9049dd
- Parents:
- cea0d0c
- File:
-
- 1 edited
-
doc/proposals/exceptions-pab.md (modified) (10 diffs)
Legend:
- Unmodified
- Added
- Removed
-
doc/proposals/exceptions-pab.md
rcea0d0c r30901eb6 59 59 60 60 ``` 61 exception int dual( int, double ) dual; // Dual, return result or exit handler block, no initial stack unwinding 61 exception int dual( int, double ) dual; // Dual, return result or exit handler block, 62 // no initial stack unwinding 62 63 ``` 63 64 … … 66 67 67 68 ``` 68 } catch( int fix( int i, float j ) ) { // stack not unwound, i and j accessible,must return69 return 42; // implies return to fix call, not return of nestedfunction69 } 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 70 71 break; // syntax error => must return 71 // fallthrough => syntax error72 // fallthrough => must return 72 73 } 73 74 ``` … … 78 79 ``` 79 80 } catch( void recover( int i ) ) { // stack unwound, cannot return 80 break; // impliesexit catch routine81 return; // impliesreturn from lexical function82 // fallthrough => impliesbreak81 break; // => exit catch routine 82 return; // => return from lexical function 83 // fallthrough => => break 83 84 } 84 85 ``` … … 88 89 ``` 89 90 } catch( int dual( int i, float j ) ) { // stack not unwound, can return 90 return 42; // impliesreturn to fix call, not return of nested function91 break; // impliesexit catch body92 // fallthrough => impliesbreak91 return 42; // => return to fix call, not return of nested function 92 break; // => exit catch body 93 // fallthrough => break 93 94 } 94 95 ``` … … 96 97 In all cases, a handler can raise another exception. 97 98 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∀/μ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 found107 // return default correction or abort or raise another exception108 // 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 found116 // abort or raise another exception117 // 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 fixup137 }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 150 99 Resumption example: 151 100 152 101 ``` 153 exception int fix( int i, float f ); 154 102 exception int fix( int, float ); 155 103 void foo() { 156 104 try { 157 105 for () { 158 if ( ... ) x = fix( 3, 5.4 );106 ... if ( ... ) x = fix( 3, 5.4 ); ... 159 107 } 160 } catch( int fix( int i, float j) ) {108 } catch( int fix( int i, float f ) ) { 161 109 ... return 42; // fix up problem and return to raise 162 110 } … … 168 116 ``` 169 117 exception void end_of_file( ifstream is ); 170 171 118 void foo() { 172 119 int i; 173 120 try { 174 121 for () { 175 sin | i; // internally, does a call end_of_file( is ), which implicitly throwsexception122 sin | i; // internally, calls end_of_file( is ) to raise exception 176 123 sout | i; 177 124 } … … 185 132 186 133 ``` 187 exception int dual( int i, float f ) dual; 188 134 exception int dual( int i, float ) dual; 189 135 void baz() { 190 136 try { … … 192 138 if ( ... ) x = dual( 3, 5.4 ); 193 139 } 194 } catch( int dual( int i, float j ) ) {140 } catch( int dual( int i, float f ) ) { // stack not unwound 195 141 if ( ... ) return 42; // return to raise calls 196 142 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 151 An exception function can have a static body, which defines the action if the exception is not caught. 152 If no body is specified, there are default actions added. 153 This matches with `defaultResume` and `defaultTerminate` in current C∀/μC++. 154 Here a parameter name is necessary to access the raise argument(s). 155 156 Resumption default: 157 158 ``` 159 exception 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 165 Termination default: 166 167 ``` 168 exception 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 174 Dual default is the same as termination default, as having a default correction action is unlikely. 175 176 177 ## Polymorphism 178 179 A catch clause can be polymorphic using the RTTI information in a mangled name. 180 181 ``` 182 forall( T | { T ?+?( T, T ); } ) 183 exception 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 195 Matching 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 ``` 208 typedef struct S { 209 void (*f)( int ); // general function pointer 210 } S; 211 212 void foo( int ) throws( E ); // external 213 void bar( int ) throws( F ); 214 215 void mary( S & s ); // external 216 void jane( S s ); 217 ``` 218 219 **test2.cfa** 220 221 ``` 222 #include "test1.hfa" 223 #include "stdlib.h" 224 225 void foo( int ) { printf( "foo\n" ); } 226 void bar( int ) { printf( "bar\n" ); } 227 228 void mary( S & s ) { 229 s.f = random() % 2 ? foo : bar; // randomly insert a function 230 } 231 232 void jane( S s ) { 233 s.f( 3 ); // call field f 234 } 235 ``` 236 237 **test1.cfa** 238 239 ``` 240 #include "test1.hfa" 241 242 int 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 256 A `throws` clause can be added to a regular function to indicate alternate outcomes. 257 258 ``` 259 int foo(...) throws( int ex( int ), float ex( int ), char ex( double, double ) ); 260 ``` 261 262 The `throws` clause is *not* part of the function type, and hence, is *not* used for overloading. 263 Functions 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 ``` 266 int bar( int i ) throws( int fixup( int ) ); // might call fixup directly or indirectly 267 268 void foo(...) { 269 ... i = bar( 3 ); ... // call statically nested within handler for fixup in baz 270 } 271 void baz() { 272 try { 273 foo(...); 274 } catch( int fixup( int ) ) { // handler for bar 275 ... return 42; 276 } 277 } 278 ``` 279 280 If 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. 281 This dual approach allows all forms of reuse to exist, and is similar to checked/unchecked exceptions in Java. 282 283 284 ## Polymorphism Extension 285 286 A 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. 287 A 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. 288 They do not interact with throws or catches as those work on concrete types. 289 At the top and bottom of the polymorphic call stack concrete functions throw and catch the exception, passing through the polymorphic section. 290 291 ``` 292 forall( 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 ``` 297 Given the concrete usage: 298 299 ``` 300 void op( int ) throws( void WWW( int ), Y, Z ) { ... } 301 array( int, 10 ) arr; 302 try { 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 310 The call to `apply` composes a list of all potential exceptions thrown by arguments. 311 312 Imagine 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. 313 The static checks should ensure that every exception falls into one set; 314 in fact if there is only one knows set we know it must belong to it. 315 Then we wrap it up with a little marker saying which set it is part of and which one in the set it is. 316 That 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 ``` 208 322 enum FloatExceptions ! { Invalid, ZeroDiv, Overflow, Underflow, Inexact }; 209 323 exception double arithmetic( double op1, double op2, int retcode ) { … … 215 329 } 216 330 217 void xxx() {331 void foo() { 218 332 double x = 3.5; 219 333 try { 220 334 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 223 337 } 224 338 … … 226 340 try { 227 341 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.