From 9d93b066cfd6505849dff12146159bedeadf96b9 Mon Sep 17 00:00:00 2001 From: Luke Lau Date: Thu, 1 Aug 2019 16:19:56 +0100 Subject: [PATCH] WIP on typechecker refactor Need to figure out the best way to union merge constraints, where they have the same key but one has a more concrete term --- tests.scm | 28 ++++++- typecheck.scm | 210 ++++++++++++++++++++++++-------------------------- 2 files changed, 126 insertions(+), 112 deletions(-) diff --git a/tests.scm b/tests.scm index c4b81f1..15e14ea 100644 --- a/tests.scm +++ b/tests.scm @@ -8,7 +8,14 @@ expected actual)))) (define (test . xs) (apply test-f (cons equal? xs))) -(define (test-types . xs) (apply test-f (cons types-equal? xs))) + +(define-syntax test-types + (syntax-rules () + ((_ a e) + (begin + (display (quote a)) + (newline) + (test-f types-equal? a e))))) (define (read-file file) (call-with-input-file file @@ -38,7 +45,7 @@ ; recursive types -(test-types (substitute '((t1 (abs t1 t10))) 't1) '(abs t1 t10)) +(test-types (substitute '((t1 . (abs t1 t10))) 't1) '(abs t1 t10)) (test-types (typecheck '(let ([bar (lambda (y) y)] [foo (lambda (x) (foo (bar #t)))]) @@ -68,6 +75,23 @@ bar)) 'int) + ; mutual recursion +(test-types (typecheck '(let ([f (lambda (n) (if (= n 0) + 0 + (+ 1 (g (- n 1)))))] + [g (lambda (m) (f m))]) + (f 10))) + 'int) + +(test-types (typecheck '(let ([pow (lambda (p y) + (let ([go (lambda (n x) + (if (= n 0) + x + (go (- n 1) (* x y))))]) + (go p 1)))]) + (pow 4 2))) + 'int) + (test-prog '(+ 1 2) 3) (test-prog '(bool->int (= 2 0)) 0) (test-prog '((lambda (x) ((lambda (y) (+ x y)) 42)) 100) 142) diff --git a/typecheck.scm b/typecheck.scm index e96f694..6a99869 100644 --- a/typecheck.scm +++ b/typecheck.scm @@ -23,6 +23,19 @@ (pretty-type (caddr t)))) (else (symbol->string t)))) +(define (pretty-constraints cs) + (string-append "{" + (fold-left string-append + "" + (map (lambda (c) + (string-append + (pretty-type (car c)) + ": " + (pretty-type (cdr c)) + ", ")) + cs)) + "}")) + ; ('a, ('b, 'a)) (define (env-lookup env n) (if (null? env) (error #f "empty env") ; it's a type equality @@ -78,14 +91,12 @@ ('print '(abs string void)) (else #f))) -; we typecheck the lambda calculus only (only single arg lambdas) -(define (typecheck prog) (define (check env x) - ;; (display "check: ") - ;; (display x) - ;; (display "\n\t") - ;; (display env) - ;; (newline) + (display "check: ") + (display x) + (display "\n\t") + (display env) + (newline) (let ((res (case (ast-type x) @@ -100,9 +111,9 @@ (else-type-res (check env (cadddr x))) (then-eq-else-cs (~ (cadr then-type-res) (cadr else-type-res))) - (cs (consolidate + (cs (constraint-merge (car then-type-res) - (consolidate (car else-type-res) + (constraint-merge (car else-type-res) then-eq-else-cs))) (return-type (substitute cs (cadr then-type-res)))) (when (not (eqv? (cadr cond-type-res) 'bool)) @@ -136,12 +147,13 @@ [cs (fold-left (lambda (acc res c) - (consolidate - acc - (consolidate (car res) + (constraint-merge + (constraint-merge ; unify with tvars from scc-env ; result ~ tvar - (~ (cadr res) (env-lookup scc-env c))))) + (~ (env-lookup scc-env c) (cadr res)) + (car res)) + acc)) '() type-results comps)] ; substitute *only* the bindings in this scc [new-env @@ -150,6 +162,9 @@ (cons (car x) (substitute cs (cdr x))) x)) scc-env)]) + (display "cs:") + (display cs) + (newline) new-env))] [new-env (fold-left process-component env components)]) (check new-env (last (let-body x))))) @@ -167,6 +182,7 @@ ;; (display "\n\t") ;; (display cs) ;; (display "\n\t") + ;; (display (format "subd-env: ~a\n" subd-env)) ;; (display resolved-arg-type) ;; (newline) (list (car body-type-res) @@ -192,12 +208,10 @@ ; f ~ a -> t0 (func-c (~ - func-type - (list 'abs - arg-type - (fresh-tvar)))) - (cs (consolidate - (consolidate func-c (car arg-type-res)) + (substitute (car arg-type-res) func-type) + `(abs ,arg-type ,(fresh-tvar)))) + (cs (constraint-merge + (constraint-merge func-c (car arg-type-res)) (car func-type-res))) (resolved-func-type (substitute cs func-type)) @@ -215,17 +229,20 @@ (let ((return-type (substitute cs (caddr resolved-func-type)))) (list cs return-type)) (error #f "not a function")))))))) - ;; (display "result of ") - ;; (display x) - ;; (display ":\n\t") - ;; (display (pretty-type (cadr res))) - ;; (display "\n\t[") - ;; (display (car res)) - ;; (display "]\n") + (display "result of ") + (display x) + (display ":\n\t") + (display (pretty-type (cadr res))) + (display "\n\t[") + (display (pretty-constraints (car res))) + (display "]\n") res)) + + ; we typecheck the lambda calculus only (only single arg lambdas) +(define (typecheck prog) (cadr (check '() (normalize prog)))) - ; returns a list of pairs of constraints + ; returns a list of constraints (define (~ a b) (let ([res (unify? a b)]) (if res @@ -235,105 +252,78 @@ (define (unify? a b) (cond [(eq? a b) '()] - [(or (tvar? a) (tvar? b)) (list (list a b))] + [(tvar? a) (list (cons a b))] + [(tvar? b) (list (cons b a))] [(and (abs? a) (abs? b)) (let* [(arg-cs (unify? (cadr a) (cadr b))) (body-cs (unify? (substitute arg-cs (caddr a)) (substitute arg-cs (caddr b))))] - (consolidate arg-cs body-cs))] + (constraint-merge body-cs arg-cs))] [else #f])) - ; TODO: what's the most appropriate substitution? - ; should all constraints just be limited to a pair? - ; this is currently horrific and i don't know what im doing. - ; should probably use ast-find here or during consolidation - ; to detect substitutions more than one layer deep - ; e.g. (abs t1 int) ~ (abs bool int) - ; substituting these constraints with t1 should resolve t1 with bool (define (substitute cs t) - ; gets the first concrete type - ; otherwise returns the last type variable - - ; removes t itself from cs, to prevent infinite recursion - (define cs-without-t - (map (lambda (c) - (filter (lambda (x) (not (eqv? t x))) c)) - cs)) - - (define (get-concrete c) - (let [(last (null? (cdr c)))] - (if (not (tvar? (car c))) - (if (abs? (car c)) - (substitute cs-without-t (car c)) - (car c)) - (if last - (car c) - (get-concrete (cdr c)))))) - (cond - ((abs? t) (list 'abs - (substitute cs (cadr t)) - (substitute cs (caddr t)))) - (else - (fold-left - (lambda (t c) - (if (member t c) - (get-concrete c) - t)) - t cs)))) - + [(tvar? t) + (if (assoc t cs) + (cdr (assoc t cs)) + t)] + [(abs? t) `(abs ,(substitute cs (cadr t)) + ,(substitute cs (caddr t)))] + [else t])) + + ; applies substitutions to all variables in environment (define (substitute-env cs env) (map (lambda (x) (cons (car x) (substitute cs (cdr x)))) env)) -(define (consolidate x y) - (define (merge a b) - (cond ((null? a) b) - ((null? b) a) - (else (if (member (car b) a) - (merge a (cdr b)) - (cons (car b) (merge a (cdr b))))))) - (define (overlap? a b) - (if (or (null? a) (null? b)) - #f - (if (fold-left (lambda (acc v) - (or acc (eq? v (car a)))) - #f b) - #t - (overlap? (cdr a) b)))) - - (cond ((null? y) x) - ((null? x) y) - (else - (let* ((a (car y)) - (merged (fold-left - (lambda (acc b) - (if acc - acc - (if (overlap? a b) - (cons (merge a b) b) - #f))) - #f x)) - (removed (if merged - (filter (lambda (b) (not (eq? b (cdr merged)))) x) - x))) - (if merged - (consolidate removed (cons (car merged) (cdr y))) - (consolidate (cons a x) (cdr y))))))) - - ; a1 -> a2 ~ a3 -> a4; - ; a1 -> a2 !~ bool -> bool - ; basically can the tvars be renamed + ; composes constraints a onto b and merges, i.e. applies a to b + ; a should be the "more important" constraints +(define (constraint-merge a b) + (define (f constraint) + (cons (car constraint) + (substitute a (cdr constraint)))) + + (define (most-concrete a b) + (cond + [(tvar? a) b] + [(tvar? b) a] + [(and (abs? a) (abs? b)) + `(abs ,(most-concrete (cadr a) (cadr b)) + ,(most-concrete (caddr a) (caddr b)))] + [(abs? a) b] + [(abs? b) a] + [else (error #f "impossible! most-concrete")])) + + (define (union p q) + (cond + [(null? p) q] + [(null? q) p] + [else + (let ([x (car q)]) + (if (assoc (car x) p) + (if (eqv? (most-concrete (cddr (assoc (car x) p)) + (cdr x)) + (cdr x)) + (cons x (union (filter (p) (not (eqv? + + + (define (union p q) + (append (filter (lambda (x) (not (assoc (car x) p))) + q) + p)) + (union a (map f b))) + + +;; ; a1 -> a2 ~ a3 -> a4; +;; ; a1 -> a2 !~ bool -> bool +;; ; basically can the tvars be renamed (define (types-equal? x y) (let ([cs (unify? x y)]) (if (not cs) #f (let* - ([test-kind - (lambda (acc c) - (if (tvar? c) acc #f))] - [test (lambda (acc c) + ([test (lambda (acc c) (and acc - (fold-left test-kind #t c) ; check only tvar substitutions - (<= (length c) 2)))]) ; check maximum 2 subs per equality group + (tvar? (car c)) ; the only substitutions allowed are tvar -> tvar + (tvar? (cdr c))))]) (fold-left test #t cs))))) ; input: a list of binds ((x . y) (y . 3)) -- 2.30.2