One challenge I realize now, having written that patch, is that there's no binding arrow because max
is not in fact provided by rhombus
(only math
is). So to make go-to-definition work right, it now has to handle the case where there's no binding location but you want to go to the definition in another file anyway.
Previously the concept has been that there may exist compound identifiers (made at least partially from two or more other pieces of syntax). These are real identifiers -- they get binding arrows, identifier-binding
knows their nominal and transitive source, and so on.
To reveal the sources of their components, they may have a syntax-property like sub-range-binders
or import-or-export-prefix-ranges
. This has covered things like struct predicates/accessors/setters, prefixed exports, and so on.
For example the struct accessor exn-message
is a real identifier. A stx prop reveals the exn
and message
source pieces. Likewise ids created by prefix-out
.
I'm not sure how to understand something like math.min
where (IIUC):
math
is real and comes from rhombus -- but...math.min
isn't realmin
isn't real even though it comes nominally from rhombus and ultimately from racket/base
I'm not claiming this is bad or wrong. I just don't understand what it even means, yet.
So I'm not sure what to propose to do about it, yet.
For now I'm reduced to pointing out the status quo techniques and wondering why they can't be used.
I think the way to understand math.min
is that it is really (math (op |.| min))
(taking a few liberties with the shrubbery syntax). So math
is real, and it's a macro that looks at its input and decides, based on parsing that input, that it wants to expand to min
(the identifier defined in racket/base
).
So there isn't really any math.min
identifier anywhere, compound or not -- that's not how that notation read
s.
This is, as you note, quite unlike how eg exn-message
works, or how dotted identifiers work in syntax-parse
, where it binds identifiers for all of the various dotted forms and they are just regular Racket identifiers.
I think min
is real, although there are two min
s -- the name after the dot and the identifier in the expanded code, which happen to have the same spelling. I think the tools we have are enough to help tools find min
appropriately, although as I mentioned some adjustments seem required.
I believe it's fine if math.min
doesn't exist, provided both math
and min
do. (In some ways that's cleaner than the composite math.min
and needing stx props to recover the pieces.)
The interpretation that math
and min
are two distinct identifiers should be OK provided:
-
check-syntax draws arrows for both (currently it doesn't for
min
, making it "unreal" for various purposes). -
The syntax at the binding/definition end of the arrow can be given to
identifier-binding
and it returns meaningful "from-xxx" and "nominal-from-xxx" values. (Which can be used when check-syntax-ing other sources, transitively... until the ultimate definition site is reached.)
Whenever @robby might have time to help figure out 1, I could see whether 2 is working out, too. (Which might lead back to the hitch that min
isn't exported in the usual/old ways... but one step at a time, I guess.)
@samth offered some code similar to this; here's a variation I wanted to ask about:
#lang racket
(define-syntax (scope stx)
(syntax-case stx (math string)
[(_ math id) (with-syntax ([i (syntax-e #'id)])
#'(let () (local-require (only-in racket/math i)) i))]))
(scope math pi)
With a definition like scope
, I don't see how check syntax is going to draw an arrow without some changes. Why is i
put into the let and not id
? There is also no place to put an arrow that goes to racket/math
(as I suppose the actual occurrence of racket/math
is elsewhere and this one is just math
). Do we need an actual arrow, or just an annotation that pi
came from the racket/math
library?
I think the question of where to put the other end of the arrow is the important one for @greghendershott's point 1. The patch I linked to gets some information so that DrRacket knows that min
comes from racket/math
and can point to the documentation (but without the arrow it won't jump to the definition). Strangely that doesn't work for pi
though.
As to why i
and not id
, it's because otherwise there are extra scopes on id
and the macro doesn't work.
Here's probably a better example to start from. This highlights/links to documentation for both sqr
and pi
, and shows arrows for math
, but doesn't show arrows or offer to go to the definition of either sqr
or pi
. I think if that can be fixed then Rhombus can be adjusted pretty easily.
#lang racket
(define-syntax math (syntax-rules ()))
(require racket/math)
(define-for-syntax tbl (hash 'pi #'pi
'sqr #'sqr))
(define-syntax (scope stx)
(syntax-case stx ()
[(_ mth id)
(free-identifier=? #'mth #'math)
(syntax-property (hash-ref tbl (syntax-e #'id))
'disappeared-use
(cons
(datum->syntax #'here
'math
#'mth
#'mth)
(datum->syntax #'here
(syntax-e #'id)
#'id
#'id)))]))
(provide scope math)
#lang racket/base
(require "a.rkt")
(scope math sqr)
(scope math pi)
This change causes DrRacket to offer a "jump to definition" or "open defining file" but there's still no arrow. I'm actually not sure where an arrow would go!
This passes the test suite and I don't see why this wouldn't be a good idea just in general, so if I can think of a good way to write a test case for it, I'll push it.
diff --git a/drracket-tool-text-lib/drracket/private/syncheck/traversals.rkt b/drracket-tool-text-lib/drracket/private/syncheck/traversals.rkt
index 26bbfbda..2c1448f7 100644
--- a/drracket-tool-text-lib/drracket/private/syncheck/traversals.rkt
+++ b/drracket-tool-text-lib/drracket/private/syncheck/traversals.rkt
@@ -968,6 +968,16 @@
(define req-phase+space-shift (list-ref req-path/pr 4))
(define require-hash-key (list req-phase-level mods))
(define require-ht (hash-ref phase-to-requires require-hash-key #f))
+ (when id
+ (define-values (filename submods)
+ (get-require-filename source-req-path user-namespace user-directory))
+ (when filename
+ (add-jump-to-definition
+ var
+ source-id
+ filename
+ submods
+ req-phase+space-shift)))
(when require-ht
(define req-binder+modss (hash-ref require-ht req-path #f))
(when req-binder+modss
@@ -979,16 +989,6 @@
(define match/prefix
(id/require-match (syntax->datum var) id require-context))
(when match/prefix
- (when id
- (define-values (filename submods)
- (get-require-filename source-req-path user-namespace user-directory))
- (when filename
- (add-jump-to-definition
- var
- source-id
- filename
- submods
- req-phase+space-shift)))
(define prefix-length
(cond
[(and (identifier? match/prefix)
How well does this work for the rhombus programs under discussion (perhaps with the disappeared-use change I posted earlier)?
I'm not sure exactly which change you have in mind (I scrolled back and didn't something simple to try, sorry!) but this program:
#lang rhombus
math.max(1, 2)
does not have a link to max
, even with the change. Perhaps the disappeared use that you have in mind will fix that, tho?
Yes, that seems to work, as far as I can tell.
That sounds like good progress!
What does identifier-binding
say for each of math
and max
-- the binding end of each of those arrows?
When I tried before with Sam's scope
example pair of files via:
#lang racket
(define mp '(file "/home/greg/src/racket/examples/b.rkt"))
(namespace-require mp)
(current-namespace (module->namespace mp))
(identifier-binding #'math)
(identifier-binding #'sqr)
I got
math
:#f
sqr
:'(#<module-path-index:racket/math> sqr #<module-path-index:racket> sqr 0 0 0)
The problem: I use the "nominal-from" information from the binding end of the arrow, to learn the "proximate" export source.
- For
math
, it's missing. - For
sqr
, it's wrong^1. The proximate source is "a.rkt", which in turn imports it fromracket
-- i.e. the "a.rkt" step of the chain is elided.
^1: I mean, strictly speaking it's not wrong, because the scope
macro isn't an import and "a.rkt" where it's defined doesn't export anything. But that's the root difficulty, from my POV: Racket has had a notion of module exports and imports -- and related supporting functions like identifier-binding
and module->exports
. If something like scope
or rhombus doesn't use or extend that, it's going to be opaque to tools.
It seems to only be partly working for me. When I have
#lang rhombus
math.pi
I get the usual "Open Defining File" or "Jump to Definition (in other file)" options. The defining file appears to be rhombus/private/math
. But with math.max
or math.max(1,2)
I don't get those options, and instead just the "View documentation for max
from racket/base" (indicating that it knows the binding but isn't showing anything about the definition).
Here I think there are two issues.
- The
scope
macro is somewhat different from howrhombus
works, because it'smath
itself that's the macro, so things are a little better there. - It's not obvious what we want to do with
sqr
in that case -- there isn't a definition of it ina.rkt
so maybe we want toracket/math
.
But also I'm a bit confused about what's going on in your example. I think your code is using the racket
identifiers, which is why math
is not bound. It's unfortunately not obvious to me how to make that test work for rhombus properly.
I was under the impression that the binding isn't in Racket code in those cases so there's no source to jump to. Am I wrong about that?
You're right; I just misinterpreted what happened with pi
.
With that change (PR soon) and your changes to drracket-tool-text-lib
, this file now supports go-to-definition for all the relevant identifiers in both DrRacket and racket-mode:
#lang rhombus/static
math.sum(1,2)
math.atan(1)
Set.length
Set.empty
1 + 2