#lang racket/base (require "struct.rkt" "basic.rkt" syntax/modcode) (provide find-racket-tag (rename-out [find-racket-tag find-scheme-tag])) (define module-info-cache (make-hasheq)) (define (module-path-index-rejoin mpi rel-to) (let-values ([(name base) (module-path-index-split mpi)]) (cond [(not name) rel-to] [(not base) mpi] [else (module-path-index-join name (module-path-index-rejoin base rel-to))]))) (define (try thunk fail-thunk) (with-handlers* ([exn:fail? (lambda (exn) (fail-thunk))]) (thunk))) (define (find-racket-tag part ri stx/binding phase-level) ;; The phase-level argument is used only when `stx/binding' ;; is an identifier. ;; ;; Note: documentation keys currently don't distinguish different ;; phase definitions of an identifier from a source module. ;; That is, there's no way to document (define x ....) differently ;; from (define-for-syntax x ...). This isn't a problem in practice, ;; because no one uses the same name for different-phase exported ;; bindings. ;; ;; Formerly, we assumed that bindings are defined as originating from some ;; module at phase 0. [Maybe it's defined at phase 1 and re-exported ;; later for phase 0 (after a require-for-template), in which case the ;; re-exporting module is the one we find.] That assumption has been ;; lifted, however; search for "GONE" below. (let ([b (cond [(identifier? stx/binding) (identifier-binding stx/binding phase-level)] [(and (list? stx/binding) (= 7 (length stx/binding))) stx/binding] [else (and (not (symbol? (car stx/binding))) (list #f (cadr stx/binding) (car stx/binding) (cadr stx/binding) (if (= 2 (length stx/binding)) 0 (caddr stx/binding)) (if (= 2 (length stx/binding)) 0 (cadddr stx/binding)) (if (= 2 (length stx/binding)) 0 (cadddr (cdr stx/binding)))))])]) (and (pair? b) (let ([seen (make-hash)] [search-key #f]) (let loop ([queue (list (list (caddr b) (cadddr b) (list-ref b 4) (list-ref b 5) (list-ref b 6)))] [rqueue null] [need-result? #t]) (cond [(null? queue) (if (null? rqueue) ;; Not documented #f (loop (reverse rqueue) null need-result?))] [else (let ([mod (list-ref (car queue) 0)] [id (list-ref (car queue) 1)] [defn-phase (list-ref (car queue) 2)] [import-phase (list-ref (car queue) 3)] [export-phase (list-ref (car queue) 4)] [queue (cdr queue)]) (let* ([rmp (module-path-index-resolve mod)] [eb (and ;; GONE: (equal? 0 export-phase) ;; look for the phase-0 export; good idea? (list (module-path-index->taglet mod) id))]) (when (and eb (not search-key)) (set! search-key eb)) (let ([v (and eb (resolve-search search-key part ri `(dep ,eb)))]) (let* ([here-result (and need-result? v (let ([v (resolve-get/tentative part ri `(form ,eb))]) (or (and v `(form ,eb)) `(def ,eb))))] [need-result? (and need-result? (not here-result))] [rmp-name (resolved-module-path-name rmp)]) ;; Even if we've found `here-result', look deeper so that we have ;; consistent `dep' results. (let ([nest-result ;; Maybe it's re-exported from this module... ;; Try a shortcut: (if (eq? rmp (and (car b) (module-path-index-resolve (car b)))) ;; Not defined through this path, so keep looking (loop queue rqueue need-result?) ;; Check parents, if we can get the source: (if (and (or (path? rmp-name) (and (list? rmp-name) (path? (car rmp-name)))) (not (hash-ref seen (cons export-phase rmp) #f))) (let ([exports (hash-ref module-info-cache rmp (lambda () (let-values ([(valss stxess) (try (lambda () ;; First, try using bytecode: (module-compiled-exports (get-module-code (if (list? rmp-name) (car rmp-name) rmp-name) #:submodule-path (if (list? rmp-name) (cdr rmp-name) '()) #:choose (lambda (src zo so) 'zo)))) (lambda () (try (lambda () ;; Bytecode not available. Declaration in the ;; current namespace? (module->exports rmp)) (lambda () (values null null)))))]) (let ([t ;; Merge the two association lists: (let loop ([base valss] [stxess stxess]) (cond [(null? stxess) base] [(assoc (caar stxess) base) => (lambda (l) (loop (cons (cons (car l) (append (cdar stxess) (cdr l))) (remq l base)) (cdr stxess)))] [else (loop (cons (car stxess) base) (cdr stxess))]))]) (hash-set! module-info-cache rmp t) t))))]) (hash-set! seen (cons export-phase rmp) #t) (let ([a (assq id (let ([a (assoc export-phase exports)]) (if a (cdr a) null)))]) (if a (loop queue (append (map (lambda (m) (if (pair? m) (list (module-path-index-rejoin (car m) mod) (list-ref m 2) defn-phase (list-ref m 1) (list-ref m 3)) (list (module-path-index-rejoin m mod) id defn-phase import-phase export-phase))) (reverse (cadr a))) rqueue) need-result?) (begin ;; A dead end may not be our fault: the files could ;; have changed in inconsistent ways. So just say #f ;; for now. #; (error 'find-racket-tag "dead end when looking for binding source: ~e" id) (loop queue rqueue need-result?))))) ;; Can't get the module source, so continue with queue: (loop queue rqueue need-result?)))]) (or here-result nest-result))))))]))))))