Skip to content

Commit d67ed5c

Browse files
committed
Include type information for DrRacket via 'mouse-over-tooltips
closes #53
1 parent c1989c1 commit d67ed5c

File tree

4 files changed

+121
-22
lines changed

4 files changed

+121
-22
lines changed

hackett-lib/hackett/private/base.rkt

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,11 @@
163163
; We add the internal definition context’s scope to each temporary identifier to allow them to be
164164
; used in reference and binding positions. We’ll need to return these at the end, to allow callers
165165
; to arrange for these identifiers to appear in binding positions.
166-
(define xs-* (map #{internal-definition-context-introduce intdef-ctx %} xs-))
166+
(define xs-* (for/list ([x (in-list xs)]
167+
[x- (in-list xs-)]
168+
[t_x (in-list ts_xs)])
169+
(attach-type (internal-definition-context-introduce intdef-ctx x-) t_x
170+
#:tooltip-src x)))
167171
(for ([x (in-list xs)]
168172
[x-* (in-list xs-*)]
169173
[t_x (in-list ts_xs)])
@@ -308,18 +312,20 @@
308312

309313
(define-syntax-parser @%module-begin
310314
[(_ form ...)
311-
(value-namespace-introduce
312-
(syntax/loc this-syntax
313-
(#%plain-module-begin- form ...)))])
315+
(~> (local-expand (value-namespace-introduce
316+
(syntax/loc this-syntax
317+
(#%plain-module-begin- form ...)))
318+
'module-begin '())
319+
apply-current-subst-in-tooltips)])
314320

315321
(define-syntax-parser @%datum
316322
[(_ . n:exact-integer)
317-
(attach-type #'(#%datum . n) (expand-type #'Integer))]
323+
(attach-type #'(#%datum . n) (expand-type #'Integer) #:tooltip-src #'n)]
318324
[(_ . n:number)
319325
#:when (double-flonum? (syntax-e #'n))
320-
(attach-type #'(#%datum . n) (expand-type #'Double))]
326+
(attach-type #'(#%datum . n) (expand-type #'Double) #:tooltip-src #'n)]
321327
[(_ . s:str)
322-
(attach-type #'(#%datum . s) (expand-type #'String))]
328+
(attach-type #'(#%datum . s) (expand-type #'String) #:tooltip-src #'n)]
323329
[(_ . x)
324330
(raise-syntax-error #f "literal not supported" #'x)])
325331

@@ -359,7 +365,7 @@
359365
#:do [(define-values [f- t_f] (τ⇒! #'f))
360366
(define-values [r- t_r] (τ⇒app! f- (apply-current-subst t_f) #'e
361367
#:src this-syntax))]
362-
(attach-type r- t_r)])
368+
(attach-type r- t_r #:tooltip-src this-syntax)])
363369

364370
(define-syntax-parser def
365371
#:literals [:]

hackett-lib/hackett/private/kernel.rkt

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
(require (for-syntax hackett/private/infix
44
racket/base
55
syntax/parse/class/paren-shape
6-
syntax/parse/experimental/template)
6+
syntax/parse/experimental/template
7+
threading)
78
syntax/parse/define
89

910
(rename-in hackett/private/base
@@ -97,10 +98,14 @@
9798

9899
(define-syntax-parser @%app/prefix
99100
[(_ f:expr) #'f]
101+
[(_ f:expr x:expr)
102+
(syntax/loc this-syntax
103+
(@%app1 f x))]
100104
[(_ f:expr x:expr xs:expr ...)
101105
(quasisyntax/loc this-syntax
102-
(@%app/prefix #,(syntax/loc this-syntax
103-
(@%app1 f x))
106+
(@%app/prefix #,(~> (syntax/loc this-syntax
107+
(@%app1 f x))
108+
(syntax-property 'omit-type-tooltip #t))
104109
xs ...))])
105110

106111
(define-syntax-parser @%app/infix
@@ -110,8 +115,9 @@
110115
#:fail-unless (andmap #{eq? % 'left} (attribute ops.fixity))
111116
"cannot mix left- and right-associative operators in the same infix expression"
112117
(quasitemplate/loc this-syntax
113-
(@%app/infix #,(syntax/loc this-syntax
114-
(@%app/infix a op b))
118+
(@%app/infix #,(~> (syntax/loc this-syntax
119+
(@%app/infix a op b))
120+
(syntax-property 'omit-type-tooltip #t))
115121
{?@ ops bs} ...))]
116122
[(_ {~seq as:expr ops:infix-operator} ...+ a:expr op:infix-operator b:expr)
117123
#:when (eq? 'right (attribute op.fixity))
@@ -120,8 +126,9 @@
120126
"cannot mix left- and right-associative operators in the same infix expression"
121127
(quasitemplate/loc this-syntax
122128
(@%app/infix {?@ as ops} ...
123-
#,(syntax/loc this-syntax
124-
(@%app/infix a op b))))]
129+
#,(~> (syntax/loc this-syntax
130+
(@%app/infix a op b))
131+
(syntax-property 'omit-type-tooltip #t))))]
125132
[(_ a:expr op:expr b:expr)
126133
(syntax/loc this-syntax
127134
(@%app/prefix op a b))]

hackett-lib/hackett/private/typecheck.rkt

Lines changed: 59 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,8 @@
5151
current-type-context modify-type-context
5252
register-global-class-instance! constr->instances lookup-instance!
5353
reduce-context type-reduce-context
54-
attach-type attach-expected get-type get-expected make-typed-var-transformer
54+
attach-type attach-expected get-type get-expected apply-current-subst-in-tooltips
55+
make-typed-var-transformer
5556

5657
(for-template (all-from-out hackett/private/type-language)
5758
local-class-instances @%superclasses-key))
@@ -576,9 +577,51 @@
576577

577578
;; -------------------------------------------------------------------------------------------------
578579

579-
(define/contract (attach-type stx t)
580-
(-> syntax? type? syntax?)
581-
(syntax-property stx ': t))
580+
; To support type tooltips, we attach 'mouse-over-tooltips properties to syntax objects whenever we
581+
; attach a type, unless the 'omit-type-tooltip property is already present. When we attach these
582+
; tooltips, we might not know the fully-solved type yet, so we perform a second pass after the module
583+
; is fully-expanded that applies the current substitution whenever it sees a type. It’s important to
584+
; apply this substitution while the module is still being expanded, since otherwise, the type context
585+
; will be empty!
586+
;
587+
; To support this, we wrap the type inside a deferred-type-in-tooltip struct. This struct cooperates
588+
; with the 'mouse-over-tooltips property by being a procedure, so even if we somehow fail to discover
589+
; the property and evaluate it earlier, we’ll at least get *some* information. When
590+
; apply-current-subst-in-tooltips is called, however, we evaluate the thunk early and replace the
591+
; property with the new value, improving the information in the tooltips.
592+
593+
(struct deferred-type-in-tooltip (type)
594+
#:property prop:procedure
595+
(λ (self) (type->string (apply-current-subst (deferred-type-in-tooltip-type self)))))
596+
597+
(define/contract (attach-type stx t #:tooltip-src [tooltip-src stx])
598+
(->* [syntax? type?] [#:tooltip-src any/c] syntax?)
599+
(let ([stx* (syntax-property stx ': t)])
600+
(if (and (not (syntax-property tooltip-src 'omit-type-tooltip))
601+
(syntax-source tooltip-src)
602+
(syntax-position tooltip-src)
603+
(syntax-span tooltip-src))
604+
(syntax-property
605+
stx* 'mouse-over-tooltips
606+
(syntax-parse tooltip-src
607+
; If it’s a pair, just add the tooltip “on the parens”.
608+
[(_ . _)
609+
(cons
610+
(vector tooltip-src
611+
(sub1 (syntax-position tooltip-src))
612+
(syntax-position tooltip-src)
613+
(deferred-type-in-tooltip t))
614+
(vector tooltip-src
615+
(+ (sub1 (syntax-position tooltip-src)) (sub1 (syntax-span tooltip-src)))
616+
(+ (sub1 (syntax-position tooltip-src)) (syntax-span tooltip-src))
617+
(deferred-type-in-tooltip t)))]
618+
; Otherwise, add the tooltip on the whole region.
619+
[_
620+
(vector tooltip-src
621+
(sub1 (syntax-position tooltip-src))
622+
(+ (sub1 (syntax-position tooltip-src)) (syntax-span tooltip-src))
623+
(deferred-type-in-tooltip t))]))
624+
stx*)))
582625
(define/contract (attach-expected stx t)
583626
(-> syntax? type? syntax?)
584627
(syntax-property stx ':⇐ t))
@@ -592,6 +635,17 @@
592635
(let loop ([val (syntax-property stx ':⇐)])
593636
(if (pair? val) (loop (car val)) val)))
594637

638+
(define (apply-current-subst-in-tooltips stx)
639+
(recursively-adjust-property
640+
stx 'mouse-over-tooltips
641+
(match-lambda
642+
[(vector a b c (? deferred-type-in-tooltip? d))
643+
(vector a b c (d))]
644+
[other other])))
645+
595646
(define/contract (make-typed-var-transformer x t)
596647
(-> identifier? type? any)
597-
(make-variable-like-transformer (attach-type x t)))
648+
(make-variable-like-transformer
649+
; Adjust source location information before calling attach-type so that tooltips end up in the
650+
; right place.
651+
(λ (stx) (attach-type (replace-stx-loc x stx) t))))

hackett-lib/hackett/private/util/stx.rkt

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@
88
racket/match
99
syntax/parse
1010
syntax/parse/define
11-
syntax/parse/experimental/template)
11+
syntax/parse/experimental/template
12+
threading)
1213

1314
(provide (contract-out [replace-stx-loc (-> syntax? syntax? syntax?)]
1415
[make-variable-like-transformer (-> (or/c syntax? (-> identifier? syntax?))
@@ -17,7 +18,9 @@
1718
pattern-expander?)]
1819
[preservable-property->expression (-> any/c syntax?)]
1920
[generate-bound-temporaries (-> (or/c syntax? list?) (listof identifier?))]
20-
[generate-bound-temporary (-> any/c identifier?)])
21+
[generate-bound-temporary (-> any/c identifier?)]
22+
[adjust-property (-> syntax? any/c (-> any/c any/c) syntax?)]
23+
[recursively-adjust-property (-> syntax? any/c (-> any/c any/c) syntax?)])
2124
syntax/loc/props quasisyntax/loc/props template/loc/props quasitemplate/loc/props)
2225

2326
; These two functions are taken with modifications from macrotypes/stx-utils, which implement a
@@ -102,3 +105,32 @@
102105
(define (generate-bound-temporary [name-base 'g])
103106
(first (generate-bound-temporaries (list name-base))))
104107

108+
; Modifies the property of a syntax object by applying a procedure to its value. If the syntax object
109+
; does not contain any such property, the original syntax object is returned. Otherwise, the
110+
; property’s value is recursively traversed as a tree of cons pairs, and the procedure is applied to
111+
; each leaf to produce a new result.
112+
(define (adjust-property stx key proc)
113+
(let ([val (syntax-property stx key)])
114+
(if val
115+
(syntax-property stx key
116+
(let loop ([val val])
117+
(cond [(list? val) (map loop val)]
118+
[(pair? val) (cons (loop (car val)) (loop (cdr val)))]
119+
[else (proc val)])))
120+
stx)))
121+
122+
; Like adjust-property, but recursively walks the syntax object and applies the function to each
123+
; subform. Handles arming and disarming as necessary.
124+
(define (recursively-adjust-property stx key proc)
125+
(let loop ([stx stx])
126+
(let* ([disarmed (syntax-disarm stx #f)]
127+
[result (~> (match (syntax-e disarmed)
128+
[(list a ...) (map loop a)]
129+
[(list* a ..1 b) (append (map loop a) (loop b))]
130+
[a a])
131+
(datum->syntax disarmed _ disarmed disarmed)
132+
(adjust-property key proc)
133+
(syntax-rearm stx))])
134+
(when (syntax-tainted? result)
135+
(raise-syntax-error 'recursively-adjust-property "could not disarm syntax object" stx))
136+
result)))

0 commit comments

Comments
 (0)