-
Notifications
You must be signed in to change notification settings - Fork 77
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Unify runtime error handling #611
Conversation
It's not an error! It happens in the normal control flow :) This is to distinguish from the other runtime exceptions which are actually fatal errors.
- Clearly distinguish Exceptions from Errors. The only catchable exception available in our AST is `EmptyError`, so the corresponding nodes are made less generic, and a node `FatalError` is added - Runtime errors are defined as a specific type in the OCaml runtime, with a carrier exception and printing functions. These are used throughout, and consistently by the interpreter. They always carry a position, that can be converted to be printed with the fancy compiler location printer, or in a simpler way from the backends. - All operators that might be subject to an error take a position as argument, in order to print an informative message without relying on backtraces from the backend
in order to match the exceptions in OCaml
This puts runtime exception info on par with what we had in the interpreter, and repairs the regression on the interpreter which no longer had them.
when no scope was specified
Positions within the Default terms are specially important since they can come from separate definitions in the source (before this, we would be falling back to the single declaration).
Added 4 patches dedicated to actually having better positions, before we need to report them
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks you @AltGr, this is a great improvement ! Hopefully it paves the way to enabling tracing in OCaml-compiled modules so that we can have a full trace across compiled modules in the interpreter.
@@ -180,6 +180,42 @@ let process_out ?check_exit cmd args = | |||
assert false | |||
with End_of_file -> Buffer.contents buf | |||
|
|||
(* SIDE EFFECT AT MODULE LOAD: sets up a signal handler on SIGWINCH (window |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is very fancy
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well I had the code lying around, so... 🤷🏿
compiler/desugared/from_surface.ml
Outdated
@@ -366,7 +375,7 @@ let rec translate_expr | |||
(try | |||
Runtime.date_of_numbers date.literal_date_year | |||
date.literal_date_month date.literal_date_day | |||
with Runtime.ImpossibleDate -> | |||
with Dates_calc.Dates.InvalidDate -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my mind the Catala compiler would never directly depend on Dates_calc
, to ensure some sort of abstraction of how things like dates are actually implemented. Which is why the runtime re-exported this error and why we used it here. Do you have a good rationale for tweaking this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, no indeed you have a good point, I'll ensure it's properly shadowed
| EExternal of { modname : VarName.t Mark.pos; name : string Mark.pos } | ||
|
||
type stmt = | ||
| SInnerFuncDef of { name : VarName.t Mark.pos; func : func } | ||
| SLocalDecl of { name : VarName.t Mark.pos; typ : typ } | ||
| SLocalInit of { name : VarName.t Mark.pos; typ : typ; expr : expr } | ||
| SLocalDef of { name : VarName.t Mark.pos; expr : expr; typ : typ } | ||
| STryExcept of { try_block : block; except : except; with_block : block } | ||
| SRaise of except | ||
| STryWEmpty of { try_block : block; with_block : block } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good
Message.warning "Assertion failed:@ %a" | ||
(Print.UserFacing.expr lang) | ||
(partially_evaluate_expr_for_assertion_failure_message ctx lang | ||
(Expr.skip_wrappers e')) | ||
(Expr.skip_wrappers e')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You raise a warning and then you raise a runtime error ?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, this one is a bit tricky: here we indeed want to benefit from the greater powers we have in the interpreter to print a more detailed message, and arbitration was needed between that and the unification of error handling between runtime and interpreter.
The choice I made was to print the more detailed message as a side-effect, then raise the exception to be handled in the common path ; but indeed that will make the console show [WARNING]
with the detail and then [ERROR]
with the position and the message. The alternatives, either specific error handling of this exception at this point, or somehow embedding the added information into the runtime exception, didn't seem worth it.
Maybe this would be better if we add a non-raising variant for Message.error
and use that here, though ?
compiler/shared_ast/interpreter.ml
Outdated
with Runtime.Error (err, rpos) -> | ||
Message.error | ||
~extra_pos:(List.map (fun rp -> "", Expr.runtime_to_pos rp) rpos) | ||
"Error during evaluation: %a." Format.pp_print_text | ||
(Runtime.error_message err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK this is the global handling.
@@ -32,90 +32,39 @@ scope Money: | |||
|
|||
|
|||
```catala-test-inline | |||
$ catala Interpret -s Dec | |||
[ERROR] division by zero at runtime | |||
$ catala test-scope Dec |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you do a PR in https://gitlab.adullact.net/dgfip/ir-catala to make sure we use test-scope
everywhere and no longer Interpret -s
anymore? Can you remind me the difference between the two again?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes!
Basically the purpose is to be able to run the test with various combination of interpreter options (dcalc/lcalc, exceptions, etc.), so the special syntax is purposefully less explicit than the real catala command interpret
.
See the make testuite
target; quoting clerk test --help
:
--test-flags=FLAGS [...]
Flags to pass to the catala interpreter on catala test-scope tests.
Comma-separated list. A subset may also be applied to the
compilation of modules, as needed. [...] NOTE: if this is set, all inline
tests that are not `catala test-scope` are skipped to avoid redundant
testing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error message is slightly less expressive now. Maybe DivisionByZero
could carry two positions instead of one in the accompanying position list, the second pointing specifically to the null denominator?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah indeed — my first version only carried a single position for runtime error, but I had to extend it for acceptable Conflict
exceptions; I didn't re-add the positions for operators though, at the moment it seemed superfluous.
Note, however, that we are now able to point precisely at the operator that failed, while before we had to point to the whole expression, which could for example include several divisions. But indeed, priority of operators could lead to things like 1 / 0 + 1
where underlining the /
is not as explicit as underlining the 0
.
An issue is that runtime exceptions can have multiple positions, but no associated messages at the moment.
EDIT: after discussion, pointing only to the denominator seems to actualy be the best compromise.
└┬ `UncomparableDurations` exception management | ||
└─ `>=` operator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So the runtime exceptions do not carry a law heading breadcrumb anymore. Maybe we should add law heading breadcrumbs to Runtime.position
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
They do, they do, it's already there.
The diff is misleading here, the change is that we only have the position of the operator instead of 2 positions for the 2 operands
The dependency should only go through the `Runtime` module
mostly reverting to the ones the interpreter was printing ; for the case of divisions, we choose to point to the denominator instead of the operator as it's where the only possible error (division by zero) comes from.
All should be addressed with the 2 added patches |
Clearly distinguish Exceptions from Errors. The only catchable exception
available in our AST is
EmptyError
, so the corresponding nodes are made lessgeneric, and a node
FatalError
is addedRuntime errors are defined as a specific type in the OCaml runtime, with a
carrier exception and printing functions. These are used throughout, and
consistently by the interpreter. They always carry a position, that can be
converted to be printed with the fancy compiler location printer, or in a
simpler way from the backends.
All operators that might be subject to an error take a position as argument,
in order to print an informative message without relying on backtraces from
the backend
BONUS: this PR also includes a tweak to have message adapt to the terminal
width, and fixes to the cheat-sheets.