Imports as completion candidates for hash-langs

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 real
  • min 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 reads.

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 mins -- 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:

  1. check-syntax draws arrows for both (currently it doesn't for min, making it "unreal" for various purposes).

  2. 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?

It's this commit: Add disappeared-use for fields. · samth/rhombus-prototype@4c700bd · GitHub

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 from racket -- 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.

  1. The scope macro is somewhat different from how rhombus works, because it's math itself that's the macro, so things are a little better there.
  2. It's not obvious what we want to do with sqr in that case -- there isn't a definition of it in a.rkt so maybe we want to racket/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
1 Like