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