[racket-dev] error-message overhaul

From: Matthew Flatt (mflatt at cs.utah.edu)
Date: Fri May 25 17:09:22 EDT 2012

I've pushed a first cut at overhauling error messages from `racket/base'.

The old error message convention tried to keep everything on one line,
but that sometimes got long and contorted. The old format didn't work
at all for contract errors, for example, and so I've more or less
adopted the contract-error style everywhere.

The new error message convention encourages a short message on the
first line, and then more information in separate "<field>: <detail>"
lines. A multi-line <detail> starts out on its old line and is indented
by an extra space. Where the old format would inline a value or name in
the message, the new format puts the value or name in its own field or
line. A source locations is similarly moved to a field, instead of
prefixing the error message. Finally, instead of a separate " ===
context ===" section for a backtrace, it's added by the default
exception printer as a "context" field.

There's a new `raise-argument-error' function to replace existing uses
of `raise-type-error'. For example

  (raise-type-error 'eat "integer" n)

should change to

  (raise-argument-error 'eat "integer?" n)

A new `raise-arguments-error' (plural) function supports simple error
messages that have any number of fields, where each field's value is
`print'ed into the message:

  (raise-arguments-error 'eat 
                         "fish is smaller than its given meal"
                         "fish size" 12
                         "given meal size" 13)

There's more room for error-formatting support, and I have some ideas
--- but also I have plenty still to do for just with the the simple
ones.

The old error-message functions remain and format messages as before
(although, notably, `raise-syntax-error' uses the new conventions).
Conversion to the new format mostly requires changing all existing call
sites.

You may wonder whether exception records should provide field and
detail information directly, instead of only encoded in the
error-message string. As appealing as that sounds, retrofitting
existing code to use a new little DSL for error content looks to me
like far too much work. I have some ideas on how to get much of the
benefit indirectly, though (so that DrRacket can show images when they
appear in an error, for example, instead of printing "#<image>"). For
now, if you convert error messages to `raise-argument-error' and
`raise-arguments-error', then you're certainly future-proof for that
step. Even if you build messages by hand, I expect that format strings
will work transparently. More soon...


Some examples:

----------------------------------------
> (+ 1 'a)
+: contract violation
  expected: number?
  given: 'a
  argument position: 2nd
  other arguments:
    1
  context:
   /Users/mflatt/proj/plt/collects/racket/private/misc.rkt:87:7
> (add1)
application: wrong number of arguments
  procedure: add1
  expected number of arguments: 1
  given number of arguments: 0
  context:
   /Users/mflatt/proj/plt/collects/racket/private/misc.rkt:87:7
> (lambda x)
lambda: bad syntax
  in: (lambda x)
  source:
   readline-input::16
  context:
   /Users/mflatt/proj/plt/collects/racket/private/misc.rkt:87:7
> .
read: illegal use of "."
  source:
   readline-input::27
  context:
   /Users/mflatt/proj/plt/collects/readline/pread.rkt:230:0: read-cmdline-syntax
   /Users/mflatt/proj/plt/collects/racket/private/misc.rkt:87:7
> (collection-path "nonesuch")
collection-path: collection not found
  collection: "nonesuch"
  in collection directories:
   /Users/mflatt/Library/Racket/5.3.0.9/collects
   /Users/mflatt/proj/plt/collects
  context:
   /Users/mflatt/proj/plt/collects/racket/private/pre-base.rkt:111:53: fail
   /Users/mflatt/proj/plt/collects/racket/private/misc.rkt:87:7
----------------------------------------

For comparison, here are the same examples with the old message style:

----------------------------------------
> (+ 1 'a)
+: expects type <number> as 2nd argument, given: 'a; other arguments were: 1

 === context ===
/Applications/Racket v5.2.1/collects/racket/private/misc.rkt:87:7

> (add1)
add1: expects 1 argument, given 0

 === context ===
/Applications/Racket v5.2.1/collects/racket/private/misc.rkt:87:7

> (lambda x)
readline-input::16: lambda: bad syntax in: (lambda x)

 === context ===
/Applications/Racket v5.2.1/collects/racket/private/misc.rkt:87:7

> .
readline-input::27: read: illegal use of "."

 === context ===
/Applications/Racket v5.2.1/collects/readline/pread.rkt:230:0: read-cmdline-syntax
/Applications/Racket v5.2.1/collects/racket/private/misc.rkt:87:7

> (collection-path "nonesuch")
collection-path: collection not found: "nonesuch" in any of: (#<path:/Users/mflatt/Library/Racket/5.2.1/collects> #<path:/Applications/Racket v5.2.1/collects>)

 === context ===
/Applications/Racket v5.2.1/collects/racket/private/pre-base.rkt:111:53: fail
/Applications/Racket v5.2.1/collects/racket/private/misc.rkt:87:7

----------------------------------------


Posted on the dev mailing list.