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