www

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

commit b6c8bb5880bf8e801dac6d02326241724ff4a5b0
parent aa930c1180f13b6fdba2b378fc1f8e7675f53f95
Author: Stephen Chang <stchang@ccs.neu.edu>
Date:   Mon, 21 Dec 2015 18:55:42 -0500

infer some lambda param types from body

Diffstat:
Mtapl/infer.rkt | 38+++++++++++++++++++++++++++++++++-----
Mtapl/stlc+cons.rkt | 5++++-
Mtapl/tests/infer-tests.rkt | 23++++++++++++++++-------
Mtapl/typecheck.rkt | 61++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
4 files changed, 107 insertions(+), 20 deletions(-)

diff --git a/tapl/infer.rkt b/tapl/infer.rkt @@ -64,13 +64,13 @@ (define y e-))] [(_ (~and Xs {X:id ...}) (f:id [x:id (~datum :) τ] ... (~datum →) τ_out) e) #:when (brace? #'Xs) - #:with g (generate-temporary) + #:with g (generate-temporary #'f) #:with e_ann #'(add-expected e τ_out) #'(begin (define-syntax f (make-rename-transformer (⊢ g : (∀ (X ...) (ext-stlc:→ τ ... τ_out))))) (define g (Λ (X ...) (ext-stlc:λ ([x : τ] ...) e_ann))))] [(_ (f:id [x:id (~datum :) τ] ... (~datum →) τ_out) e) - #:with g (generate-temporary) + #:with g (generate-temporary #'f) #:with e_ann #'(add-expected e τ_out) #'(begin (define-syntax f (make-rename-transformer (⊢ g : (→ τ ... τ_out)))) @@ -78,7 +78,7 @@ ; all λs have type (∀ (X ...) (→ τ_in ... τ_out)) (define-typed-syntax λ #:datum-literals (:) - [(~and fn (_ (x:id ...) e) ~!) ; no annotations + [(~and fn (_ (x:id ...) e)) ; no annotations, try to infer from outer ctx, ie an application #:with given-τ-args (syntax-property #'fn 'given-τ-args) #:fail-unless (syntax-e #'given-τ-args) ; no inferred types or annotations, so error (format "input types for ~a could not be inferred; add annotations" @@ -86,12 +86,28 @@ #:with (τ_arg ...) #'given-τ-args #:with [λ- τ_λ] (infer+erase #'(ext-stlc:λ ([x : τ_arg] ...) e)) (⊢ λ- : (∀ () τ_λ))] + [(~and fn (_ (x:id ...) e) ~!) ; no annotations, couldnt infer from ctx (eg, unapplied lam), try to infer from body + #:with (xs- e- τ_res) (infer/ctx+erase #'([x : x] ...) #'e) + #:with env (get-env #'e-) + #:fail-unless (syntax-e #'env) + (format "input types for ~a could not be inferred; add annotations" + (syntax->datum #'fn)) + #:with (τ_arg ...) (stx-map (λ (y) (lookup y #'env)) #'xs-) + #:fail-unless (stx-andmap syntax-e #'(τ_arg ...)) + (format "some input types for ~a could not be inferred; add annotations" + (syntax->datum #'fn)) + ;; propagate up inferred types of variables + #:with res (add-env #'(λ xs- e-) #'env) +; #:with [λ- τ_λ] (infer+erase #'(ext-stlc:λ ([x : x] ...) e)) + (⊢ res : (∀ () (ext-stlc:→ τ_arg ... τ_res)))] + ;(⊢ (λ xs- e-) : (∀ () (ext-stlc:→ τ_arg ... τ_res)))] [(_ . rst) #:with [λ- τ_λ] (infer+erase #'(ext-stlc:λ . rst)) (⊢ λ- : (∀ () τ_λ))]) (define-typed-syntax #%app [(_ e_fn e_arg ...) ; infer args first + ; #:when (printf "args first ~a\n" (syntax->datum stx)) #:with maybe-inferred-τs (with-handlers ([exn:fail:type:infer? (λ _ #f)]) (infers+erase #'(e_arg ...))) #:when (syntax-e #'maybe-inferred-τs) @@ -131,8 +147,13 @@ (format "Expected: ~a arguments with type(s): " (stx-length #'(τ_in ...))) (string-join (stx-map type->str #'(τ_in ...)) ", ")) - (⊢ (#%app e_fn- e_arg- ...) : τ_out)] + ; propagate inferred types for variables up + #:with env (stx-flatten (filter (λ (x) x) (stx-map get-env #'(e_arg- ...)))) + #:with result-app (add-env #'(#%app e_fn- e_arg- ...) #'env) + ;(⊢ (#%app e_fn- e_arg- ...) : τ_out)] + (⊢ result-app : τ_out)] [(_ e_fn e_arg ...) ; infer fn first ------------------------- ; TODO: remove code dup +; #:when (printf "fn first ~a\n" (syntax->datum stx)) #:with [e_fn- ((X ...) ((~ext-stlc:→ τ_inX ... τ_outX)))] (⇑ e_fn as ∀) #:fail-unless (stx-length=? #'(τ_inX ...) #'(e_arg ...)) ; check arity (string-append @@ -153,15 +174,20 @@ (define/with-syntax τs_solved (stx-map (λ (y) (lookup y cs)) #'(X ...))) (cond [(andmap syntax-e (syntax->list #'τs_solved)) ; all tyvars X have mapping + ; TODO: substs is not properly transferring #%type property +; (stx-map displayln #'τs_solved) (define e+τ (infer+erase #`(add-expected #,e_arg #,(substs #'τs_solved #'(X ...) τ_inX)))) + ; (displayln e+τ) (values cs (cons e+τ e+τs))] [else (define/with-syntax [e τ] (infer+erase e_arg)) + ; (displayln #'(e τ)) (define/with-syntax (c ...) cs) (define/with-syntax (new-c ...) (compute-constraint #`(#,τ_inX τ))) (values #'(new-c ... c ...) (cons #'[e τ] e+τs))]))]) (define/with-syntax e+τs/stx e+τs) (list cs (reverse (syntax->list #'e+τs/stx)))) + #:with env (stx-flatten (filter (λ (x) x) (stx-map get-env #'(e_arg- ...)))) #:with (τ_solved ...) (stx-map (λ (y) (lookup y #'cs)) #'(X ...)) #:with (τ_in ... τ_out) (stx-map (λ (t) (substs #'(τ_solved ...) #'(X ...) t)) #'(τ_inX ... τ_outX)) ; some code duplication @@ -179,4 +205,6 @@ (format "Expected: ~a arguments with type(s): " (stx-length #'(τ_in ...))) (string-join (stx-map type->str #'(τ_in ...)) ", ")) - (⊢ (#%app e_fn- e_arg- ...) : τ_out)]) + #:with result-app (add-env #'(#%app e_fn- e_arg- ...) #'env) + ;(⊢ (#%app e_fn- e_arg- ...) : τ_out)]) + (⊢ result-app : τ_out)]) diff --git a/tapl/stlc+cons.rkt b/tapl/stlc+cons.rkt @@ -35,7 +35,10 @@ (format "trying to cons expression ~a with type ~a to list ~a with type ~a\n" (syntax->datum #'e1) (type->str #'τ1) (syntax->datum #'e2) (type->str #'(List τ2))) - (⊢ (cons e1- e2-) : (List τ1))]) + ;; propagate up inferred types of variables + #:with env (stx-flatten (filter (λ (x) x) (stx-map get-env #'(e1- e2-)))) + #:with result-cons (add-env #'(cons e1- e2-) #'env) + (⊢ result-cons : (List τ1))]) (define-typed-syntax isnil [(_ e) #:with (e- _) (⇑ e as List) diff --git a/tapl/tests/infer-tests.rkt b/tapl/tests/infer-tests.rkt @@ -1,13 +1,15 @@ #lang s-exp "../infer.rkt" (require "rackunit-typechecking.rkt") -(typecheck-fail (λ (x) x) #:with-msg "add annotations") +(typecheck-fail (λ (x) x) #:with-msg "could not infer type of x; add annotation\\(s\\)") ; should bidirectional checking work for this case? ; I think no, since TR doesnt handle it either -(typecheck-fail (λ (x) (+ x 1)) #:with-msg "add annotations") +;(typecheck-fail (λ (x) (+ x 1)) #:with-msg "add annotations") +; 2015-12-18: can infer this type now +(check-type (λ (x) (+ x 1)) : (→ Int Int)) ; can't check this case either -(typecheck-fail ((λ (f) (f 10)) (λ (x) x)) #:with-msg "add annotations") +(typecheck-fail ((λ (f) (f 10)) (λ (x) x)) #:with-msg "add annotation\\(s\\)") ; stlc+lit tests with app, but infer types (no annotations) (check-type ((λ (x) x) 1) : Int ⇒ 1) @@ -89,7 +91,8 @@ (typecheck-fail (map add1 (list "1")) #:with-msg "Arguments.+have wrong type") (check-type (map (λ ([x : Int]) (+ x 2)) (list 1 2 3)) : (List Int) ⇒ (list 3 4 5)) ; doesnt work yet -;(map (λ (x) (+ x 2)) (list 1 2 3)) +;; 2015-12-18: dont need annotations on lambdas with concrete type +(check-type (map (λ (x) (+ x 2)) (list 1 2 3)) : (List Int) ⇒ (list 3 4 5)) (define {X} (filter [p? : (→ X Bool)] [lst : (List X)] → (List X)) (if (nil? lst) @@ -107,6 +110,8 @@ (check-type (filter zero? (list 1 2 3)) : (List Int) ⇒ (nil {Int})) (check-type (filter zero? (list 0 1 2)) : (List Int) ⇒ (list 0)) (check-type (filter (λ ([x : Int]) (not (zero? x))) (list 0 1 2)) : (List Int) ⇒ (list 1 2)) +;; 2015-12-18: dont need annotations on lambdas with concrete type +(check-type (filter (λ (x) (not (zero? x))) (list 0 1 2)) : (List Int) ⇒ (list 1 2)) (define {X Y} (foldr [f : (→ X Y Y)] [base : Y] [lst : (List X)] → Y) (if (nil? lst) @@ -161,12 +166,16 @@ (define (nqueens [n : Int] → (List Queen)) (let* ([process-row - (λ ([r : Int] [all-possible-so-far : (List (List Queen))]) + (λ ;([r : Int] [all-possible-so-far : (List (List Queen))]) + (r all-possible-so-far) (foldr - (λ ([qs : (List Queen)] [new-qss : (List (List Queen))]) + ;; 2015-12-18: dont need annotations on lambdas with concrete type + (λ ;([qs : (List Queen)] [new-qss : (List (List Queen))]) + (qs new-qss) (append (map - (λ ([c : Int]) (cons (Q r c) qs)) + ;; 2015-12-18: dont need annotations on lambdas with concrete type + (λ (c) (cons (Q r c) qs)) (build-list n add1)) new-qss)) nil diff --git a/tapl/typecheck.rkt b/tapl/typecheck.rkt @@ -54,7 +54,7 @@ (syntax-parameterize ([stx (syntax-id-rules () [_ syntx])]) (syntax-parse syntx #:context #'out-name stx-parse-clause ...))))] [(_ name:id stx-parse-clause ...) - #`(define-typed-syntax #,(generate-temporary) #:export-as name + #`(define-typed-syntax #,(generate-temporary #'name) #:export-as name stx-parse-clause ...)])) ;; need options for @@ -118,7 +118,11 @@ (all-from-out base-lang))))])) (define-syntax add-expected - (syntax-parser [(_ e τ) (syntax-property #'e 'expected-type #'τ)])) + (syntax-parser + [(_ e τ) +; #:when (printf "adding expected type ~a to expression ~a\n" +; (syntax->datum #'τ) (syntax->datum #'e)) + (syntax-property #'e 'expected-type #'τ)])) ;; type assignment (begin-for-syntax @@ -141,6 +145,8 @@ (syntax-property e 'expected-type τ)) ; dont type-eval?, ie expand? (define (get-expected-type e) (syntax-property e 'expected-type)) + (define (add-env e env) (syntax-property e 'env env)) + (define (get-env e) (syntax-property e 'env)) ;; typeof : Syntax -> Type or #f ;; Retrieves type of given stx, or #f if input has not been assigned a type. @@ -249,17 +255,45 @@ (assign-type #'tv #'k) #'ok #:tag '#,tag))] ...) (λ (x ...) - (let-syntax ([x (syntax-parser [_:id (assign-type #'x #'τ)] + (let-syntax ([x (syntax-parser [i:id +; #:when (or (not (and (identifier? #'τ) (free-identifier=? #'x #'τ))) +; (printf "~a has type = itself\n" #'i)) +; #:when (or (not (get-expected-type #'i)) +; (printf "expected type of ~a: ~a\n" +; #'i (and (get-expected-type #'i) +; (syntax->datum (get-expected-type #'i))))) +; #:when (or (not (get-expected-type #'i)) +; (printf "assigned type of ~a: ~a\n" +; (syntax->datum #'i) (syntax->datum #'τ))) + (if (and (identifier? #'τ) (free-identifier=? #'x #'τ)) + (if (get-expected-type #'i) + (add-env (assign-type #'x (get-expected-type #'i)) #`((x #,(get-expected-type #'i)))) + (raise (exn:fail:type:infer + (format "~a (~a:~a): could not infer type of ~a; add annotation(s)" + (syntax-source #'x) (syntax-line #'x) (syntax-column #'x) + (syntax->datum #'x)) + (current-continuation-marks)))) + (assign-type #'x #'τ))] [(o . rst) ; handle if x used in fn position + #:fail-when (and (identifier? #'τ) (free-identifier=? #'x #'τ)) + (raise (exn:fail:type:infer + (format "~a (~a:~a): could not infer type of function ~a; add annotation(s)" + (syntax-source #'o) (syntax-line #'o) (syntax-column #'o) + (syntax->datum #'o)) + (current-continuation-marks))) #:with app (datum->syntax #'o '#%app) #`(app #,(assign-type #'x #'τ) . rst)] #;[(_ . rst) #`(#,(assign-type #'x #'τ) . rst)]) - #;(make-rename-transformer - (assign-type #'x #'τ))] ...) + #;(make-rename-transformer (assign-type #'x #'τ))] ...) (#%expression e) ... void))))) (list #'tvs+ #'xs+ #'(e+ ...) (stx-map ; need this check when combining #%type and kinds - (λ (t) (or (false? t) (syntax-local-introduce t))) + (λ (t) (or (false? t) + ; TODO: why does this happen? + ; happens when propagating 'env up in λ + #;(and (pair? t) + (syntax-local-introduce (car t))) + (syntax-local-introduce t))) (stx-map typeof #'(e+ ...))))] [([x τ] ...) (infer es #:ctx #'([x : τ] ...) #:tvctx tvctx)])) @@ -578,10 +612,23 @@ ; substitution (begin-for-syntax + (define (merge-type-tags stx) + (define t (syntax-property stx 'type)) + (or (and (pair? t) + (identifier? (car t)) (identifier? (cdr t)) + (free-identifier=? (car t) (cdr t)) + (syntax-property stx 'type (car t))) + stx)) ; subst τ for y in e, if (bound-id=? x y) (define (subst τ x e) (syntax-parse e - [y:id #:when (bound-identifier=? e x) (syntax-track-origin τ #'y #'y)] + [y:id #:when (bound-identifier=? e x) +; #:when +; (displayln (syntax-property (syntax-track-origin τ #'y #'y) 'type)) +; #:when (displayln (syntax-property (syntax-property (syntax-track-origin τ #'y #'y) 'type #'#%type) 'type)) + ; use syntax-track-origin to transfer 'orig + ; but may transfer multiple #%type tags, so merge + (merge-type-tags (syntax-track-origin τ #'y #'y))] [(esub ...) #:with (esub_subst ...) (stx-map (λ (e1) (subst τ x e1)) #'(esub ...)) (syntax-track-origin #'(esub_subst ...) e x)]