git.fiddlerwoaroof.com
type/unify.scm
4e987026
 
 ;;; File: type/unify.scm   Author: John
 
 ;;; This is the basic unification algorithm used in type checking.
 
 ;;; Unification failure invokes the current type error handler
 
 ;;; Start by removing instantiated type variables from the type.
 
 (define (unify type1 type2)
   (unify-1 (prune type1) (prune type2)))
 
 ;;; The only real tweak here is the read-only bit on type variables.
 ;;; The rule is that a RO tyvar can be unified only with a generic
 ;;; non-RO tyvar which has the same or more general context.
 
 ;;; Aside from this, this is standard unification except that context
 ;;; propagation is needed when a tyvar with a non-empty context is
 ;;; instantiated.
 
 ;;; If type2 is a tyvar and type1 is not they are switched.
 
 (define (unify-1 type1 type2)
     (cond ((eq? type1 type2)  ;; this catches variable to variable unify
 	   'OK)
 	  ((ntyvar? type1)
 	   (cond ((occurs-in-type type1 type2)
 		  (type-error "Circular type: cannot unify ~A with ~A"
 			      type1 type2))
 		 ((ntyvar-read-only? type1)
 		  (cond ((or (not (ntyvar? type2)) (ntyvar-read-only? type2))
 			 (type-error
 			  "Signature too general: cannot unify ~A with ~A"
 			  type1 type2))
 			(else
 			 (unify-1 type2 type1))))
 		 ((and (ntyvar? type2)
 		       (ntyvar-read-only? type2)
 		       (non-generic? type1))
 		  (type-error
  "Type signature cannot be used: monomorphic type variables present."))
 		 (else
 		  (instantiate-tyvar type1 type2)
 		  (let ((classes (ntyvar-context type1)))
 		    (if (null? classes)
 			'OK
 			(propagate-contexts/ntype type1 type2 classes))))))
 	  ((ntyvar? type2)
 	   (unify-1 type2 type1))
 	  ((eq? (ntycon-tycon type1) (ntycon-tycon type2))
 	   (unify-list (ntycon-args type1) (ntycon-args type2)))
 	  (else
 	   (let ((etype1 (expand-ntype-synonym type1))
 		 (etype2 (expand-ntype-synonym type2)))
 	    (if (same-tycon? (ntycon-tycon etype1) (ntycon-tycon etype2))
 		(unify-list (ntycon-args etype1) (ntycon-args etype2))
 		;; This error message should probably show both the original
 		;; and the expanded types for clarity.
 		(type-error
 		      "Type conflict: type ~A does not match ~A"
 			    etype1 etype2))))))
 
 
 (define-integrable (instantiate-tyvar tyvar val)
   (setf (ntyvar-value tyvar) val))
 
 ;;; This is needed since interface files may leave multiple def's
 ;;; for the same tycon sitting around.
 
 (define (same-tycon? ty1 ty2)
   (or (eq? ty1 ty2)
       (and (eq? (def-name ty1) (def-name ty2))
 	   (eq? (def-module ty1) (def-module ty2)))))
 
 
 ;;; unifies two lists of types pairwise.  Used for tycon args.
 
 (define (unify-list args1 args2)
   (if (null? args1)
       'OK
       (begin (unify-list (cdr args1) (cdr args2))
 	     (unify (car args1) (car args2)))))
 
 ;;; combines a list of types into a single type.  Used in constructs
 ;;; such as [x,y,z] and case expressions.
 
 (define (unify-list/single-type types)
   (when (not (null? types))
     (let ((type (car types)))
       (dolist (type2 (cdr types))
         (unify type type2)))))
 
 ;;; This propagates the context from a just instantiated tyvar to the
 ;;; instantiated value.  If the value is a tycon, instances must be
 ;;; looked up.  If the value is a tyvar, the context is added to that of
 ;;; other tyvar.
 
 ;;; This is used to back out of the unification on errors.  This is a
 ;;; poor mans trail stack!  Without this, error messages get very
 ;;; obscure.
 
 (define *instantiated-tyvar* '())
 
 (define (propagate-contexts/ntype tyvar type classes)
  (dynamic-let ((*instantiated-tyvar* tyvar))
     (propagate-contexts/inner type classes)))
 
 (define (propagate-contexts/inner type classes)
  (let ((type (prune type)))
   (if (ntyvar? type)
       (if (ntyvar-read-only? type)
 	  (if (context-implies? (ntyvar-context type) classes)
 	      'OK ; no need for context propagation here
 	      (begin 
 		(setf (ntyvar-value (dynamic *instantiated-tyvar*)) '#f)
 		(type-error "Signature context is too general")))
 	  (if (null? (ntyvar-context type))
 	      (setf (ntyvar-context type) classes)
 	      (setf (ntyvar-context type)
 		    (merge-contexts classes (ntyvar-context type)))))
       (propagate-contexts-1 (expand-ntype-synonym type) classes))))
 
 ;;; The type has now been expanded.  This propagates each class constraint
 ;;; in turn.
 
 (define (propagate-contexts-1 type classes)
   (dolist (class classes)
      (propagate-single-class type class)))
 
 ;;; Now we have a single class & data type.  Either an instance decl can
 ;;; be found or a type error should be signalled.  Once the instance
 ;;; decl is found, contexts are propagated to the component types.
 
 (define (propagate-single-class type class)
   (let ((instance (lookup-instance (ntycon-tycon type) class)))
     (cond ((eq? instance '#f)
 	   ;; This remove the instantiation which caused the type
 	   ;; error - perhaps stop error propagation & make
 	   ;; error message better.
 	   (setf (ntyvar-value (dynamic *instantiated-tyvar*)) '#f)
 	   (type-error "Type ~A is not in class ~A" type class))
 	  (else
 	   ;; The instance contains a list of class constraints for
 	   ;; each argument.  This loop pairs the argument to the
 	   ;; type constructor with the context required by the instance
 	   ;; decl.
 	   (dolist2 (classes (instance-gcontext instance))
 		    (arg (ntycon-args type))
 	     (propagate-contexts/inner arg classes)))))
   'OK)
 
 ;;; The routines which handle contexts (merge-contexts and context-implies?)
 ;;; are in type-utils.  The occurs check is also there.