Skip to content

support for bounded polymorphism #1060

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 13 commits into
base: master
Choose a base branch
from
72 changes: 53 additions & 19 deletions typed-racket-lib/typed-racket/infer/infer-unit.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
"signatures.rkt" "fail.rkt"
"promote-demote.rkt"
racket/match
(only-in racket/function curry curryr thunk)
;racket/trace
(contract-req)
(for-syntax
Expand All @@ -38,6 +39,7 @@
(define (empty-set) '())

(define current-seen (make-parameter (empty-set)))
(define infered-tvar-map (make-parameter (hash)))

;; Type Type -> Pair<Seq, Seq>
;; construct a pair for the set of seen type pairs
Expand Down Expand Up @@ -65,6 +67,7 @@
[(context V X Y)
(context (append bounds V) (append vars X) (append indices Y))]))


(define (inferable-index? ctx bound)
(match ctx
[(context _ _ Y)
Expand Down Expand Up @@ -492,6 +495,31 @@
;; this constrains just x (which is a single var)
(define (singleton S x T)
(insert empty x S T))

(define (constrain tvar-a tvar-b #:above above)
(define (maybe-type-app t)
(match t
[(App: t1 (list (F: var))) #:when (hash-has-key? (infered-tvar-map) var)
(define v (hash-ref (infered-tvar-map) var))
(-App t1 (list v))]
[_ t]))

(match-define (F: var (app maybe-type-app maybe-type-bound)) tvar-a)

(define-values (default sub sing) (if above
(values Univ
(thunk (subtype tvar-b maybe-type-bound obj))
(curry singleton (var-promote tvar-b (context-bounds context)) var))
(values -Bottom
(thunk (subtype maybe-type-bound tvar-b obj))
(curryr singleton var (var-demote tvar-b (context-bounds context))))))
(cond
[(not maybe-type-bound) (sing default)]
[(sub)
(infered-tvar-map (hash-set (infered-tvar-map) var maybe-type-bound))
(sing maybe-type-bound)]
[else #f]))

;; FIXME -- figure out how to use parameters less here
;; subtyping doesn't need to use it quite as much
(define cs (current-seen))
Expand Down Expand Up @@ -568,24 +596,24 @@

;; variables that are in X and should be constrained
;; all other variables are compatible only with themselves
[((F: (? (inferable-var? context) v)) T)
[((F: (? (inferable-var? context))) T)
#:return-when
(match T
;; fail when v* is an index variable
[(F: v*) (and (bound-index? v*) (not (bound-tvar? v*)))]
[_ #f])
#f
;; constrain v to be below T (but don't mention bounds)
(singleton -Bottom v (var-demote T (context-bounds context)))]
;; constrain S to be below T (but don't mention bounds)
(constrain S T #:above #f)]

[(S (F: (? (inferable-var? context) v)))
[(S (F: (? (inferable-var? context))))
#:return-when
(match S
[(F: v*) (and (bound-index? v*) (not (bound-tvar? v*)))]
[_ #f])
#f
;; constrain v to be above S (but don't mention bounds)
(singleton (var-promote S (context-bounds context)) v Univ)]
;; constrain T to be above S (but don't mention bounds)
(constrain T S #:above #t)]

;; recursive names should get resolved as they're seen
[(s (? Name? t))
Expand All @@ -595,6 +623,10 @@
(let ([s (resolve-once s)])
(and s (cg s t obj)))]

[((F: var (? Type? bound)) t)
(let ([s (resolve-once bound)])
(and s (cg s t obj)))]

;; constrain b1 to be below T, but don't mention the new vars
[((Poly: v1 b1) T) (cgen (context-add context #:bounds v1) b1 T)]

Expand Down Expand Up @@ -966,6 +998,7 @@
(build-subst md))
(build-subst (stream-first (cset-maps C)))))


;; context : the context of what to infer/not infer
;; S : a list of types to be the subtypes of T
;; T : a list of types
Expand All @@ -983,9 +1016,9 @@
(for/list/fail ([s (in-list S)]
[t (in-list T)]
[obj (in-list/rest objs #f)])
;; We meet early to prune the csets to a reasonable size.
;; This weakens the inference a bit, but sometimes avoids
;; constraint explosion.
;; We meet early to prune the csets to a reasonable size.
;; This weakens the inference a bit, but sometimes avoids
;; constraint explosion.
(% cset-meet (cgen context s t obj) expected-cset)))))


Expand Down Expand Up @@ -1031,16 +1064,17 @@
;; like infer, but T-var is the vararg type:
(define (infer/vararg X Y S T T-var R [expected #f]
#:objs [objs '()])
(and ((length S) . >= . (length T))
(let* ([fewer-ts (- (length S) (length T))]
[new-T (match T-var
[(? Type? var-t) (list-extend S T var-t)]
[(Rest: rst-ts)
#:when (zero? (remainder fewer-ts (length rst-ts)))
(append T (repeat-list rst-ts
(quotient fewer-ts (length rst-ts))))]
[_ T])])
(infer X Y S new-T R expected #:objs objs))))
(parameterize ([infered-tvar-map (hash)])
(and ((length S) . >= . (length T))
(let* ([fewer-ts (- (length S) (length T))]
[new-T (match T-var
[(? Type? var-t) (list-extend S T var-t)]
[(Rest: rst-ts)
#:when (zero? (remainder fewer-ts (length rst-ts)))
(append T (repeat-list rst-ts
(quotient fewer-ts (length rst-ts))))]
[_ T])])
(infer X Y S new-T R expected #:objs objs)))))

;; like infer, but dotted-var is the bound on the ...
;; and T-dotted is the repeated type
Expand Down
21 changes: 20 additions & 1 deletion typed-racket-lib/typed-racket/private/parse-type.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,11 @@
(parse-literal-alls #'t.type))]
[_ null]))

(define-syntax-class maybe-bounded
#:datum-literals (<:)
#:attributes (name bound)
(pattern (name:id <: bound:expr))
(pattern name:id #:attr bound #f))

;; Syntax -> Type
;; Parse a Forall type
Expand All @@ -215,7 +220,21 @@
"variable" (syntax-e maybe-dup)))
(let* ([vars (stx-map syntax-e #'(vars ...))])
(extend-tvars vars
(make-Poly vars (parse-type #'t.type))))]
(make-Poly vars (parse-type #'t.type))))]
[(:All^ (vars:maybe-bounded ...) . t:omit-parens)
(define maybe-dup (check-duplicate-identifier (attribute vars.name)))
(when maybe-dup
(parse-error "duplicate type variable"
"variable" (syntax-e maybe-dup)))
(define bounds (for/fold ([acc (hash)])
([i (stx-map syntax-e (attribute vars.name))]
[j (attribute vars.bound)]
#:when j)
(hash-set acc i
(extend-tvars (hash-keys acc) (parse-type j)))))
(let* ([vars (stx-map syntax-e (attribute vars.name))])
(extend-tvars vars
(make-Poly vars (parse-type #'t.type) #:bounds bounds)))]
;; Next two are row polymorphic cases
[(:All^ (var:id #:row) . t:omit-parens)
(add-disappeared-use #'kw)
Expand Down
73 changes: 60 additions & 13 deletions typed-racket-lib/typed-racket/rep/type-rep.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
syntax/id-set
racket/contract
racket/lazy-require
racket/syntax
racket/unsafe/undefined
(for-syntax racket/base
racket/syntax
Expand All @@ -46,6 +47,9 @@
PolyDots-unsafe:
Mu? Poly? PolyDots? PolyRow?
Poly-n
F-n
F-bound
F?
PolyDots-n
Class? Row? Row:
free-vars*
Expand Down Expand Up @@ -84,6 +88,8 @@
[PolyDots:* PolyDots:]
[PolyRow:* PolyRow:]
[Mu* make-Mu]
[F* make-F]
[F:* F:]
[make-Mu unsafe-make-Mu]
[Poly* make-Poly]
[PolyDots* make-PolyDots]
Expand All @@ -105,6 +111,7 @@
(App? x)))

(lazy-require
("../types/substitute.rkt" (subst))
("../types/overlap.rkt" (overlap?))
("../types/prop-ops.rkt" (-and))
("../types/resolve.rkt" (resolve-app))
Expand Down Expand Up @@ -139,13 +146,26 @@

;; free type variables
;; n is a Name
(def-type F ([n symbol?])
(def-type F ([n symbol?]
[bound (or/c #f Type?)])
#:no-provide
[#:frees
[#:vars (_) (single-free-var n)]
[#:idxs (_) empty-free-vars]]
[#:fmap (_ #:self self) self]
[#:for-each (_) (void)])

(define (F* n [bound #f])
(make-F n bound))


(define-match-expander F:*
(lambda (stx)
(syntax-case stx ()
[(_ n) #'(F: n _)]
[(_ n b) #'(F: n b)])))


(define Name-table (make-free-id-table))

;; Name, an indirection of a type through the environment
Expand Down Expand Up @@ -519,10 +539,14 @@
;; n is how many variables are bound here
;; body is a type
(def-type Poly ([n exact-nonnegative-integer?]
[bounds (hash/c exact-nonnegative-integer?
Type?
#:immutable #t
#:flat #t)]
[body Type?])
#:no-provide
[#:frees (f) (f body)]
[#:fmap (f) (make-Poly n (f body))]
[#:fmap (f) (make-Poly n bounds (f body))]
[#:for-each (f) (f body)]
[#:mask (λ (t) (mask (Poly-body t)))])

Expand Down Expand Up @@ -1456,7 +1480,7 @@
;; De Bruijn indices
[(B: idx) (transform idx lvl cur #f)]
;; Type variables
[(F: var) (transform var lvl cur #f)]
[(F: var _) (transform var lvl cur #f)]
;; forms w/ dotted type vars/indices
[(RestDots: ty d)
(make-RestDots (rec ty) (transform d lvl d #t))]
Expand All @@ -1477,8 +1501,8 @@
(make-PolyRow constraints (rec/lvl body (add1 lvl)))]
[(PolyDots: n body)
(make-PolyDots n (rec/lvl body (+ n lvl)))]
[(Poly: n body)
(make-Poly n (rec/lvl body (+ n lvl)))]
[(Poly: n bounds body)
(make-Poly n bounds (rec/lvl body (+ n lvl)))]
[_ (Rep-fmap cur rec)])))


Expand Down Expand Up @@ -1618,7 +1642,7 @@
(define (Mu-body* name t)
(match t
[(Mu: body)
(instantiate-type body (make-F name))]))
(instantiate-type body (F* name))]))

;; unfold : Mu -> Type
(define/cond-contract (unfold t)
Expand All @@ -1638,19 +1662,42 @@
;;
;; list<symbol> type #:original-names list<symbol> -> type
;;
(define (Poly* names body #:original-names [orig names])

(define (Poly* names body #:bounds [bounds '#hash()] #:original-names [orig names])
(if (null? names) body
(let ([v (make-Poly (length names) (abstract-type body names))])
(let* ([len (length names)]
[new-bounds (for/hash ([(n v) bounds])
(values (index-of names n) v))]
[v (make-Poly len new-bounds (abstract-type body names))])
(hash-set! type-var-name-table v orig)
v)))

(define (unsubst ty orig-names names)
(for/fold ([acc ty])
([o orig-names]
[n names])
(subst o (make-F n #f) acc)
#;
(subst o (make-Name (format-id #f "~a" n) 0 #f) acc)))

;; Poly 'smart' destructor
(define (Poly-body* names t)
(define orig-names (hash-ref type-var-name-table t null))
(match t
[(Poly: n body)
[(Poly: n bounds body)
(define new-bounds (for/hash ([(idx v) bounds])
(values (list-ref names idx) (unsubst v orig-names names))))
(unless (= (length names) n)
(int-err "Wrong number of names: expected ~a got ~a" n (length names)))
(instantiate-type body (map make-F names))]))
(instantiate-type body
(map (lambda (n)
(define v (match (hash-ref new-bounds n #f)
[(App: rator (list (F: vb _)))
#:when (hash-has-key? new-bounds vb)
(make-App rator (list (hash-ref new-bounds vb)))]
[_else _else]))
(make-F n v))
names))]))

;; PolyDots 'smart' constructor
(define (PolyDots* names body)
Expand All @@ -1665,7 +1712,7 @@
[(PolyDots: n body)
(unless (= (length names) n)
(int-err "Wrong number of names: expected ~a got ~a" n (length names)))
(instantiate-type body (map make-F names))]))
(instantiate-type body (map F* names))]))


;; PolyRow 'smart' constructor
Expand All @@ -1683,7 +1730,7 @@
(define (PolyRow-body* names t)
(match t
[(PolyRow: constraints body)
(instantiate-type body (map make-F names))]))
(instantiate-type body (map F* names))]))


;;***************************************************************
Expand Down Expand Up @@ -1939,7 +1986,7 @@
[(Some: n body)
(unless (= (length names) n)
(int-err "Wrong number of names: expected ~a got ~a" n (length names)))
(instantiate-type body (map make-F names))]))
(instantiate-type body (map F* names))]))


(define-match-expander Some-names:
Expand Down
3 changes: 2 additions & 1 deletion typed-racket-lib/typed-racket/types/printer.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -714,10 +714,11 @@
[(? Base?) (Base-name type)]
[(Pair: l r) `(Pairof ,(t->s l) ,(t->s r))]
[(ListDots: dty dbound) `(List ,(t->s dty) ... ,dbound)]
[(F: nm)
[(F: nm bound)
(cond
[(eq? nm imp-var) "Imp"]
[(eq? nm self-var) "Self"]
[(Type? bound) (format "~a <: ~a" nm (t->s bound))]
[else nm])]
[(Param: in out)
(if (equal? in out)
Expand Down
9 changes: 5 additions & 4 deletions typed-racket-lib/typed-racket/types/subtype.rkt
Original file line number Diff line number Diff line change
Expand Up @@ -802,11 +802,12 @@
(match t2
[(Evt: result2) (subtype* A result1 result2)]
[_ (continue<: A t1 t2 obj)])]
[(case: F (F: var1))
(match t2
[(case: F (F: var1 bound))
(match* (t2 bound)
;; tvars are equal if they are the same variable
[(F: var2) (and (eq? var1 var2) A)]
[_ (continue<: A t1 t2 obj)])]
[((F: var2) _) (and (eq? var1 var2) A)]
[(_ (? Type?)) (subtype* A bound t2 obj)]
[(_ _) (continue<: A t1 t2 obj)])]
[(case: Fun (Fun: arrows1))
(match* (t2 arrows1)
;; special case when t1 can be collapsed into simpler arrow
Expand Down
Loading