; 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)))))))
