My bug or Racket's? Macro + srcdoc = confusion

已查看 17 次
跳至第一个未读帖子

Stephen Foster

未读,
2020年1月23日 16:00:262020/1/23
收件人 Racket Users
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? 



回复全部
回复作者
转发
0 个新帖子