Refactor subtype.rkt not to use exceptions.

Matthew suggests that using exceptions for failure
in the implementation of `subtype` is a performance
problem.  This commit removes all use of exceptions
for failure in subtype.rkt, replacing it with the
standard Racket #f/value option.

Extensive use is made of the `subtype-seq` form,
which is basically do-notation for the subtyping
monad.
(cherry picked from commit a6f110893f)
This commit is contained in:
Sam Tobin-Hochstadt 2013-05-08 11:54:49 -04:00 committed by Ryan Culpepper
parent 8483d0cd6f
commit 20f546110b

View File

@ -1,4 +1,4 @@
#lang racket/base
#lang errortrace racket/base
(require (except-in "../utils/utils.rkt" infer)
(rep type-rep filter-rep object-rep rep-utils)
(utils tc-utils)
@ -11,22 +11,11 @@
racket/lazy-require
(prefix-in c: racket/contract)
(for-syntax racket/base syntax/parse))
(require racket/trace)
(lazy-require
("union.rkt" (Un))
("../infer/infer.rkt" (infer)))
;; exn representing failure of subtyping
;; s,t both types
(define-struct (exn:subtype exn:fail) (s t))
;; subtyping failure - masked before it gets to the user program
(define-syntax fail!
(syntax-rules ()
[(_ s t) (raise (make-exn:subtype "subtyping failed" (current-continuation-marks) s t))]))
(define subtype-cache (make-hash))
(define (cache-types s t)
(cache-keys (Type-seq s) (Type-seq t)))
@ -36,43 +25,44 @@
(hash-ref subtype-cache (cons (Type-seq s) (Type-seq t)) #f))
(define-syntax-rule (handle-failure e)
(with-handlers ([exn:subtype? (λ (_) #f)])
e))
e)
;; is s a subtype of t?
;; type type -> boolean
(define/cond-contract (subtype s t)
(c:-> (c:or/c Type/c SomeValues/c) (c:or/c Type/c SomeValues/c) boolean?)
(define k (cons (Type-seq s) (Type-seq t)))
(define (new-val)
(define result (handle-failure (and (subtype* (current-seen) s t) #t)))
;(printf "subtype cache miss ~a ~a\n" s t)
result)
((if (currently-subtyping?) hash-ref hash-ref!)
subtype-cache k new-val))
subtype-cache k
(lambda () (and (subtype* (current-seen) s t) #t))))
;; are all the s's subtypes of all the t's?
;; [type] [type] -> boolean
(define (subtypes s t) (handle-failure (and (subtypes* (current-seen) s t) #t)))
;; subtyping under constraint set, but produces boolean result instead of raising exn
;; List[(cons Number Number)] type type -> maybe[List[(cons Number Number)]]
(define (subtype*/no-fail A s t) (handle-failure (subtype* A s t)))
(define (subtypes s t) (and (subtypes* (current-seen) s t) #t))
;; check subtyping for two lists of types
;; List[(cons Number Number)] listof[type] listof[type] -> List[(cons Number Number)]
;; List[(cons Number Number)] listof[type] listof[type] -> Opt[List[(cons Number Number)]]
(define (subtypes* A ss ts)
(cond [(and (null? ss) (null? ts) A)]
[(or (null? ss) (null? ts)) (fail! ss ts)]
[(or (null? ss) (null? ts)) #f]
[(subtype* A (car ss) (car ts))
=>
(lambda (A*) (subtypes* A* (cdr ss) (cdr ts)))]
[else (fail! (car ss) (car ts))]))
[else #f]))
;; check if s is a supertype of any element of ts
(define (supertype-of-one/arr A s ts)
(ormap (lambda (e) (arr-subtype*/no-fail A e s)) ts))
(define-syntax (let*/and stx)
(syntax-parse stx
[(_ () . e) (syntax/loc stx (let () . e))]
[(_ ([id expr] . rest) . body)
(syntax/loc stx
(let ([id expr])
(and id (let*/and rest . body))))]))
;; do notation for the subtyping monad
(define-syntax (subtype-seq stx)
(define-syntax-class sub*
(pattern e:expr))
@ -85,33 +75,31 @@
[A (syntax->list #'(init A* ...))]
[A-next (syntax->list #'(A* ... A-last))])
#`[#,A-next (#,s #,A . #,args)])])
#'(let* (clauses ...)
A-last)))]))
(syntax/loc stx (let*/and (clauses ...)
A-last))))]))
(define (kw-subtypes* A0 t-kws s-kws)
(let loop ([A A0] [t t-kws] [s s-kws])
(and
A
(match* (t s)
[((list (Keyword: kt tt rt) rest-t) (list (Keyword: ks ts rs) rest-s))
(cond [(eq? kt ks)
(if
;; if s is optional, t must be as well
(and ;; if s is optional, t must be as well
(or rs (not rt))
(loop (subtype* A tt ts) rest-t rest-s)
(fail! t s))]
(loop (subtype* A tt ts) rest-t rest-s))]
;; extra keywords in t are ok
;; we just ignore them
[(keyword<? kt ks) (loop A rest-t s)]
;; extra keywords in s are a problem
[else (fail! t s)])]
[else #f])]
;; no more keywords to satisfy
[(_ '()) A]
;; we failed to satisfy all the keyword
[(_ _) (fail! s t)])))
[(_ _) #f]))))
;; simple co/contra-variance for ->
(define (arr-subtype*/no-fail A0 s t)
(with-handlers
([exn:subtype? (lambda _ #f)])
(match* (s t)
;; top for functions is above everything
[(_ (top-arr:)) A0]
@ -135,7 +123,7 @@
(subtype* s-rng t-rng))]
[((arr: s-dom s-rng #f #f s-kws)
(arr: t-dom t-rng t-rest #f t-kws))
(fail! s t)]
#f]
[((arr: s-dom s-rng s-rest #f s-kws)
(arr: t-dom t-rng t-rest #f t-kws))
(subtype-seq A0
@ -151,8 +139,7 @@
(subtypes* t-dom s-dom)
(kw-subtypes* t-kws s-kws)
(subtype* s-rng t-rng))]
[(_ _)
(fail! s t)])))
[(_ _) #f]))
;; check subtyping of filters, so that predicates subtype correctly
(define (filter-subtype* A0 s t)
@ -160,7 +147,7 @@
[(f f) A0]
[((Bot:) t) A0]
[(s (Top:)) A0]
[(_ _) (fail! s t)]))
[(_ _) #f]))
(define (subtypes/varargs args dom rst)
(handle-failure (and (subtypes*/varargs null args dom rst) #t)))
@ -168,14 +155,15 @@
(define (subtypes*/varargs A0 argtys dom rst)
(let loop-varargs ([dom dom] [argtys argtys] [A A0])
(cond
[(not A) #f]
[(and (null? dom) (null? argtys)) A]
[(null? argtys) (fail! argtys dom)]
[(null? argtys) #f]
[(and (null? dom) rst)
(cond [(subtype* A (car argtys) rst) => (lambda (A) (loop-varargs dom (cdr argtys) A))]
[else (fail! (car argtys) rst)])]
[(null? dom) (fail! argtys dom)]
[else #f])]
[(null? dom) #f]
[(subtype* A (car argtys) (car dom)) => (lambda (A) (loop-varargs (cdr dom) (cdr argtys) A))]
[else (fail! (car argtys) (car dom))])))
[else #f])))
;(trace subtypes*/varargs)
@ -199,12 +187,17 @@
(App: (and (Name: _) (app resolve-once (Poly: _ (? Struct? i)))) _ _))])))
(define (subtype/flds* A flds flds*)
(for/fold ([A A]) ([f (in-list flds)] [f* (in-list flds*)])
(for/fold ([A A]) ([f (in-list flds)] [f* (in-list flds*)] #:break (not A))
(and
A
(match* (f f*)
[((fld: t _ #t) (fld: t* _ #t))
(subtype* (subtype* A t* t) t t*)]
(subtype-seq A
(subtype* t* t)
(subtype* t t*))]
[((fld: t _ #f) (fld: t* _ #f))
(subtype* A t t*)])))
(subtype* A t t*)]
[(_ _) #f]))))
(define (unrelated-structs s1 s2)
(define (in-hierarchy? s par)
@ -226,25 +219,29 @@
[_ (int-err "wtf is this? ~a" s)])))
(not (or (in-hierarchy? s1 s2) (in-hierarchy? s2 s1))))
(define (type-equiv? A0 s t)
(subtype* (subtype* A0 s t) t s))
(define/cond-contract (type-equiv? A0 s t)
(c:-> list? Type? Type? c:any/c)
(subtype-seq A0
(subtype* s t)
(subtype* t s)))
;; the algorithm for recursive types transcribed directly from TAPL, pg 305
;; List[(cons Number Number)] type type -> List[(cons Number Number)]
;; potentially raises exn:subtype, when the algorithm fails
;; is s a subtype of t, taking into account constraints A
(define (subtype* A s t)
(define/cond-contract (subtype* A s t)
(c:-> list? Type? Type? c:any/c)
(define =t (lambda (a b) (if (and (Rep? a) (Rep? b)) (type-equal? a b) (equal? a b))))
(parameterize ([match-equality-test =t]
[current-seen A])
(let ([ks (Type-key s)] [kt (Type-key t)])
(cond
[(or (seen? s t) (type-equal? s t)) A]
[(and (symbol? ks) (symbol? kt) (not (eq? ks kt))) (fail! s t)]
[(and (symbol? ks) (pair? kt) (not (memq ks kt))) (fail! s t)]
[(and (symbol? ks) (symbol? kt) (not (eq? ks kt))) #f]
[(and (symbol? ks) (pair? kt) (not (memq ks kt))) #f]
[(and (pair? ks) (pair? kt)
(for/and ([i (in-list ks)]) (not (memq i kt))))
(fail! s t)]
#f]
[else
(let* ([A0 (remember s t A)])
(parameterize ([current-seen A0])
@ -255,14 +252,14 @@
[(_ (Error:)) A0]
[((Error:) _) A0]
;; (Un) is bot
[(_ (Union: (list))) (fail! s t)]
[(_ (Union: (list))) #f]
[((Union: (list)) _) A0]
;; value types
[((Value: v1) (Value: v2)) (=> unmatch) (if (equal? v1 v2) A0 (unmatch))]
;; values are subtypes of their "type"
[((Value: v) (Base: _ _ pred _ _)) (if (pred v) A0 (fail! s t))]
[((Value: v) (Base: _ _ pred _ _)) (if (pred v) A0 #f)]
;; tvars are equal if they are the same variable
[((F: t) (F: t*)) (if (eq? t t*) A0 (fail! s t))]
[((F: t) (F: t*)) (if (eq? t t*) A0 #f)]
;; Avoid needing to resolve things that refer to different structs.
;; Saves us from non-termination
;; Must happen *before* the sequence cases, which sometimes call `resolve' in match expanders
@ -270,41 +267,41 @@
(=> unmatch)
(cond [(unrelated-structs s1 s2)
;(dprintf "found unrelated structs: ~a ~a\n" s1 s2)
(fail! s t)]
#f]
[else (unmatch)])]
;; similar case for structs and base types, which are obviously unrelated
[((Base: _ _ _ _ _) (or (? Struct? s1) (NameStruct: s1)))
(fail! s t)]
#f]
[((or (? Struct? s1) (NameStruct: s1)) (Base: _ _ _ _ _))
(fail! s t)]
#f]
;; same for all values.
[((Value: (? (negate struct?) _)) (or (? Struct? s1) (NameStruct: s1)))
(fail! s t)]
#f]
[((or (? Struct? s1) (NameStruct: s1)) (Value: (? (negate struct?) _)))
(fail! s t)]
#f]
;; just checking if s/t is a struct misses recursive/union/etc cases
[((? (lambda (_) (eq? ks 'struct))) (Base: _ _ _ _ _)) (fail! s t)]
[((Base: _ _ _ _ _) (? (lambda (_) (eq? kt 'struct)))) (fail! s t)]
[((? (lambda (_) (eq? ks 'struct))) (Base: _ _ _ _ _)) #f]
[((Base: _ _ _ _ _) (? (lambda (_) (eq? kt 'struct)))) #f]
;; sequences are covariant
[((Sequence: ts) (Sequence: ts*))
(subtypes* A0 ts ts*)]
[((Listof: t) (Sequence: (list t*)))
(subtype* A0 t t*)]
[((Pair: t1 t2) (Sequence: (list t*)))
(let ([A1 (subtype* A0 t1 t*)])
(subtype* A1 t2 (-lst t*)))]
(subtype-seq A0 (subtype* t1 t*) (subtype* t2 (-lst t*)))]
[((MListof: t) (Sequence: (list t*)))
(subtype* A0 t t*)]
;; To check that mutable pair is a sequence we check that the cdr
;; is both an mutable list and a sequence
[((MPair: t1 t2) (Sequence: (list t*)))
(let* ([A1 (subtype* A0 t1 t*)]
[A2 (subtype* A1 t2 (simple-Un (-val null) (make-MPairTop)))])
(subtype* A2 t2 t))]
(subtype-seq A0
(subtype* t1 t*)
(subtype* t2 (simple-Un (-val null) (make-MPairTop)))
(subtype* t2 t))]
[((List: ts) (Sequence: (list t*)))
(subtypes* A0 ts (map (λ _ t*) ts))]
(subtypes* A0 ts (map (λ (_) t*) ts))]
[((HeterogeneousVector: ts) (Sequence: (list t*)))
(subtypes* A0 ts (map (λ _ t*) ts))]
(subtypes* A0 ts (map (λ (_) t*) ts))]
[((Vector: t) (Sequence: (list t*)))
(subtype* A0 t t*)]
[((Base: 'String _ _ _ _) (Sequence: (list t*)))
@ -321,38 +318,39 @@
(list portable-fixnum? -NonNegFixnum)
(list values -Nat)))
(define type
(for/or ((pred-type possibilities))
(for/or ((pred-type (in-list possibilities)))
(match pred-type
((list pred? type)
(and (pred? n) type)))))
(subtype* A0 type t*)]
[((Base: _ _ _ _ #t) (Sequence: (list t*)))
(define type
;; FIXME: thread the store through here
(for/or ((t (list -Byte -Index -NonNegFixnum -Nat)))
(and (subtype s t) t)))
(or (and (subtype* A0 s t) t))))
(if type
(subtype* A0 type t*)
(fail! s t))]
#f)]
[((Hashtable: k v) (Sequence: (list k* v*)))
(subtypes* A0 (list k v) (list k* v*))]
[((Set: t) (Sequence: (list t*)))
(subtype* A0 t t*)]
;; special-case for case-lambda/union with only one argument
[((Function: arr1) (Function: (list arr2)))
(when (null? arr1) (fail! s t))
(cond [(null? arr1) #f]
[else
(define comb (combine-arrs arr1))
(or (and comb (arr-subtype*/no-fail A0 comb arr2))
(supertype-of-one/arr A0 arr2 arr1)
(fail! s t))]
(supertype-of-one/arr A0 arr2 arr1))])]
;; case-lambda
[((Function: arr1) (Function: arr2))
(when (null? arr1) (fail! s t))
(when (null? arr1) #f)
(let loop-arities ([A* A0]
[arr2 arr2])
(cond
[(null? arr2) A*]
[(supertype-of-one/arr A* (car arr2) arr1) => (lambda (A) (loop-arities A (cdr arr2)))]
[else (fail! s t)]))]
[else #f]))]
;; recur structurally on pairs
[((Pair: a d) (Pair: a* d*))
(subtypes* A0 (list a d) (list a* d*))]
@ -417,12 +415,12 @@
(let ([s* (resolve-once s)])
(if (Type/c? s*) ;; needed in case this was a name that hasn't been resolved yet
(subtype* A0 s* other)
(fail! s t)))]
#f))]
[(other (? needs-resolving? t))
(let ([t* (resolve-once t)])
(if (Type/c? t*) ;; needed in case this was a name that hasn't been resolved yet
(subtype* A0 other t*)
(fail! s t)))]
#f))]
;; for unions, we check the cross-product
;; some special cases for better performance
;; first, if both types are numeric, they will be built from the same base types
@ -430,7 +428,7 @@
[((Base: _ _ _ _ _) (Union: l2))
(=> unmatch)
(if (and (eq? ks 'number) (eq? kt 'number))
(if (memq s l2) A0 (fail! s t))
(if (memq s l2) A0 #f)
(unmatch))]
[((Union: l1) (Union: l2))
(=> unmatch)
@ -441,40 +439,37 @@
(cond [(null? l1)
A0]
[(null? l2)
(fail! s t)]
#f]
[(eq? (car l1) (car l2))
(loop (cdr l1) (cdr l2))]
[else
(loop l1 (cdr l2))]))
(unmatch))]
[((Union: (list e1 e2)) t)
(if (and (subtype* A0 e1 t) (subtype* A0 e2 t))
A0
(fail! s t))]
(and (and (subtype* A0 e1 t) (subtype* A0 e2 t))
A0)]
[((Union: (list e1 e2 e3)) t)
(if (and (subtype* A0 e1 t) (subtype* A0 e2 t) (subtype* A0 e3 t))
A0
(fail! s t))]
(and (and (subtype* A0 e1 t) (subtype* A0 e2 t) (subtype* A0 e3 t))
A0)]
[((Union: es) t)
(if (for/and ([elem (in-list es)])
(and (for/and ([elem (in-list es)])
(subtype* A0 elem t))
A0
(fail! s t))]
A0)]
[(s (Union: es))
(if (for/or ([elem (in-list es)])
(with-handlers ([exn:subtype? (lambda _ #f)])
(subtype* A0 s elem)))
A0
(fail! s t))]
(and (for/or ([elem (in-list es)])
(subtype* A0 s elem))
A0)]
;; subtyping on immutable structs is covariant
[((Struct: nm _ flds proc _ _) (Struct: nm* _ flds* proc* _ _)) (=> nevermind)
(unless (free-identifier=? nm nm*) (nevermind))
(let ([A (cond [(and proc proc*) (subtype* proc proc*)]
[proc* (fail! proc proc*)]
[((Struct: nm _ flds proc _ _) (Struct: nm* _ flds* proc* _ _))
(=> unmatch)
(unless (free-identifier=? nm nm*) (unmatch))
(let ([A (cond [(and proc proc*) (subtype* A0 proc proc*)]
[proc* #f]
[else A0])])
(subtype/flds* A flds flds*))]
[((Struct: nm _ _ _ _ _) (StructTop: (Struct: nm* _ _ _ _ _))) (=> nevermind)
(unless (free-identifier=? nm nm*) (nevermind))
(and A (subtype/flds* A flds flds*)))]
[((Struct: nm _ _ _ _ _) (StructTop: (Struct: nm* _ _ _ _ _)))
(=> unmatch)
(unless (free-identifier=? nm nm*) (unmatch))
A0]
;; Promises are covariant
[((Promise: s) (Promise: t))
@ -496,21 +491,27 @@
[((Vector: _) (VectorTop:)) A0]
[((HeterogeneousVector: _) (VectorTop:)) A0]
[((HeterogeneousVector: (list e ...)) (Vector: e*))
(for/fold ((A A0)) ((e e))
(type-equiv? A e e*))]
(for/fold ((A A0)) ((e e) #:break (not A))
(and A (type-equiv? A e e*)))]
[((HeterogeneousVector: (list s* ...)) (HeterogeneousVector: (list t* ...)))
(if (= (length s*) (length t*))
(for/fold ((A A0)) ((s s*) (t t*))
(for/fold ((A A0)) ((s s*) (t t*) #:break (not A))
(type-equiv? A s t))
(fail! s* t*))]
#f)]
[((MPair: s1 s2) (MPair: t1 t2))
(type-equiv? (type-equiv? A0 s1 t1) s2 t2)]
(subtype-seq A0
(type-equiv? s1 t1)
(type-equiv? s2 t2))]
[((MPair: _ _) (MPairTop:)) A0]
[((Hashtable: s1 s2) (Hashtable: t1 t2))
(type-equiv? (type-equiv? A0 s1 t1) s2 t2)]
(subtype-seq A0
(type-equiv? s1 t1)
(type-equiv? s2 t2))]
[((Hashtable: _ _) (HashtableTop:)) A0]
[((Prompt-Tagof: s1 s2) (Prompt-Tagof: t1 t2))
(type-equiv? (type-equiv? A0 s1 t1) s2 t2)]
(subtype-seq A0
(type-equiv? s1 t1)
(type-equiv? s2 t2))]
[((Prompt-Tagof: _ _) (Prompt-TagTop:)) A0]
[((Continuation-Mark-Keyof: s) (Continuation-Mark-Keyof: t))
(type-equiv? A0 s t)]
@ -539,7 +540,9 @@
[((Future: t) (Future: t*))
(subtype* A0 t t*)]
[((Param: s-in s-out) (Param: t-in t-out))
(subtype* (subtype* A0 t-in s-in) s-out t-out)]
(subtype-seq A0
(subtype* t-in s-in)
(subtype* s-out t-out))]
[((Param: in out) t)
(subtype* A0 (cl->* (-> out) (-> in -Void)) t)]
[((Instance: t) (Instance: t*))
@ -547,15 +550,17 @@
[((Class: '() '() (list (and s (list names meths )) ...))
(Class: '() '() (list (and s* (list names* meths*)) ...)))
(for/fold ([A A0])
([n names*] [m meths*])
(cond [(assq n s) => (lambda (spec) (subtype* A (cadr spec) m))]
[else (fail! s t)]))]
([n names*] [m meths*] #:break (not A))
(and A (cond [(assq n s) => (lambda (spec) (subtype* A (cadr spec) m))]
[else #f])))]
;; otherwise, not a subtype
[(_ _) (fail! s t) #;(dprintf "failed")])))]))))
[(_ _) #f #;(dprintf "failed")])))]))))
(define (type-compare? a b)
(and (subtype a b) (subtype b a)))
;; List[(cons Number Number)] type type -> maybe[List[(cons Number Number)]]
(define subtype*/no-fail subtype*)
(provide/cond-contract
[subtype (c:-> (c:or/c Type/c SomeValues/c) (c:or/c Type/c SomeValues/c) boolean?)]