Skip to content
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

Merged
merged 14 commits into from
May 3, 2024
Merged

Conversation

AltGr
Copy link
Contributor

@AltGr AltGr commented Apr 26, 2024

  • 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

BONUS: this PR also includes a tweak to have message adapt to the terminal
width, and fixes to the cheat-sheets.

AltGr added 11 commits April 25, 2024 14:20
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.
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).
@AltGr
Copy link
Contributor Author

AltGr commented Apr 30, 2024

Added 4 patches dedicated to actually having better positions, before we need to report them

  • the first may make the diff quite bigger, but it's just an addition of position marks to the operators in the EAppOp AST node
  • some positions were otherwise lost in translation to lcalc, which took a bit to debug (I first suspected optimisations) ; all tests that used explicit interpret instead of test-scope because of this are now fixed and positions in error reporting are now consistent with the different backends (this is verified by make testsuite)

Copy link
Contributor

@denismerigoux denismerigoux left a 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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is very fancy

Copy link
Contributor Author

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... 🤷🏿

@@ -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 ->
Copy link
Contributor

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?

Copy link
Contributor Author

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 }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good

Comment on lines +753 to +756
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'));
Copy link
Contributor

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 ?

Copy link
Contributor Author

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 ?

Comment on lines 846 to 850
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)
Copy link
Contributor

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
Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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?

Copy link
Contributor Author

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.

Comment on lines -52 to -53
└┬ `UncomparableDurations` exception management
└─ `>=` operator
Copy link
Contributor

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?

Copy link
Contributor Author

@AltGr AltGr May 2, 2024

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

tests/default/bad/empty.catala_en Outdated Show resolved Hide resolved
tests/func/bad/bad_func.catala_en Outdated Show resolved Hide resolved
AltGr added 2 commits May 2, 2024 16:30
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.
@AltGr
Copy link
Contributor Author

AltGr commented May 2, 2024

All should be addressed with the 2 added patches

@denismerigoux denismerigoux merged commit 0f425dc into CatalaLang:master May 3, 2024
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants