Skip to content

Commit ff3e5a1

Browse files
committed
Support infix notation in more places, including type aliases
1 parent bb77409 commit ff3e5a1

File tree

5 files changed

+70
-46
lines changed

5 files changed

+70
-46
lines changed

hackett-doc/scribblings/hackett/reference.scrbl

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,10 @@ specified controls the fixity used by the associated @racket[type-constructor-id
315315
(type type-clause type-expr)
316316
#:grammar
317317
([type-clause name-id
318-
(code:line (name-id param-id ...+))])]{
318+
(code:line (name-id param-id ...+))]
319+
[maybe-fixity-ann (code:line #:fixity fixity)
320+
(code:line)]
321+
[fixity left right])]{
319322

320323
Defines a @deftech{type alias} named @racket[name-id]. Uses of @racket[name-id] are equivalent to
321324
uses of the type specified in @racket[type-expr]. If @racket[type-clause] is a bare @racket[name-id],
@@ -327,11 +330,6 @@ then @racket[name-id] is bound directly to the type alias.
327330
(def n : Num 1.5)
328331
(#:type n))
329332

330-
@margin-note{
331-
Type aliases with @racket[param-id]s can only be used with prefix notation, using
332-
@racket[(name-id type-argument ...)] rather than
333-
@racket[{type-argument name-id type-argument}].}
334-
335333
If @racket[param-id]s are specified, then uses of the type alias must supply as many arguments as
336334
there are @racket[param-id]s. The arguments are supplied like those to a type constructor—i.e.
337335
@racket[(name-id type-argument ...)]—and the resulting type is @racket[type-expr] with each

hackett-lib/hackett/private/base.rkt

Lines changed: 30 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -187,18 +187,36 @@
187187
syntax-local-introduce)
188188
xs)])
189189
(for/lists [es- ts_es]
190-
([k (in-list ks)]
191-
[e (in-list (map car es+ts))]
192-
[e/elab (in-list es/elab)]
193-
[scoped-intdef-ctx (in-list scoped-intdef-ctxs)])
194-
(let* ([e- (local-expand e/elab 'expression stop-ids (if scoped-intdef-ctx
195-
(list intdef-ctx scoped-intdef-ctx)
196-
intdef-ctx))]
197-
[t_e (get-type e-)])
198-
(unless t_e (raise-syntax-error #f "no inferred type" e))
199-
(k (syntax-property e- 'disappeared-binding
200-
(cons (syntax-property e 'disappeared-binding) disappeared-bindings))
201-
t_e)))))
190+
([k (in-list ks)]
191+
[e (in-list (map car es+ts))]
192+
[e/elab (in-list es/elab)]
193+
[scoped-intdef-ctx (in-list scoped-intdef-ctxs)])
194+
(let* ([e- (let ([intdef-ctxs (if scoped-intdef-ctx
195+
(list intdef-ctx scoped-intdef-ctx)
196+
intdef-ctx)])
197+
(let loop ([e e/elab])
198+
(syntax-parse (local-expand e 'expression stop-ids intdef-ctxs)
199+
#:literals [#%expression]
200+
; Expand through #%expression forms if we don’t find an inferred type
201+
; immediately and hope that the nested expression will have a type.
202+
[(head:#%expression e*)
203+
#:when (not (get-type this-syntax))
204+
(syntax-track-origin (loop #'e*) this-syntax #'head)]
205+
; If we find a bare identifier, it’s either a variable, an out-of-context
206+
; identifier, or an unbound identifier that stopped expanding just before
207+
; introducing racket/base’s #%top (since that #%top is implicitly added to
208+
; the stop list). The former two cases are okay, but the latter is not, so
209+
; keep going to trigger the unbound identifier error if the identifier is
210+
; actually unbound.
211+
[_:id
212+
#:when (not (identifier-binding this-syntax))
213+
(local-expand this-syntax 'expression '() intdef-ctxs)]
214+
[_ this-syntax])))]
215+
[t_e (get-type e-)])
216+
(unless t_e (raise-syntax-error #f "no inferred type" e-))
217+
(k (syntax-property e- 'disappeared-binding
218+
(cons (syntax-property e 'disappeared-binding) disappeared-bindings))
219+
t_e)))))
202220

203221
; With everything inferred and checked, all that’s left to do is return the results.
204222
(values xs-* es- ts_es))

hackett-lib/hackett/private/kernel.rkt

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -90,48 +90,45 @@
9090
(begin
9191
(define-syntax-parser @%app
9292
[(~parens _ . args)
93-
(syntax/loc this-syntax
94-
(@%app/prefix . args))]
93+
(datum->syntax this-syntax (cons #'@%app/prefix #'args) this-syntax)]
9594
[{~braces _ . args}
96-
(syntax/loc this-syntax
97-
(@%app/infix . args))])
95+
(datum->syntax this-syntax (cons #'@%app/infix #'args) this-syntax)])
9896

9997
(define-syntax-parser @%app/prefix
10098
[(_ f:expr) #'f]
10199
[(_ f:expr x:expr)
102-
(syntax/loc this-syntax
103-
(@%app1 f x))]
100+
(datum->syntax this-syntax (list #'@%app1 #'f #'x) this-syntax)]
104101
[(_ f:expr x:expr xs:expr ...)
105-
(quasisyntax/loc this-syntax
106-
(@%app/prefix #,(~> (syntax/loc this-syntax
107-
(@%app1 f x))
108-
(syntax-property 'omit-type-tooltip #t))
109-
xs ...))])
102+
#:with inner-app (~> (datum->syntax this-syntax (list #'@%app1 #'f #'x) this-syntax)
103+
(syntax-property 'omit-type-tooltip #t))
104+
(datum->syntax this-syntax (list* #'@%app/prefix #'inner-app #'(xs ...)) this-syntax)])
110105

111106
(define-syntax-parser @%app/infix
112107
[(_ a:expr op:infix-operator b:expr {~seq ops:infix-operator bs:expr} ...+)
113108
#:when (eq? 'left (attribute op.fixity))
114109
#:and ~!
115110
#:fail-unless (andmap #{eq? % 'left} (attribute ops.fixity))
116111
"cannot mix left- and right-associative operators in the same infix expression"
117-
(quasitemplate/loc this-syntax
118-
(@%app/infix #,(~> (syntax/loc this-syntax
119-
(@%app/infix a op b))
112+
#:with inner-app (~> (datum->syntax this-syntax (list #'@%app/infix #'a #'op #'b) this-syntax)
120113
(syntax-property 'omit-type-tooltip #t))
121-
{?@ ops bs} ...))]
114+
(~> (list* #'@%app/infix #'inner-app (syntax->list #'({?@ ops bs} ...)))
115+
(datum->syntax this-syntax _ this-syntax))]
122116
[(_ {~seq as:expr ops:infix-operator} ...+ a:expr op:infix-operator b:expr)
123117
#:when (eq? 'right (attribute op.fixity))
124118
#:and ~!
125119
#:fail-unless (andmap #{eq? % 'right} (attribute ops.fixity))
126120
"cannot mix left- and right-associative operators in the same infix expression"
127-
(quasitemplate/loc this-syntax
128-
(@%app/infix {?@ as ops} ...
129-
#,(~> (syntax/loc this-syntax
130-
(@%app/infix a op b))
131-
(syntax-property 'omit-type-tooltip #t))))]
121+
#:with inner-app (~> (datum->syntax this-syntax (list #'@%app/infix #'a #'op #'b) this-syntax)
122+
(syntax-property 'omit-type-tooltip #t))
123+
(~> (append (list #'@%app/infix) (syntax->list #'({?@ as ops} ...)) (list #'inner-app))
124+
(datum->syntax this-syntax _ this-syntax))]
132125
[(_ a:expr op:expr b:expr)
133-
(syntax/loc this-syntax
134-
(@%app/prefix op a b))]
126+
(quasisyntax/loc this-syntax
127+
(#%expression
128+
#,(~> (datum->syntax this-syntax (list #'op #'a #'b) this-syntax)
129+
; Explicitly make 'paren-shape #f on the new application form to avoid the #\{ value
130+
; being copied onto the prefix application when #%expression is expanded.
131+
(syntax-property 'paren-shape #f))))]
135132
[(_ a:expr)
136133
#'a]))))
137134

hackett-lib/hackett/private/type-alias.rkt

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,12 @@
1515
(begin-for-syntax
1616
; Alias transformer bindings; use the make-alias-transformer constructor instead of creating
1717
; instances of this struct directly.
18-
(struct alias-transformer (procedure)
19-
#:property prop:procedure (struct-field-index procedure))
18+
(struct alias-transformer (procedure fixity)
19+
#:property prop:procedure (struct-field-index procedure)
20+
#:property prop:infix-operator
21+
(λ (self) (alias-transformer-fixity self)))
2022

21-
(define (make-alias-transformer args type-template)
23+
(define (make-alias-transformer args type-template fixity)
2224
(let ([arity (length args)])
2325
(alias-transformer
2426
(cond
@@ -33,13 +35,14 @@
3335
(insts type-template (map cons args (attribute t)))]
3436
[:id
3537
#:fail-when #t (~a "expected " arity " argument(s) to type alias")
36-
(error "unreachable")])])))))
38+
(error "unreachable")])])
39+
fixity))))
3740

3841

3942
(define-syntax-parser type
4043
[(_ ctor-spec:type-constructor-spec {~type type-template:expr})
4144
#:with [ctor-spec*:type-constructor-spec] (type-namespace-introduce #'ctor-spec)
42-
#:fail-when (attribute ctor-spec.fixity) "type aliases do not support infix notation"
45+
#:with fixity (attribute ctor-spec.fixity)
4346

4447
; Create a dummy internal definition context with args.
4548
#:do [(define intdef-ctx (syntax-local-make-definition-context))
@@ -55,5 +58,6 @@
5558
(define-syntax ctor-spec*.tag
5659
(make-alias-transformer
5760
(list (quote-syntax arg*) ...)
58-
(quote-syntax type-template-.expansion))))])
61+
(quote-syntax type-template-.expansion)
62+
'fixity)))])
5963

hackett-test/tests/hackett/integration/type-alias.rkt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,3 +29,10 @@
2929

3030
(test {{4 int= 6} ==! False})
3131
(test {{4 int= 4} ==! True})
32+
33+
(type {a ~> b} #:fixity right {a -> (Maybe b)})
34+
35+
(def head* : (forall [a] {(List a) ~> a}) head)
36+
37+
(test {(head* {1 :: Nil}) ==! (Just 1)})
38+
(test {(head* {Nil : (List Integer)}) ==! Nothing})

0 commit comments

Comments
 (0)