git.fiddlerwoaroof.com
Raw Blame History
;;; This deals with declarations (let & letrec).  The input is a list of
;;; declarations (valdefs) which may contain recursive-decl-groups, as
;;; introduced in dependency analysis.  This function alters the list
;;; of non-generic type variables.  Expressions containing declarations
;;; need to rebind the non-generic list around the decls and all expressions
;;; within their scope.

;;; This returns an updated decl list with recursive decl groups removed.

(define (type-decls decls)
  (cond ((null? decls)
	 '())
	((is-type? 'recursive-decl-group (car decls))
	 (let ((d (recursive-decl-group-decls (car decls))))
 	   (type-recursive d)
	   (append d (type-decls (cdr decls)))))
	(else
	 (type-non-recursive (car decls))
	 (cons (car decls)
	       (type-decls (cdr decls))))))

;;; This typechecks a mutually recursive group of declarations (valdefs).
;;; Generate a monomorphic variable for each declaration and unify it with
;;; the lhs of the decl.  The variable all-vars collects all variables defined
;;; by the declaration group.  Save the values of placeholders and ng-list
;;; before recursing.

;;; The type of each variable is marked as recursive.

(define (type-recursive decls)
  (let ((old-ng (dynamic *non-generic-tyvars*))
	(old-placeholders (dynamic *placeholders*))
	(all-vars '())
	(new-tyvars '())
	(decls+tyvars '()))
    ;; on a type error set all types to `a' and give up.
    (setf (dynamic *placeholders*) '())
    (recover-type-error 
       (lambda (r)
	 (make-dummy-sigs decls)
	 (setf (dynamic *dict-placeholders*) old-placeholders)
	 (funcall r))
       ;; Type the lhs of each decl and then mark each variable bound
       ;; in the decl as recursive.
       (dolist (d decls)
        (fresh-type lhs-type
	  (push lhs-type (dynamic *non-generic-tyvars*))
	  (push lhs-type new-tyvars)
	  (type-decl-lhs d lhs-type)
	  (push (tuple d lhs-type) decls+tyvars))
	(dolist (var-ref (collect-pattern-vars (valdef-lhs d)))
	  (let ((var (var-ref-var var-ref)))
	    (push var all-vars)
	    (setf (var-type var)
		  (make recursive-type (type (var-type var))
			(placeholders '()))))))

;;; This types the decl right hand sides.  Each rhs type is unified with the
;;; tyvar corresponding to the lhs.  Before checking the signatures, the
;;; ng-list is restored.  

       (dolist (d decls+tyvars)
	 (let ((rhs-type (type-decl-rhs (tuple-2-1 d)))
	       (lhs-type (tuple-2-2 d)))
	   (type-unify lhs-type rhs-type
		 (type-mismatch (tuple-2-1 d)
			  "Decl type mismatch" lhs-type rhs-type))))
       (setf (dynamic *non-generic-tyvars*) old-ng)
       (let ((sig-contexts (check-user-signatures all-vars)))

;;; This generalizes the signatures of recursive decls.  First, the
;;; context of the declaration group is computed.  Any tyvar in the
;;; bodies with a non-empty context must appear in all signatures that
;;; are non-ambiguous.
	  
	 (let* ((all-tyvars (collect-tyvars/l new-tyvars))
		(overloaded-tyvars '()))
	   (dolist (tyvar all-tyvars)
	      (when (and (ntyvar-context tyvar) (not (non-generic? tyvar)))
		 (push tyvar overloaded-tyvars)))
	   (reconcile-sig-contexts overloaded-tyvars sig-contexts)
	 ;; We should probably also emit a warning about inherently
	 ;; ambiguous decls.
	   (when (and overloaded-tyvars
		      (apply-pattern-binding-rule? decls))
		 (setf (dynamic *non-generic-tyvars*)
		       (do-pattern-binding-rule
			decls overloaded-tyvars old-ng))
		 (setf overloaded-tyvars '()))
	 ;; The next step is to compute the signatures of the defined
	 ;; variables and to define all recursive placeholders.  When
	 ;; there is no context the placeholders become simple var refs.
	 ;; and the types are simply converted.
	   (cond ((null? overloaded-tyvars)
		  (dolist (var all-vars)
		    (let ((r (var-type var)))
		      (setf (var-type var) (recursive-type-type (var-type var)))
		      (dolist (p (recursive-type-placeholders r))
		        (setf (recursive-placeholder-exp p)
			      (**var/def var)))
		      (generalize-type var))))
	 ;; When the declaration has a context things get very hairy.
	 ;; First, grap the recursive placeholders before generalizing the
	 ;; types.
		 (else
		  ;; Mark the overloaded tyvars as read-only.  This prevents
		  ;; signature unification from changing the set of tyvars
		  ;; defined in the mapping.
		  (dolist (tyvar overloaded-tyvars)
		     (setf (ntyvar-read-only? tyvar) '#t))
		  (let ((r-placeholders '()))
		    (dolist (var all-vars)
		     (let ((rt (var-type var)))
		      (dolist (p (recursive-type-placeholders rt))
			(push p r-placeholders))
		      (setf (var-type var) (recursive-type-type rt))))
	 ;; Now compute a signature for each definition and do dictionary
	 ;; conversion.  The var-map defines the actual parameter associated
	 ;; with each of the overloaded tyvars.
		    (let ((var-map (map (lambda (decl)
					 (tuple (decl-var decl)
					  (generalize-overloaded-type
					   decl overloaded-tyvars)))
					decls)))
	 ;; Finally discharge each recursive placeholder.
		      (dolist (p r-placeholders)
			(let ((ref-to (recursive-placeholder-var p))
			      (decl-from
			       (search-enclosing-decls
				 (recursive-placeholder-enclosing-decls p)
				 decls)))
			  (setf (recursive-placeholder-exp p)
				(recursive-call-code decl-from ref-to var-map)))
			)))))
	   (setf (dynamic *placeholders*)
		 (process-placeholders
		  (dynamic *placeholders*) old-placeholders decls)))))))

;;; Non-recursive decls are easier.  Save the placeholders, use a fresh type
;;; for the left hand side, check signatures, and generalize.

(define (type-non-recursive decl)
 (remember-context decl
  (fresh-type lhs-type
    (let ((old-placeholders (dynamic *placeholders*))
	  (all-vars (map (lambda (x) (var-ref-var x))
			    (collect-pattern-vars (valdef-lhs decl)))))
     (setf (dynamic *placeholders*) '())
     (recover-type-error
      (lambda (r)
        (make-dummy-sigs (list decl))
	(setf (dynamic *placeholders*) old-placeholders)
        (funcall r))
      (type-decl-lhs decl lhs-type)
      (let ((rhs-type (type-decl-rhs decl)))
	(type-unify lhs-type rhs-type
           (type-mismatch decl
	       "Decl type mismatch" lhs-type rhs-type)))
      (check-user-signatures all-vars)
      (let ((all-tyvars (collect-tyvars lhs-type))
	    (overloaded-tyvars '()))
	(dolist (tyvar all-tyvars)
	  (when (ntyvar-context tyvar)
	     (push tyvar overloaded-tyvars)))
	(when (and overloaded-tyvars
		   (apply-pattern-binding-rule? (list decl)))
	 (setf (dynamic *non-generic-tyvars*)
	   (do-pattern-binding-rule
	    (list decl) overloaded-tyvars (dynamic *non-generic-tyvars*)))
	 (setf overloaded-tyvars '()))
	(if (null? overloaded-tyvars)
	    (dolist (var all-vars)
	      (generalize-type var))
	    (generalize-overloaded-type decl '()))
	(setf (dynamic *placeholders*)
	      (process-placeholders
	       (dynamic *placeholders*) old-placeholders (list decl)))))))))

;;; These functions type check definition components.

;;; This unifies the type of the lhs pattern with a type variable.

(define (type-decl-lhs object type)
 (dynamic-let ((*enclosing-decls* (cons object (dynamic *enclosing-decls*))))
  (remember-context object
   (type-check valdef lhs pat-type
    (type-unify type pat-type #f)))))


;;; This types the right hand side.  The *enclosing-decls* variable is
;;; used to keep track of which decl the type checker is inside.  This
;;; is needed for both defaulting (to find which module defaults apply)
;;; and recursive types to keep track of the dictionary parameter variables
;;; for recursive references.

(define (type-decl-rhs object)
 (dynamic-let ((*enclosing-decls* (cons object (dynamic *enclosing-decls*))))
  (remember-context object
   (type-check/unify-list valdef definitions res-type
       (type-mismatch/list object
	   "Right hand sides have different types")
       res-type))))


;;; This is similar to typing lambda.

(define-type-checker single-fun-def
  (fresh-monomorphic-types (length (single-fun-def-args object)) tyvars
    (type-check/list single-fun-def args arg-types
      (unify-list tyvars arg-types)
      (type-check/decls single-fun-def where-decls
        (type-check/unify-list single-fun-def rhs-list rhs-type
           (type-mismatch/list object
			"Bodies have incompatible types")
	  (return-type object (**arrow/l-2 arg-types rhs-type)))))))


;;; These functions are part of the generalization process.

;;; This function processes user signature declarations for the set of
;;; variables defined in a declaration.  Since unification of one signature
;;; may change the type associated with a previously verified signature,
;;; signature unification is done twice unless only one variable is
;;; involved.  The context of the signatures is returned to compare
;;; with the overall context of the declaration group.

(define (check-user-signatures vars)
  (cond ((null? (cdr vars))
	 (let* ((var (car vars))
		(sig (var-signature var)))
	   (if (eq? sig '#f)
	       '()
	       (list (tuple var (check-var-signature var sig))))))
	(else
	 (let ((sigs '()))
	   (dolist (var vars)
	     (let ((sig (var-signature var)))
	       (unless (eq? sig '#f)
		 (check-var-signature var sig))))
	   (dolist (var vars)
	     (let ((sig (var-signature var)))
	       (unless (eq? sig '#f)
		 (push (tuple var (check-var-signature var sig)) sigs))))
	   sigs))))


(define (check-var-signature var sig)
  (mlet (((sig-type sig-vars) (instantiate-gtype/newvars sig)))
    (dolist (tyvar sig-vars)
      (setf (ntyvar-read-only? tyvar) '#t))
    (type-unify (remove-recursive-type (var-type var)) sig-type
	     (signature-mismatch var))
    (dolist (tyvar sig-vars)
      (setf (ntyvar-read-only? tyvar) '#f))
    sig-vars))
  
;;; Once the declaration context is computed, it must be compared to the
;;; contexts given by the user.  All we need to check is that all tyvars
;;; constrained in the user signatures are also in the decl-context.
;;; All user supplied contexts are correct at this point - we just need
;;; to see if some ambiguous portion of the context exists.

;;; This error message needs work.  We need to present the contexts.

(define (reconcile-sig-contexts overloaded-tyvars sig-contexts)
  (dolist (sig sig-contexts)
    (let ((sig-vars (tuple-2-2 sig)))
      (dolist (d overloaded-tyvars)
	(when (not (memq d sig-vars))
	  (type-error
"Declaration signature has insufficiant context in declaration~%~A~%"
            (tuple-2-1 sig)))))))

;;; This is used for noisy type inference

(define (report-typing var)
 (when (memq 'type (dynamic *printers*))
  (let* ((name (symbol->string (def-name var))))
    (when (not (or (string-starts? "sel-" name)
		   (string-starts? "i-" name)
		   (string-starts? "default-" name)
		   (string-starts? "dict-" name)))
      (format '#t "~A :: ~A~%" var (var-type var))))))

;;; This is used during error recovery.  When a type error occurs, all
;;; variables defined in the enclosing declaration are set to type `a'
;;; and typing is resumed.

(define (make-dummy-sigs decls)
  (let ((dummy-type (make gtype (context '(()))
			        (type (**gtyvar 0)))))
    (dolist (d decls)
      (dolist (var-ref (collect-pattern-vars (valdef-lhs d)))
        (let ((var (var-ref-var var-ref)))
	  (setf (var-type var) dummy-type))))))


;;; This is used to generalize the variable signatures.  If there is
;;; an attached signature, the signature is used.  Otherwise the ntype
;;; is converted to a gtype.

(define (generalize-type var)
  (if (eq? (var-signature var) '#f)
      (setf (var-type var) (ntype->gtype (var-type var)))
      (setf (var-type var) (var-signature var)))
  (report-typing var))
      
;;; For overloaded types, it is necessary to map the declaration context
;;; onto the generalized type.  User signatures may provide different but
;;; equivilant contexts for different declarations in a decl goup.

;;; The overloaded-vars argument allows ambiguous contexts.  This is not
;;; needed for non-recursive vars since the context cannot be ambiguous.

(define (generalize-overloaded-type decl overloaded-vars)
  (let* ((var (decl-var decl))
	 (sig (var-signature var))
	 (new-tyvars '()))
    (cond ((eq? sig '#f)
	   (mlet (((gtype tyvars)
		   (ntype->gtype/env (var-type var) overloaded-vars)))
	      (setf (var-type var) gtype)
	      (setf new-tyvars tyvars)))
	  (else
	   (mlet (((ntype tyvars) (instantiate-gtype/newvars sig)))
	     (unify ntype (var-type var))
	     (setf (var-type var) sig)
	     (setf new-tyvars (prune/l tyvars)))))
    (report-typing var)
    (dictionary-conversion/definition decl new-tyvars)
    new-tyvars))

(define (remove-recursive-type ty)
  (if (recursive-type? ty)
      (recursive-type-type ty)
      ty))