; Copyright (c) 1994 by Richard Kelsey. See file COPYING. ; Type checking nodes. ; Entry point ; Because NODE is not the car of a pair, this depends on lambdas not being ; coerceable and literal nodes being coerced in place (instead of having a ; call inserted). (define (infer-definition-type node name) (set! *currently-checking* name) (let ((res (cond ((literal-node? node) (infer-literal-type node name)) ((lambda-node? node) (infer-type node 0)) ((name-node? node) (get-global-type (binding-place (node-ref node 'binding)))) (else (bug "definition value is not a value node ~S" node))))) (set! *currently-checking* #f) res)) (define (infer-literal-type node name) (let ((value (node-form node))) (cond ((vector? value) (let ((uvar (make-uvar name -1))) (do ((i 0 (+ i 1))) ((>= i (vector-length value))) (unify! uvar (type-check-thing (vector-ref value i)) value)) (make-pointer-type (maybe-follow-uvar uvar)))) (else (infer-type node 0))))) (define (type-check-thing thing) (if (variable? thing) (get-package-variable-type thing) (literal-value-type thing))) (define literal-operator (get-operator 'literal)) (define (make-literal-node value) (make-node literal-operator value)) ; Get the type of the variable - if it is a type-variable, then create a new ; one and relate the two; if it is a polymorphic pattern, instantiate it. (define (get-package-variable-type var) (let ((rep (variable-type var))) (cond ((eq? rep type/undetermined) (let ((type (make-uvar (variable-name var) -1))) (set-variable-type! var type) (set-uvar-source! type var) type)) ((type-scheme? rep) (instantiate-type-scheme rep -1)) (else rep)))) ; Exported (define (get-variable-type var) (let ((rep (variable-type var))) (cond ((eq? rep type/undetermined) (bug "lexically bound variable ~S has no type" var)) ((type-scheme? rep) (instantiate-type-scheme rep -1)) (else rep)))) ;---------------------------------------------------------------- (define (infer-type node depth) (infer-any-type node depth #f)) (define (infer-any-type node depth return?) (let ((type ((operator-table-ref inference-rules (node-operator-id node)) node depth return?))) (set-node-type! node type) (maybe-follow-uvar type))) (define inference-rules (make-operator-table (lambda (node depth return?) (error "no type inference for node ~S" node)))) (define (define-inference-rule name proc) (operator-define! inference-rules name #f proc)) (define-inference-rule 'literal (lambda (node depth return?) (infer-literal (node-form node) node))) (define-inference-rule 'quote (lambda (node depth return?) (infer-literal (cadr (node-form node)) node))) (define (infer-literal value node) (literal-value-type value)) (define (literal-value-type value) (or (maybe-literal-value-type value) (error "don't know type of literal ~S" value))) (define (maybe-literal-value-type value) (cond ((boolean? value) type/boolean) ((char? value) type/char) ((integer? value) type/integer) ((string? value) type/string) (((structure-ref eval-node unspecific?) value) type/null) ((input-port? value) type/input-port) ((output-port? value) type/output-port) ((external-value? value) (external-value-type value)) ((external-constant? value) type/integer) (else #f))) (define-inference-rule 'unspecific (lambda (node depth return?) type/null)) (define-inference-rule 'lambda (lambda (node depth return?) (let* ((uid (unique-id)) (exp (node-form node)) (var-types (map (lambda (name-node) (initialize-name-node-type name-node uid depth)) (cadr exp))) (result (infer-any-type (caddr exp) depth #t))) ; stash the return type (set-lambda-node-return-type! node result) (make-arrow-type var-types result)))) ; Create a new type variable for VAR. (define (initialize-name-node-type node uid depth) (let ((uvar (make-uvar (node-form node) depth uid))) (set-node-type! node uvar) (set-uvar-source! uvar node) uvar)) ; Get the type of the variable - if it is a type-variable, then create a new ; one and relate the two; if it is a polymorphic pattern, instantiate it. ; How to pass the source? (define-inference-rule 'name (lambda (node depth return?) (let ((type (if (node-ref node 'binding) (get-global-type (binding-place (node-ref node 'binding))) (node-type node)))) (if (not type) (bug "name node ~S has no type" node)) (if (type-scheme? type) (instantiate-type-scheme type depth) type)))) (define-inference-rule 'primitive (lambda (node depth return?) (let ((type (get-global-type (cdr (node-form node))))) (if (type-scheme? type) (instantiate-type-scheme type depth) type)))) ; If no type is present, create a type variable. (define (get-global-type value) (if (location? value) (literal-value-type (contents value)) (let ((has (maybe-follow-uvar (variable-type value)))) (cond ((not (eq? has type/undetermined)) has) (else (let ((type (make-uvar (variable-name value) -1))) (set-variable-type! value type) (set-uvar-source! type value) type)))))) (define-inference-rule 'set! (lambda (node depth return?) (let* ((exp (node-form node)) (type (infer-type (caddr exp) depth)) (binding (node-ref (cadr exp) 'binding))) (if (not binding) (error "SET! on a local variable ~S" (schemify node))) (unify! type (variable-type (binding-place binding)) node) type/null))) (define-inference-rule 'call (lambda (node depth return?) (rule-for-calls (node-form node) node depth return?))) (define-inference-rule 'goto (lambda (node depth return?) (rule-for-calls (cdr (node-form node)) node depth return?))) (define (rule-for-calls proc+args node depth return?) (let ((proc (car proc+args)) (args (cdr proc+args))) (cond ((lambda-node? proc) (rule-for-let node depth proc args return?)) ((primitive-node? proc) (rule-for-primitives node depth (node-form proc) args return?)) (else (rule-for-unknown-calls node depth proc+args return?))))) (define name-node? (node-predicate 'name)) (define lambda-node? (node-predicate 'lambda)) (define literal-node? (node-predicate 'literal)) (define primitive-node? (node-predicate 'primitive)) (define (rule-for-let node depth proc args return?) (let ((depth (+ depth 1)) (uid (unique-id)) (proc (node-form proc))) (do ((names (cadr proc) (cdr names)) (vals args (cdr vals))) ((null? names)) (let ((type (schemify-type (infer-type (car vals) depth) depth))) (if (type-scheme? type) (set-node-type! (car names) type) (unify! (initialize-name-node-type (car names) uid depth) type node)))) (infer-any-type (caddr proc) depth return?))) (define (rule-for-primitives node depth primitive args return?) ((primitive-inference-rule primitive) args node depth return?)) (define (rule-for-unknown-calls node depth proc+args return?) (let ((proc-type (infer-type (car proc+args) depth)) (arg-types (infer-types (cdr proc+args) depth)) (return-type (if return? (make-tuple-uvar 'result depth) (make-uvar 'result depth)))) (unify! proc-type (make-arrow-type arg-types return-type) node) ; (if (= 244 (uvar-id return-type)) ; (breakpoint "rule-for-unknown-calls")) (maybe-follow-uvar return-type))) (define (infer-types nodes depth) (map (lambda (node) (infer-type node depth)) nodes)) (define-inference-rule 'begin (lambda (node depth return?) (let loop ((exps (cdr (node-form node))) (type type/unit)) (if (null? exps) type (loop (cdr exps) (infer-any-type (car exps) depth (or (not (null? (cdr exps))) return?))))))) ; It would be nice if we could just try to unify the two arms and return ; type/unit if we lost, but unification has side-effects. (define-inference-rule 'if (lambda (node depth return?) (let* ((args (cdr (node-form node))) (true-type (infer-any-type (cadr args) depth return?)) (false-type (infer-any-type (caddr args) depth return?))) (unify! (infer-type (car args) depth) type/boolean node) (cond ((eq? true-type type/null) false-type) ((eq? false-type type/null) true-type) (else (unify! true-type false-type node) true-type))))) (define-inference-rule 'letrec (lambda (node depth return?) (let ((form (node-form node)) (depth (+ depth 1)) (uid (unique-id))) (let ((names (map car (cadr form))) (vals (map cadr (cadr form)))) (for-each (lambda (name) (initialize-name-node-type name uid depth)) names) (do ((names names (cdr names)) (vals vals (cdr vals))) ((null? names)) (if (not (lambda-node? (car vals))) (error "LETREC value is not a LAMBDA: ~S" (schemify node))) (unify! (infer-type (car vals) depth) (node-type (car names)) node)) (for-each (lambda (name) (let ((type (schemify-type (node-type name) depth))) (if (type-scheme? type) (set-node-type! name type)))) names) (infer-any-type (caddr form) depth return?))))) ;-------------------------------------------------- (define (node-type node) (maybe-follow-uvar (node-ref node 'type))) (define (set-node-type! node type) (node-set! node 'type type)) (define (lambda-node-return-type node) (node-ref node 'return-type)) (define (set-lambda-node-return-type! node type) (node-set! node 'return-type type)) ;-------------------------------------------------- ; Utility procedures used by the inferencers of the various primops. ; Check that the INDEX'th argument of CALL has type TYPE. (define (check-arg-type args index type depth exp) (if (null? args) (begin (format #t "Wrong number of arguments in ~S~% " (schemify exp)) (if *currently-checking* (format #t "~% while reconstructing the type of '~S'" *currently-checking*)) (error "type problem"))) (unify! (infer-type (list-ref args index) depth) type exp))