git.fiddlerwoaroof.com
type/type-decl.scm
4e987026
 ;;; 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))