; Copyright (c) 1994 by Richard Kelsey.  See file COPYING.

; Type checking nodes.

; Entry point

; Because NODE is not the car of a pair, this depends on lambdas not being
; coerceable and literal nodes being coerced in place (instead of having a
; call inserted).

(define (infer-definition-type node name)
  (set! *currently-checking* name)
  (let ((res (cond ((literal-node? node)
		    (infer-literal-type node name))
		   ((lambda-node? node)
		    (infer-type node 0))
		   ((name-node? node)
		    (get-global-type (binding-place (node-ref node 'binding))))
		   (else
		    (bug "definition value is not a value node ~S" node)))))
    (set! *currently-checking* #f)
    res))

(define (infer-literal-type node name)
  (let ((value (node-form node)))
    (cond ((vector? value)
	   (let ((uvar (make-uvar name -1)))
	     (do ((i 0 (+ i 1)))
		 ((>= i (vector-length value)))
	       (unify! uvar (type-check-thing (vector-ref value i)) value))
	     (make-pointer-type (maybe-follow-uvar uvar))))
	  (else
	   (infer-type node 0)))))

(define (type-check-thing thing)
  (if (variable? thing)
      (get-package-variable-type thing)
      (literal-value-type thing)))

(define literal-operator (get-operator 'literal))

(define (make-literal-node value)
  (make-node literal-operator value))

; Get the type of the variable - if it is a type-variable, then create a new
; one and relate the two; if it is a polymorphic pattern, instantiate it.

(define (get-package-variable-type var)
  (let ((rep (variable-type var)))
    (cond ((eq? rep type/undetermined)
	   (let ((type (make-uvar (variable-name var) -1)))
	     (set-variable-type! var type)
	     (set-uvar-source! type var)
	     type))
	  ((type-scheme? rep)
	   (instantiate-type-scheme rep -1))
	  (else
	   rep))))

; Exported

(define (get-variable-type var)
  (let ((rep (variable-type var)))
    (cond ((eq? rep type/undetermined)
	   (bug "lexically bound variable ~S has no type" var))
	  ((type-scheme? rep)
	   (instantiate-type-scheme rep -1))
	  (else
	   rep))))

;----------------------------------------------------------------

(define (infer-type node depth)
  (infer-any-type node depth #f))

(define (infer-any-type node depth return?)
  (let ((type ((operator-table-ref inference-rules (node-operator-id node))
	       node
	       depth
	       return?)))
    (set-node-type! node type)
    (maybe-follow-uvar type)))

(define inference-rules
  (make-operator-table
   (lambda (node depth return?)
     (error "no type inference for node ~S" node))))

(define (define-inference-rule name proc)
  (operator-define! inference-rules name #f proc))

(define-inference-rule 'literal
  (lambda (node depth return?)
    (infer-literal (node-form node) node)))

(define-inference-rule 'quote
  (lambda (node depth return?)
    (infer-literal (cadr (node-form node)) node)))

(define (infer-literal value node)
  (literal-value-type value))

(define (literal-value-type value)
  (or (maybe-literal-value-type value)
      (error "don't know type of literal ~S" value)))
      
(define (maybe-literal-value-type value)
  (cond ((boolean? value)
	 type/boolean)
	((char? value)
	 type/char)
	((integer? value)
	 type/integer)
	((string? value)
	 type/string)
	(((structure-ref eval-node unspecific?) value)
	 type/null)
	((input-port? value)
	 type/input-port)
	((output-port? value)
	 type/output-port)
	((external-value? value)
	 (external-value-type value))
	((external-constant? value)
         type/integer)
	(else
	 #f)))

(define-inference-rule 'unspecific
  (lambda (node depth return?)
    type/null))

(define-inference-rule 'lambda
  (lambda (node depth return?)
    (let* ((uid (unique-id))
	   (exp (node-form node))
	   (var-types (map (lambda (name-node)
			     (initialize-name-node-type name-node uid depth))
			   (cadr exp)))
	   (result (infer-any-type (caddr exp) depth #t)))
      ; stash the return type
      (set-lambda-node-return-type! node result)
      (make-arrow-type var-types result))))
	
; Create a new type variable for VAR.

(define (initialize-name-node-type node uid depth)
  (let ((uvar (make-uvar (node-form node) depth uid)))
    (set-node-type! node uvar)
    (set-uvar-source! uvar node)
    uvar))

; Get the type of the variable - if it is a type-variable, then create a new
; one and relate the two; if it is a polymorphic pattern, instantiate it.
; How to pass the source?

(define-inference-rule 'name
  (lambda (node depth return?)
    (let ((type (if (node-ref node 'binding)
		    (get-global-type (binding-place (node-ref node 'binding)))
		    (node-type node))))
      (if (not type)
	  (bug "name node ~S has no type" node))
      (if (type-scheme? type)
	  (instantiate-type-scheme type depth)
	  type))))

(define-inference-rule 'primitive
  (lambda (node depth return?)
    (let ((type (get-global-type (cdr (node-form node)))))
      (if (type-scheme? type)
	  (instantiate-type-scheme type depth)
	  type))))

; If no type is present, create a type variable.

(define (get-global-type value)
  (if (location? value)
      (literal-value-type (contents value))
      (let ((has (maybe-follow-uvar (variable-type value))))
	(cond ((not (eq? has type/undetermined))
	       has)
	      (else
	       (let ((type (make-uvar (variable-name value) -1)))
		 (set-variable-type! value type)
		 (set-uvar-source! type value)
		 type))))))

(define-inference-rule 'set!
  (lambda (node depth return?)
    (let* ((exp (node-form node))
	   (type (infer-type (caddr exp) depth))
	   (binding (node-ref (cadr exp) 'binding)))
      (if (not binding)
	  (error "SET! on a local variable ~S" (schemify node)))
      (unify! type (variable-type (binding-place binding)) node)
      type/null)))

(define-inference-rule 'call
  (lambda (node depth return?)
    (rule-for-calls (node-form node) node depth return?)))

(define-inference-rule 'goto
  (lambda (node depth return?)
    (rule-for-calls (cdr (node-form node)) node depth return?)))

(define (rule-for-calls proc+args node depth return?)
  (let ((proc (car proc+args))
	(args (cdr proc+args)))
    (cond ((lambda-node? proc)
	   (rule-for-let node depth proc args return?))
	  ((primitive-node? proc)
	   (rule-for-primitives node depth (node-form proc) args return?))
	  (else
	   (rule-for-unknown-calls node depth proc+args return?)))))

(define name-node? (node-predicate 'name))
(define lambda-node? (node-predicate 'lambda))
(define literal-node? (node-predicate 'literal))
(define primitive-node? (node-predicate 'primitive))

(define (rule-for-let node depth proc args return?)
  (let ((depth (+ depth 1))
	(uid (unique-id))
	(proc (node-form proc)))
    (do ((names (cadr proc) (cdr names))
	 (vals args (cdr vals)))
	((null? names))
      (let ((type (schemify-type (infer-type (car vals) depth) depth)))
	(if (type-scheme? type)
	    (set-node-type! (car names) type)
	    (unify! (initialize-name-node-type (car names) uid depth)
		    type
		    node))))
    (infer-any-type (caddr proc) depth return?)))

(define (rule-for-primitives node depth primitive args return?)
  ((primitive-inference-rule primitive)
     args node depth return?))

(define (rule-for-unknown-calls node depth proc+args return?)
  (let ((proc-type (infer-type (car proc+args) depth))
	(arg-types (infer-types (cdr proc+args) depth))
	(return-type (if return?
			 (make-tuple-uvar 'result depth)
			 (make-uvar 'result depth))))
    (unify! proc-type
	    (make-arrow-type arg-types return-type)
	    node)
;    (if (= 244 (uvar-id return-type))
;	(breakpoint "rule-for-unknown-calls"))
    (maybe-follow-uvar return-type)))
  
(define (infer-types nodes depth)
  (map (lambda (node)
	 (infer-type node depth))
       nodes))

(define-inference-rule 'begin
  (lambda (node depth return?)
    (let loop ((exps (cdr (node-form node))) (type type/unit))
      (if (null? exps)
	  type
	  (loop (cdr exps)
		(infer-any-type (car exps)
				depth
				(or (not (null? (cdr exps)))
				    return?)))))))

; It would be nice if we could just try to unify the two arms and return
; type/unit if we lost, but unification has side-effects.

(define-inference-rule 'if
  (lambda (node depth return?)
    (let* ((args (cdr (node-form node)))
	   (true-type (infer-any-type (cadr args) depth return?))
	   (false-type (infer-any-type (caddr args) depth return?)))
      (unify! (infer-type (car args) depth) type/boolean node)
      (cond ((eq? true-type type/null)
	     false-type)
	    ((eq? false-type type/null)
	     true-type)
	    (else
	     (unify! true-type false-type node)
	     true-type)))))

(define-inference-rule 'letrec
  (lambda (node depth return?)
    (let ((form (node-form node))
	  (depth (+ depth 1))
	  (uid (unique-id)))
      (let ((names (map car (cadr form)))
	    (vals (map cadr (cadr form))))
	(for-each (lambda (name)
		    (initialize-name-node-type name uid depth))
		  names)
	(do ((names names (cdr names))
	     (vals vals (cdr vals)))
	    ((null? names))
	  (if (not (lambda-node? (car vals)))
	      (error "LETREC value is not a LAMBDA: ~S" (schemify node)))
	  (unify! (infer-type (car vals) depth)
		  (node-type (car names))
		  node))
	(for-each (lambda (name)
		    (let ((type (schemify-type (node-type name) depth)))
		      (if (type-scheme? type)
			  (set-node-type! name type))))
		  names)
	(infer-any-type (caddr form) depth return?)))))

;--------------------------------------------------

(define (node-type node)
  (maybe-follow-uvar (node-ref node 'type)))

(define (set-node-type! node type)
  (node-set! node 'type type))

(define (lambda-node-return-type node)
  (node-ref node 'return-type))

(define (set-lambda-node-return-type! node type)
  (node-set! node 'return-type type))

;--------------------------------------------------
; Utility procedures used by the inferencers of the various primops.

; Check that the INDEX'th argument of CALL has type TYPE.

(define (check-arg-type args index type depth exp)
  (if (null? args)
      (begin
	(format #t "Wrong number of arguments in ~S~% " (schemify exp))
	(if *currently-checking*
	    (format #t "~% while reconstructing the type of '~S'" *currently-checking*))
	(error "type problem")))
  (unify! (infer-type (list-ref args index) depth)
	  type
	  exp))