; Copyright (c) 1993, 1994 Richard Kelsey and Jonathan Rees. See file COPYING. ; Type lattice. ; Sorry this is so hairy, but before it was written, type checking ; consumed 15% of compile time. ; f : t1 -> t2 restrictive means: ; if x : t1 then (f x) : t2 (possible error!), else (f x) : error. ; f : t1 -> t2 nonrestrictive means: ; There exists an x : t1 such that (f x) : t2. (define-record-type meta-type :meta-type (really-make-type mask more info) meta-type? (mask type-mask) (more type-more) (info type-info)) (define-record-discloser :meta-type (lambda (t) `(type ,(let ((m (type-mask t))) (or (table-ref mask->name-table m) m)) ,(let ((more (type-more t))) (if (and (pair? more) (eq? (cdr more) t)) '* more)) ,(type-info t)))) (define (make-type mask more info) (make-immutable! (really-make-type mask more info))) (define name->type-table (make-table)) (define mask->name-table (make-table)) (define (name->type x) (or (table-ref name->type-table x) (make-other-type x))) (define (set-type-name! type name) (table-set! name->type-table name type) (if (not (or (type-info type) (type-more type))) (table-set! mask->name-table (type-mask type) name))) ; Masks ; Top of lattice has mask = -1, bottom has mask = 0. (define *mask* 1) (define (new-type-bit) (let ((m *mask*)) (set! *mask* (arithmetic-shift *mask* 1)) m)) (define (mask->type mask) (make-type mask #f #f)) (define bottom-type (mask->type 0)) (define error-type bottom-type) (define (bottom-type? t) (= (type-mask t) 0)) (set-type-name! bottom-type ':error) (define (new-atomic-type) (mask->type (new-type-bit))) (define (named-atomic-type name) (let ((t (new-atomic-type))) (set-type-name! t name) t)) ; -------------------- ; Top of the lattice. (define syntax-type (named-atomic-type ':syntax)) (define other-static-type (new-atomic-type)) ; -------------------- ; "Rails" are argument sequence or return value sequences. ; Four constructors: ; empty-rail-type ; (rail-type t1 t2) ; (optional-rail-type t1 t2) ; (make-rest-type t) ; If a type's two-or-more? bit is set, then ; more = (head . tail). ; Otherwise, more = #f. (define empty-rail-type (new-atomic-type)) (define (rail-type t1 t2) ;CONS analog (cond ((empty-rail-type? t2) t1) ((bottom-type? t1) t1) ((bottom-type? t2) t2) ((and (optional-type? t1) (rest-type? t2) (same-type? t1 (head-type t2))) ;; Turn (&opt t &rest t) into (&rest t) t2) ((or (optional-type? t1) (optional-type? t2)) (make-type (bitwise-ior (type-mask t1) mask/two-or-more) (make-immutable! (cons t1 t2)) #f)) (else (make-type mask/two-or-more (make-immutable! (cons t1 t2)) (type-info t1))))) (define (make-optional-type t) (if (type-more t) (warn "peculiar type in make-optional-type" t)) (make-type (bitwise-ior (type-mask t) mask/no-values) #f (type-info t))) (define (make-rest-type t) (if (bottom-type? t) t (let* ((z (cons (make-optional-type t) #f)) (t (make-type (bitwise-ior (type-mask t) mask/&rest) z (type-info t)))) (set-cdr! z t) (make-immutable! z) t))) (define (head-type t) ;Can return an &opt type (let ((more (type-more t))) (if more (car more) t))) (define (head-type-really t) ;Always returns a value type (let ((h (head-type t))) (if (optional-type? h) (make-type (bitwise-and (type-mask h) (bitwise-not mask/no-values)) #f (type-info h)) h))) (define (tail-type t) (if (empty-rail-type? t) ;; bottom-type ? (warn "rail-type of empty rail" t)) (let ((more (type-more t))) (if more (cdr more) empty-rail-type))) (define (empty-rail-type? t) (= (bitwise-and (type-mask t) mask/one-or-more) 0)) (define (rest-type? t) ;For terminating recursions (let ((more (type-more t))) (and more (eq? (cdr more) t)))) (define (optional-type? t) (> (bitwise-and (type-mask t) mask/no-values) 0)) ; The no-values type has one element, the rail of length zero. ; The two-or-more type consists of all rails of length two ; or more. (define mask/no-values (type-mask empty-rail-type)) (define mask/two-or-more (new-type-bit)) (define mask/&rest (bitwise-ior (type-mask empty-rail-type) mask/two-or-more)) (table-set! mask->name-table mask/no-values ':no-values) (define value-type (mask->type (bitwise-not (- *mask* 1)))) (set-type-name! value-type ':value) (define mask/value (type-mask value-type)) (define (value-type? t) (let ((m (type-mask t))) (= (bitwise-and m mask/value) m))) (define any-values-type (make-rest-type value-type)) (set-type-name! any-values-type ':values) (define any-arguments-type any-values-type) (define mask/one-or-more (bitwise-ior mask/value mask/two-or-more)) ; -------------------- ; Lattice operations. ; Equivalence (define (same-type? t1 t2) (or (eq? t1 t2) (and (= (type-mask t1) (type-mask t2)) (let ((more1 (type-more t1)) (more2 (type-more t2))) (if more1 (and more2 (if (eq? (cdr more1) t1) (eq? (cdr more2) t2) (if (eq? (cdr more2) t2) #f (and (same-type? (car more1) (car more2)) (same-type? (cdr more1) (cdr more2)))))) (not more2))) (let ((info1 (type-info t1)) (info2 (type-info t2))) (or (eq? info1 info2) (and (pair? info1) (pair? info2) (same-type? (car info1) (car info2)) ;Procedure (same-type? (cadr info1) (cadr info2)) (eq? (caddr info1) (caddr info2)))))))) (define (subtype? t1 t2) ;*** optimize later (same-type? t1 (meet-type t1 t2))) ; (mask->type mask/procedure) represents the TOP of the procedure ; subhierarchy. (define (meet-type t1 t2) (if (same-type? t1 t2) t1 (let ((m (bitwise-and (type-mask t1) (type-mask t2)))) (cond ((> (bitwise-and m mask/two-or-more) 0) (meet-rail t1 t2)) ((eq? (type-info t1) (type-info t2)) (make-type m #f (type-info t1))) ((> (bitwise-and m mask/other) 0) (let ((i1 (other-type-info t1)) (i2 (other-type-info t2))) (if (and i1 i2) (mask->type (bitwise-and m (bitwise-not mask/other))) (make-type m #f (or i1 i2))))) ((> (bitwise-and m mask/procedure) 0) (meet-procedure m t1 t2)) (else (mask->type m)))))) (define (other-type-info t) (let ((i (type-info t))) (if (pair? i) #f i))) (define (p name x) (write `(,name ,x)) (newline) x) (define (meet-rail t1 t2) (let ((t (meet-type (head-type t1) (head-type t2)))) (if (and (rest-type? t1) (rest-type? t2)) (make-rest-type t) (rail-type t (meet-type (tail-type t1) (tail-type t2)))))) ; Start with these assumptions: ; ; . (meet? t1 t2) == (not (bottom-type? (meet-type t1 t2))) ; . (subtype? t1 t2) == (same-type? t1 (meet-type t1 t2)) ; . (subtype? t1 t2) == (same-type? t2 (join-type t1 t2)) ; . We signal a type error if not (intersect? have want). ; . We infer the type of a parameter by intersecting the want-types ; of all definitely-reached points of use. ; ; 1. If both types are nonrestrictive, we have to JOIN both domains ; and codomains (if we are to avoid conjunctive types). ; ; (+ (f 1) (car (f 'a))) [reconstructing type of f by computing meet of all contexts] ; => meet (proc (:integer) :number nonr) (proc (:symbol) :pair nonr) ; => (proc ((join :integer :symbol) (join :number :pair)) nonr), yes? ; ; 2. If both types are restrictive, we need to MEET both domains and ; codomains. ; ; (define (foo) 3), (export (foo (proc (:value) :value))) ; Error - disjoint domains. ; ; (define (foo) 'baz), (export (foo (proc () :number))) ; Error - disjoint codomains. ; ; 3. If one is restrictive and the other isn't then we still need to ; MEET on both sides. ; ; (with-output-to-file "foo" car) ; => meet (proc () :any nonr), (proc (:pair) :value restr) ; => Error - disjoint domains. ; ; (frob (lambda () 'a)) where (define (frob f) (+ (f) 1)) ; => meet (proc () :symbol restr), (proc () :number nonr) ; => Error - disjoint codomains. ; ; Does export checking look for (intersect? want have), or for ; (subtype? want have) ? We should be able to narrow something as we ; export it, but not widen it. ; ; (define (foo . x) 3), (export (foo (proc (value) value))) ; No problem, since the domain of the first contains the domain of the second. ; ; (define (foo x . x) (+ x 3)), (export (foo (proc (value) value))) ; Dubious; the domains intersect but are incomparable. The meet ; should be (proc (number) number). ; ; (define (foo x) (numerator x)), (export (foo (proc (real) integer))) ; This is dubious, since the stated domain certainly contains values ; that will be rejected. (But then, what about divide by zero, or ; vector indexing?) ; ; (define (foo x) (numerator x)), (export (foo (proc (integer) integer))) ; This should definitely be OK. (define (meet-procedure m t1 t2) (let ((dom1 (procedure-type-domain t1)) (dom2 (procedure-type-domain t2)) (cod1 (procedure-type-codomain t1)) (cod2 (procedure-type-codomain t2))) (cond ((or (restrictive? t1) (restrictive? t2)) (let ((dom (meet-type dom1 dom2)) (cod (meet-type cod1 cod2))) (if (or (bottom-type? dom) (and (bottom-type? cod) (not (bottom-type? cod1)) ;uck (not (bottom-type? cod2)))) (mask->type (bitwise-and m (bitwise-not mask/procedure))) (make-procedure-type m dom cod #t)))) ((and (subtype? dom2 dom1) (subtype? cod2 cod1)) ;; exists x : dom1 s.t. (f x) : cod1 adds no info (make-procedure-type m dom2 cod2 #f)) (else ;; Arbitrary choice. (make-procedure-type m dom1 cod1 #f))))) ; MEET? is the operation used all the time by the compiler. We want ; getting a yes answer to be as fast as possible. We could do ; ; (define (meet? t1 t2) (not (bottom-type? (meet-type t1 t2)))) ; ; but that would be too slow. (define (meet? t1 t2) (or (eq? t1 t2) (let ((m (bitwise-and (type-mask t1) (type-mask t2)))) (cond ((= m mask/two-or-more) (and (meet? (head-type t1) (head-type t2)) (meet? (tail-type t1) (tail-type t2)))) ((= m 0) #f) ((eq? (type-info t1) (type-info t2)) #t) ((= m mask/other) (not (and (other-type-info t1) (other-type-info t2)))) ((= m mask/procedure) (meet-procedure? t1 t2)) (else #t))))) (define (meet-procedure? t1 t2) (if (or (restrictive? t1) (restrictive? t2)) (and (meet? (procedure-type-domain t1) (procedure-type-domain t2)) (meet? (procedure-type-codomain t1) (procedure-type-codomain t2))) #t)) ; Join (define (join-type t1 t2) (if (same-type? t1 t2) t1 (let ((m (bitwise-ior (type-mask t1) (type-mask t2)))) (if (> (bitwise-and m mask/two-or-more) 0) (join-rail t1 t2) (let ((info1 (type-info t1)) (info2 (type-info t2))) (cond ((equal? info1 info2) (make-type m #f (type-info t1))) ((> (bitwise-and m mask/other) 0) (make-type m #f #f)) ((> (bitwise-and m mask/procedure) 0) (join-procedure m t1 t2)) (else (error "This shouldn't happen" t1 t2)))))))) (define (join-rail t1 t2) (let ((t (join-type (head-type t1) (head-type t2)))) (if (and (rest-type? t1) (rest-type? t2)) (make-rest-type t) (rail-type t (if (type-more t1) (if (type-more t2) (join-type (tail-type t1) (tail-type t2)) (tail-type t1)) (tail-type t2)))))) ; This is pretty gross. (define (join-procedure m t1 t2) (if (procedure-type? t1) (if (procedure-type? t2) (let ((dom1 (procedure-type-domain t1)) (dom2 (procedure-type-domain t2)) (cod1 (procedure-type-codomain t1)) (cod2 (procedure-type-codomain t2))) (make-procedure-type m (join-type dom1 dom2) ;Error when outside here (join-type cod1 cod2) (and (restrictive? t1) (restrictive? t2)))) (make-type m #f (type-info t1))) (make-type m #f (type-info t2)))) ; -------------------- ; Value types. ; First, the ten indivisible number types. (define number-hierarchy '(:integer :rational :real :complex :number)) (let loop ((names number-hierarchy) (exact bottom-type) (inexact bottom-type)) (if (null? names) (begin (set-type-name! exact ':exact) (set-type-name! inexact ':inexact)) (let* ((exact (join-type exact (new-atomic-type))) (inexact (join-type inexact (new-atomic-type)))) (set-type-name! (join-type exact inexact) (car names)) (loop (cdr names) exact inexact)))) (define integer-type (name->type ':integer)) (define rational-type (name->type ':rational)) (define real-type (name->type ':real)) (define complex-type (name->type ':complex)) (define number-type (name->type ':number)) (define exact-type (name->type ':exact)) (define inexact-type (name->type ':inexact)) (define exact-integer-type (meet-type integer-type exact-type)) (set-type-name! exact-integer-type ':exact-integer) ; Next, all the others. (define boolean-type (named-atomic-type ':boolean)) (define pair-type (named-atomic-type ':pair)) (define null-type (named-atomic-type ':null)) (define record-type (named-atomic-type ':record)) (define any-procedure-type (named-atomic-type ':procedure)) ; ??? ; (define procedure-nonbottom-type (new-atomic-type)) ; (define procedure-bottom-type (new-atomic-type)) ; (define mask/procedure (meet procedure-nonbottom-type procedure-bottom-type)) ; OTHER-VALUE-TYPE is a catchall for all the other ones we don't ; anticipate (for now including string, vector, char, etc.). (define other-value-type (named-atomic-type ':other)) (define mask/other (type-mask other-value-type)) (define (make-other-type id) (let ((t (make-type mask/other #f id))) (set-type-name! t id) t)) (define char-type (make-other-type ':char)) (define unspecific-type (make-other-type ':unspecific)) (define string-type (make-other-type ':string)) (define symbol-type (make-other-type ':symbol)) (define vector-type (make-other-type ':vector)) (define escape-type (make-other-type ':escape)) (define structure-type (make-other-type ':structure)) ; -------------------- ; Procedures. (define mask/procedure (type-mask any-procedure-type)) (define (procedure-type dom cod r?) (make-procedure-type mask/procedure dom cod r?)) (define (make-procedure-type m dom cod r?) (make-type m #f (if (and (not r?) (same-type? dom value-type) (same-type? cod value-type)) #f ;LUB of all procedure types (list dom cod r?)))) (define (procedure-type-domain t) (let ((info (type-info t))) (if (pair? info) (car info) any-values-type))) (define (procedure-type-codomain t) (let ((info (type-info t))) (if (pair? info) (cadr info) any-values-type))) (define (restrictive? t) (let ((info (type-info t))) (if (pair? info) (caddr info) #f))) ; -------------------- ; Conversion to and from S-expression. (define (sexp->type x r?) (cond ((symbol? x) (name->type x)) ((pair? x) (case (car x) ((some-values) (sexp->values-type (cdr x) #t r?)) ((proc) (let ((r? (if (or (null? (cdddr x)) (eq? (cadddr x) r?)) r? (not r?)))) (procedure-type (sexp->values-type (cadr x) #t (not r?)) (sexp->type (caddr x) r?) r?))) ((meet) (if (null? (cdr x)) bottom-type (let ((l (map (lambda (x) (sexp->type x r?)) (cdr x)))) (reduce meet-type (car l) (cdr l))))) ((join) (let ((l (map (lambda (x) (sexp->type x r?)) (cdr x)))) (reduce join-type (car l) (cdr l)))) ((mask->type) (mask->type (cadr x))) (else (error "unrecognized type" x)))) (else (error "unrecognized type" x)))) (define (sexp->values-type l req? r?) (cond ((null? l) empty-rail-type) ((eq? (car l) '&rest) (make-rest-type (sexp->type (cadr l) r?))) ((eq? (car l) '&opt) (sexp->values-type (cdr l) #f r?)) (else (let ((t (sexp->type (car l) r?))) (rail-type (if req? t (make-optional-type t)) (sexp->values-type (cdr l) req? r?)))))) ; Convert type to S-expression (define (type->sexp t r?) (if (> (bitwise-and (type-mask t) mask/&rest) 0) (if (same-type? t any-values-type) ':values `(some-values ,@(rail-type->sexp t r?))) (let ((j (disjoin-type t))) (cond ((null? j) ':error) ((null? (cdr j)) (atomic-type->sexp (car j) r?)) (else `(join ,@(map (lambda (t) (atomic-type->sexp t r?)) j))))))) (define (atomic-type->sexp t r?) (let ((m (type-mask t))) (cond ((and (not (type-info t)) (table-ref mask->name-table m))) ((= m mask/other) (or (type-info t) ':value)) ;not quite ((= m mask/procedure) (let ((r (restrictive? t))) `(proc ,(rail-type->sexp (procedure-type-domain t) (not r)) ,(type->sexp (procedure-type-codomain t) r) ,@(if (eq? r r?) '() `(,r))))) ((type-info t) `(ill-formed ,(type-mask t) ,(type-info t))) ((subtype? t exact-type) `(meet :exact ,(type->sexp (mask->type (let ((m (type-mask t))) (bitwise-ior m (arithmetic-shift m 1)))) #t))) ((subtype? t inexact-type) `(meet :inexact ,(type->sexp (mask->type (let ((m (type-mask t))) (bitwise-ior m (arithmetic-shift m -1)))) #t))) ;; ((meet? t number-type) ...) (else `(mask->type ,(type-mask t)))))) (define (rail-type->sexp t r?) (let recur ((t t) (prev-req? #t) (r? r?)) (cond ((empty-rail-type? t) '()) ((rest-type? t) `(&rest ,(type->sexp (head-type-really t) r?))) ((optional-type? t) (let ((tail (cons (type->sexp (head-type-really t) r?) (recur (tail-type t) #f r?)))) (if prev-req? `(&opt ,@tail) tail))) (else (cons (type->sexp (head-type t) r?) (recur (tail-type t) #t r?)))))) ; Decompose a type into components (define (disjoin-type t) (cond ((bottom-type? t) '()) ((and (not (type-info t)) (table-ref mask->name-table (type-mask t))) (list t)) ((meet? t other-value-type) (cons (meet-type t other-value-type) (disjoin-rest t mask/other))) ((meet? t any-procedure-type) (cons (meet-type t any-procedure-type) (disjoin-rest t mask/procedure))) ((meet? t number-type) (cons (meet-type t number-type) (disjoin-rest t mask/number))) (else (do ((i 1 (arithmetic-shift i 1))) ((> (bitwise-and (type-mask t) i) 0) (cons (mask->type i) (disjoin-rest t i))))))) (define (disjoin-rest t mask) (disjoin-type (mask->type (bitwise-and (type-mask t) (bitwise-not mask))))) (define mask/number (type-mask number-type)) ; -------------------- ; obsolescent? see lambda and values reconstructors in recon.scm (define (make-some-values-type types) (if (null? types) empty-rail-type (rail-type (car types) (make-some-values-type (cdr types))))) (define-syntax proc (syntax-rules () ((proc (?type ...) ?cod) (procedure-type (some-values ?type ...) ?cod #t)) ((proc (?type ...) ?cod ?r) (procedure-type (some-values ?type ...) ?cod ?r)))) (define-syntax some-values (syntax-rules (&opt &rest) ((some-values) empty-rail-type) ((some-values &opt) empty-rail-type) ((some-values ?t) ?t) ((some-values &rest ?t) (make-rest-type ?t)) ((some-values &opt &rest ?t) (make-rest-type ?t)) ((some-values &opt ?t1 . ?ts) (rail-type (make-optional-type ?t1) (some-values &opt . ?ts))) ((some-values ?t1 . ?ts) (rail-type ?t1 (some-values . ?ts))))) (define (procedure-type? t) (= (type-mask t) mask/procedure)) (define (fixed-arity-procedure-type? t) (and (procedure-type? t) (let loop ((d (procedure-type-domain t))) (cond ((empty-rail-type? d) #t) ((optional-type? d) #f) (else (loop (tail-type d))))))) (define (procedure-type-arity t) (do ((d (procedure-type-domain t) (tail-type d)) (i 0 (+ i 1))) ((empty-rail-type? d) i) (if (optional-type? d) (error "this shouldn't happen" t d)))) (define (procedure-type-argument-types t) (let recur ((d (procedure-type-domain t))) (cond ((empty-rail-type? d) '()) ((optional-type? d) (call-error "lossage" procedure-type-argument-types t)) (else (cons (head-type d) (recur (tail-type d)))))))