asai icon indicating copy to clipboard operation
asai copied to clipboard

Changing the message of a structured diagnostic

Open mikeshulman opened this issue 1 year ago • 2 comments

Suppose I'm using StructuredReporter and I want to catch a fatal error, change the message code, and re-raise it. I would naively expect the following to work:

Reporter.try_with (fun () -> fatal Original_message)
  ~fatal:(fun d -> fatal_diagnostic { d with message = New_message })

However, this doesn't work, because the original call to fatal has already invoked get_text on Original_message and stored the result in d.explanation, and that's what gets displayed to the user. This is unintuitive to me and was hard to track down.

I feel like in general if an interface includes the definition of a type as a record, then it should be valid to define new elements of that type as records. If there are invariants that should be maintained relating the fields, or if some fields are memoized computations from other fields, then the fact that the type is a record shouldn't be exposed in the interface. Of course a change like that would be breaking. A partial solution would be to supply a function like with_message that changes the message and also recomputes the explanation:

let with_message { severity; message = _; backtrace; explanation; extra_remarks } message = 
  let explanation = locate_opt explanation.loc (Code.default_text message) in
  { severity; message; backtrace; explanation; extra_remarks }

With a warning in the documentation that this should be used instead of manually setting the message field.

mikeshulman avatar Nov 23 '24 19:11 mikeshulman

As we are breaking the interface with #184, maybe we should indeed hide the record type from the interface. And maybe the helper function could be map_message that takes the old message. Maybe the map_message should take an optional argument for keeping thee current explanation.

favonia avatar Dec 12 '24 14:12 favonia

Sounds good!

mikeshulman avatar Dec 12 '24 17:12 mikeshulman