Scribble/srcdoc's for-doc with submod issue

Using scribble/srcdoc with at-exp reader allows you to write module documentation within the provide form ensuring all the provided symbols are somehow (preferably properly) documented. In certain situations, you want to include the actual values of the symbols you provide or values used for computation when the symbol is a procedure or syntax form. I use this approach in uni-table documentation[1] to make sure the documentation lists all valid color names and other things as they are used in the module itself.

Here's a minimal example of a module for-doc-test1.rkt using scribble/srcdoc for providing the documentation of provided procedure:

#lang at-exp racket/base

(require scribble/srcdoc
         (for-doc scribble/manual
                  racket/base
                  (submod ".." my-constants)
                  )
         racket/contract)

(provide
 (proc-doc
  return-a-number
  (-> number?)
  @{

    Returns @racket[a-number]: @racket[#,a-number].

    }))

(module my-constants racket/base
  (provide a-number)
  (define a-number 1) )

(require 'my-constants)

(define (return-a-number)
  a-number)

In order to render the documentation, a small for-doc-test1.scrbl file may read:

#lang scribble/manual

@require[scribble/extract]

@title{for-doc test 1}

@defmodule[for-doc-test1]
@include-extracted["for-doc-test1.rkt"]

Creating the documentation is then just a matter of running scribble for-doc-test1.scrbl and it generates a nice HTML documentation which properly states "Returns a-number: 1." in the procedure description.
If you use this module in a program it works like charm. Take the following ```for-doc-test1-usage.rkt" as an example:

#lang at-exp racket/base

(require  "for-doc-test1.rkt")

(define (test-a-number)
  (displayln (return-a-number)))

(module+ main
  (test-a-number))

This program can be run using racket for-doc-test1-usage.rkt, it can be compiled using raco make for-doc-test1-usage.rkt and no problems show up.
However if the usage program is a module that is intended to be used elsewhere and scribble/srcdoc provides are present, something strange happens. The updated usage example is as follows:

#lang at-exp racket/base

(require scribble/srcdoc
         (for-doc scribble/manual
                  racket/base)
         "for-doc-test1.rkt"
         racket/contract)

(provide
 (proc-doc
  test-a-number
  (-> void?)
  @{

    Prints a-number.

    }))

(define (test-a-number)
  (displayln (return-a-number)))

(module+ main
  (test-a-number))

Trying to run this program yields:

car: contract violation
  expected: pair?
  given: #<path:/home/joe/Projects/Programming/uni-table/for-doc/..>
  context...:
   /usr/share/racket/collects/racket/require-transform.rkt:190:2: convert-relative-module-path
   /usr/share/racket/collects/racket/private/reqprov.rkt:72:2: check-lib-form
   /usr/share/racket/collects/racket/private/reqprov.rkt:80:13: t
   /usr/share/racket/collects/racket/require-transform.rkt:266:2: expand-import
   /usr/share/racket/collects/racket/private/reqprov.rkt:607:22
   /usr/share/racket/collects/racket/private/reqprov.rkt:603:5
   /usr/share/racket/collects/racket/require-transform.rkt:266:2: expand-import
   [repeats 1 more time]
   /usr/share/racket/collects/racket/private/reqprov.rkt:285:21: try-next
   /usr/share/racket/collects/racket/private/reqprov.rkt:256:2
   /usr/share/racket/collects/syntax/wrap-modbeg.rkt:46:4

Of course, trying to compile it produces the same result. I found a rather simple workaround, just remove the submod from the for-doc form and use begin-for-doc. The updated module then looks like this:

#lang at-exp racket/base

(require scribble/srcdoc
         (for-doc scribble/manual
                  racket/base)
         racket/contract)

(begin-for-doc
  (require (submod ".." my-constants)))

(provide
 (proc-doc
  return-a-number
  (-> number?)
  @{

    Returns @racket[a-number]: @racket[#,a-number].

    }))

(module my-constants racket/base
  (provide a-number)
  (define a-number 1))

(require 'my-constants)

(define (return-a-number)
  a-number)

Now it compiles and runs fine - that's what I actually used in affected module of uni-table[2].

The question is: is there something missing in my understanding of how require subforms (for-doc especially) work or is it a bug in Racket?

[1] https://docs.racket-lang.org/uni-table/Styles.html#(part._.S.G.R_.Styles)
[2] https://gitlab.com/racketeer/uni-table/-/blob/master/private/sgr-style.rkt

There are two layers of bugs here: problems with relative-in, and a problem with the relative-in that for-doc eventually builds. I'll push repairs for both.

2 Likes