racket / rhombus

Rhombus programming language
Other
332 stars 59 forks source link

`error` and `error.message` #550

Closed mflatt closed 1 week ago

mflatt commented 2 weeks ago

This change is backward-incompatible, because it makes the who argument to error a keyword argument with ~who, and it renames Exn.Fail.Contract to Exn.Fail.Annot.

The main change is to add support for message details and clauses. Clause constructors like error.val and error.annot build the label–value pairs that go at the end of an error message. The error.message function can be used to just construct the message text that error puts into an exception.

This commit does not change the non-message content of exceptions, but the clause protocol is meant to accommodate and adapt to future changes.

Examples from the docs:

> error("oops")
oops
> error(~who: #'me, "oops")
me: oops
> error(~who: #'me,
        "oops",
        ~details: ["something has gone wrong;",
                   "see the manual for more information"])
me: oops;
 something has gone wrong;
 see the manual for more information
> error(~who: #'me,
        ~exn: Exn.Fail.Annot,
        error.annot_msg("fruit"),
        error.annot("Tropical"),
        error.val(~label: "fruit", #'Apple))
me: fruit does not satisfy annotation
  annotation: Tropical
  fruit: #’Apple
> error(~who: #'me,
        "mismatch between fruit and vegetable",
        error.val(~label: "fruit", [#'Apple, 0]),
        error.val(~label: "vegetable", [#'Lettuce, -1]))
me: mismatch between fruit and vegetable
  fruit: [#’Apple, 0]
  vegetable: [#’Lettuce, -1]

I expected to have various helper functions like annot_error to raise a standard "does not satisfy annotation" error, for example. But in changing existing error messages, I concluded that it's better to have functions for constructing arguments to error, such as the error.annot_msg() for constructing the main "does not satisfy annotation" message text. A drawback of this approach is that error.annot_msg() as an argument to error needs to be paired with ~exn: Exn.Fail.Annot as well as (usually) error.annot and error.val clause constructions. But this approach still ended up being more comfortable than an annot_error function, because explicitly raised annotation errors often include small variations on the normal message (otherwise :: would be used in the first place), and it seemed easiest to just remember and use the error protocol.

mflatt commented 1 week ago

Merging because this seems like progress, at least — but as usual, that doesn't mean it's the final word.