I ran into a series of confusing Racket things today. Although I've fixed my code, I'm looking for insight. Clearly there are things I don't understand about
scribble/srcdoc, and maybe macros in general.I have a macro called define/contract/doc. It lets me do things like this:
(define/contract/doc (test #:number [number 75.2])
(->i ()
(#:number [number number?])
[returns any/c])
@{Returns what you pass in for #:number. Defaults to 75.2}
number)
It has worked fine for almost a year. But today, I discovered that when I define a function with a default parameter value that happens to include a decimal number (i.e. 75.2 above), I get a cryptic error when requiring in the srcdoc submodule.
I'll post the macro definition in a moment, but first let me mention that on the Windows machine where my co-worker first discovered this bug, the error was presented without a stacktrace. This was it:
-: contract violation
expected: number?
given: #f
argument position: 1st
other arguments...:
2
context...:
Puzzle #1. I don't usually develop on Windows, so can someone tell me if there's something special I'm supposed to do to get more context to print out? Note: Even in the Racket REPL, I couldn't get a backtrace. Using errortrace changed nothing. I'm on Racket 7.4, btw.
It was only after poking, prodding, and simplifying for an hour that we extracted a minimal reproduction of the bug and figured out that the decimal number was causing the problem. The lack of stacktrace led to a lot of guesswork.
I would have been at a loss, but I happened to switch to my Mac, where (luckily!) the error printed with a better stacktrace:
-: contract violation
expected: number?
given: #f
argument position: 1st
other arguments...:
2
context...:
/Applications/Racket v7.4/share/pkgs/scribble-lib/scribble/racket.rkt:227:2: typeset-atom
/Applications/Racket v7.4/share/pkgs/scribble-lib/scribble/racket.rkt:471:8
/Applications/Racket v7.4/share/pkgs/scribble-lib/scribble/racket.rkt:322:2: gen-typeset
/Applications/Racket v7.4/share/pkgs/scribble-lib/scribble/private/manual-proc.rkt:549:6
/Applications/Racket v7.4/collects/racket/private/map.rkt:259:4: loop
/Applications/Racket v7.4/collects/racket/list.rkt:586:2: append-map
/Applications/Racket v7.4/share/pkgs/scribble-lib/scribble/private/manual-proc.rkt:316:2: do-one
/Applications/Racket v7.4/collects/racket/private/map.rkt:259:4: loop
/Applications/Racket v7.4/collects/racket/list.rkt:586:2: append-map
/Applications/Racket v7.4/share/pkgs/scribble-lib/scribble/private/manual-proc.rkt:212:0: *defproc13
(submod "/Users/thoughtstem/Dev/test/test.rkt" srcdoc): [running body]
temp37_0
for-loop
run-module-instance!125
perform-require!78
for-loop
...
This informed me that the problem was related to typeset-atom, which in turn helped me to make slightly more educated guesses about how to fix the problem.
Puzzle #2. If you save the snippet below into a file called test.rkt, you can reproduce the error and/or my fixes with something like racket -e "(require (submod \"./test.rkt\" srcdoc))". Why does the datum->syntax fix the problem? After skimming the srcdoc code, I suspect some line-number information needs to be present on the syntax being typeset.... Why this only matters for numbers with decimals is still a mystery.
#lang at-exp racket
(require (for-syntax syntax/parse))
(define-syntax (define/contract/doc stx)
(define defaults
'(75) ;Works
#;
'(75.2) ;Breaks
#;
(datum->syntax stx '(75.2) stx) ;Works, and is essentially the way we fixed it.
#;
(datum->syntax stx '(75.2)) ;Breaks, and is roughly what we had in production -- without the hardcoded 75.2, obviously.
)
(define ret
(syntax-parse stx
([_ (f-name args ... ) contract doc body ...]
#`(begin
(require scribble/srcdoc)
(provide (proc-doc
f-name
contract
#,defaults
doc))
(define (f-name args ...)
body ...)))))
ret)
(define/contract/doc (test #:number [number 75.2])
(->i ()
(#:number [number number?])
[returns any/c])
@{Returns what you pass in for #:number. Defaults to 75.2}
number)
Puzzle #3. What's the appropriate code to "blame" here? Is this some kind of noob mistake on my part (i.e. advanced Racket macrologists would have known to use datum->syntax appropriately and never would have run into the problem in the first place)? Is this a bug in srcdoc? Is it both?
(Bonus) Puzzle #4. How would you have debugged this? Was there anything I could have done to localize the problem more quickly?