sysf: impl renaming of forall binders but then backed out

- capture when applying forall is ok since binder is renamed as well
  - and since type names are valid binder names, this is ok
  - TODO: what if we call inst with non-base type?
- add more variable capture tests
- typecheck: type= handles alpha-equiv
This commit is contained in:
Stephen Chang 2014-08-20 14:39:24 -04:00
parent 5a4e27a7d8
commit b0769c5f1a
3 changed files with 69 additions and 15 deletions

View File

@ -25,6 +25,8 @@
(map/List {Int Bool} (λ {[x : Int]} #f) (Cons {Int} 1 (Cons {Int} 2 (Null {Int}))))
: (MyList Bool) => (Cons {Bool} #f (Cons {Bool} #f (Null {Bool}))))
;; fails without inst (2014-08-18)
;; - Typed Racket also requires inst
;; - OCaml does not require inst
(check-type-and-result
(map/List {Int Bool} (inst (λ {X} {[x : X]} #f) Int) (Cons {Int} 1 (Cons {Int} 2 (Null {Int}))))
: (MyList Bool) => (Cons {Bool} #f (Cons {Bool} #f (Null {Bool}))))
@ -164,5 +166,23 @@
(check-type polyf : ( (X) ( X X)))
(define (polyf2 {X} [x : X]) : ( (X) ( X X)) polyf)
(check-type polyf2 : ( (X) ( X ( (X) ( X X)))))
; fails bc X gets captured (2014-08-18)
;(check-type (inst polyf2 Int) : (→ Int (∀ (X) (→ X X))))
; the following test fails bc X gets captured (2014-08-18)
; - 2014-08-20: fixed
; - 2014-08-20: backed out fix, so renamer is equiv to redex's "subst-vars"
;; Capture is actually ok because the binder gets renamed as well.
;; Since types are names anyways, it's ok.
;; Eg, the following example has type (→ Int (∀ (Int) (→ Int Int))), which is ok
(check-type (inst polyf2 Int) : ( Int ( (X) ( X X))))
;; the following test "fails" bc forall is nested
;; - Typed Racket has same behavior, so ok
(check-type-error (inst (inst polyf2 Int) Bool))
(check-type-error ((inst polyf2 Int) #f))
;; again, the following example has type (∀ (Int) (→ Int Int)), which is ok
(check-type ((inst polyf2 Int) 1) : ( (X) ( X X)))
(check-type (inst ((inst polyf2 Int) 1) Bool) : ( Bool Bool))
;; test same example with type-instantiating apply instead of inst
(check-type (polyf2 {Int} 1) : ( (Y) ( Y Y)))
(check-type-error (polyf2 {Int} #f))
(check-type-and-result ((polyf2 {Int} 1) {Bool} #f) : Bool => #f)
(check-type-error ((polyf2 {Int} 1) {Bool} 2))

View File

@ -43,7 +43,15 @@
#:with e+ (expand/df #'e)
#:with ( (X ...) τbody) (typeof #'e+)
#:with τinst (apply-forall (typeof #'e+) #'(τ ...))
( #'e+ #'τinst)]))
( #'e+ #'τinst)]
[(_ e τ ...) ; error if not ∀ type
#:with τ_e (typeof (expand/df #'e))
#:when
(type-error
#:src #'e
#:msg "Could not instantiate expression ~a with non-polymorphic type ~a"
#'e #'τ_e)
#f]))
;; cases ----------------------------------------------------------------------
(define-syntax (cases stx)
@ -78,8 +86,8 @@
(syntax-parse stx #:datum-literals (:)
;; most of the code from this case, except for the curly? check,
;; is copied from stlc:λ
[(_ τs ([x:id : τ] ...) e ... e_result)
#:when (curly-parens? #'τs)
[(_ τvars ([x:id : τ] ...) e ... e_result)
#:when (curly-parens? #'τvars)
;; the with-extended-type-env must be outside the expand/df (instead of
;; around just the body) bc ow the parameter will get restored to the old
;; value before the local-expand happens
@ -93,10 +101,15 @@
(stx-map
(λ (e) (if (identifier? e) (expand/df e) e))
#'(e+ ... e_result+)))
;; manually handle the implicit begin
;; manually typecheck the implicit begin
#:when (stx-map assert-Unit-type #'(e++ ...))
#:with τ_body (typeof #'e_result++)
( (syntax/loc stx (lam xs e++ ... e_result++)) #'( τs ( τ ... τ_body)))]
;; rename binders before creating ∀ type,
;; to avoid capture when ∀s are nested
; #:with fresh-τvars (generate-temporaries #'τvars)
; #:with (→ τ_res ...) (apply-forall #'(∀ τvars (→ τ ... τ_body)) #'fresh-τvars)
; (⊢ (syntax/loc stx (lam xs e++ ... e_result++)) #'(∀ fresh-τvars (→ τ_res ...)))]
( (syntax/loc stx (lam xs e++ ... e_result++)) #'( τvars ( τ ... τ_body)))]
[(_ any ...) #'(stlc:λ any ...)]))
; define ----------------------------------------------------------------------
@ -104,15 +117,23 @@
(syntax-parse stx #:datum-literals (:)
;; most of the code from this case, except for the curly? check,
;; is copied from stlc:define
[(_ (f:id τs [x:id : τ] ...) : τ_result e ...)
#:when (curly-parens? #'τs)
#:when (Γ (type-env-extend #'([f ( τs ( τ ... τ_result))])))
#'(define f (λ/tc τs ([x : τ] ...) e ...))]
[(_ (f:id τvars [x:id : τ] ...) : τ_result e ...)
#:when (curly-parens? #'τvars)
;; rename binders before creating ∀ type,
;; to avoid capture when ∀s are nested
; #:with fresh-τvars (generate-temporaries #'τvars)
; #:with (→ τ/renamed ...) (apply-forall #'(∀ τvars (→ τ ... τ_result)) #'fresh-τvars)
; #:when (Γ (type-env-extend #'([f (∀ fresh-τvars (→ τ/renamed ...))])))
#:when (Γ (type-env-extend #'([f ( τvars ( τ ... τ_result))])))
#'(define f (λ/tc τvars ([x : τ] ...) e ...))]
[(_ any ...) #'(stlc:define any ...)]))
; #%app -----------------------------------------------------------------------
(define-syntax (app/tc stx)
(syntax-parse stx #:literals ( void)
; this case only triggered by testing forms, eg check-type
; more specifically, types should never get expanded, except when testing
[(_ arg ...) #'( arg ...)]
[(_ e_fn τs e_arg ...)
#:when (curly-parens? #'τs)
#:with (e_fn+ e_arg+ ...) (stx-map expand/df #'(e_fn e_arg ...))
@ -128,7 +149,10 @@
[(_ e_fn e_arg ...)
#:with e_fn+ (expand/df #'e_fn)
#:with ( (X ...) ( τX ...)) (typeof #'e_fn+)
#:when (error 'TYPE-ERROR
#:when (type-error #:src #'stx
#:msg "Missing type instantiation(s) in application: ~a"
#'(e_fn e_arg ...))
#;(error 'TYPE-ERROR
"(~a:~a) Missing type instantiation(s) in application: ~a"
(syntax-line stx) (syntax-column stx)
(syntax->datum #'(e_fn e_arg ...)))

View File

@ -26,9 +26,17 @@
(define-for-syntax (type=? τ1 τ2)
; (printf "type= ~a ~a\n" (syntax->datum τ1) (syntax->datum τ2))
(syntax-parse #`(#,τ1 #,τ2)
(syntax-parse #`(#,τ1 #,τ2) #:datum-literals ()
[(x:id y:id) (free-identifier=? τ1 τ2)]
[((tycon1 τ1 ...) (tycon2 τ2 ...))
[(∀τ1 ∀τ2)
#:with ( τvars1 τ_body1) #'∀τ1
#:with ( τvars2 τ_body2) #'∀τ2
#:with fresh-τvars (generate-temporaries #'τvars1)
;; to handle α-equiv, for apply-forall with same vars
(and (= (length (syntax->list #'τvars1))
(length (syntax->list #'τvars2)))
(type=? (apply-forall #'∀τ1 #'fresh-τvars) (apply-forall #'∀τ2 #'fresh-τvars)))]
[((tycon1:id τ1 ...) (tycon2:id τ2 ...))
(and (free-identifier=? #'tycon1 #'tycon2)
(= (length (syntax->list #'(τ1 ...))) (length (syntax->list #'(τ2 ...))))
(stx-andmap type=? #'(τ1 ...) #'(τ2 ...)))]
@ -55,7 +63,8 @@
;; because env must be set before expanding λ body (ie before going under λ)
;; so x's in the body won't be free-id=? to the one in the table
;; use symbols instead of identifiers for now --- should be fine because
;; I'm manually managing the environment
;; I'm manually managing the environment, and surface language has no macros
;; so I know all the binding forms
(define Γ (make-parameter base-type-env))
(define (type-env-lookup x)
@ -78,6 +87,7 @@
;; apply-forall ---------------------------------------------------------------
(define-for-syntax (apply-forall ∀τ τs)
; (printf "apply:~a\n~a\n" ∀τ τs)
(define ctx (syntax-local-make-definition-context))
(define id (generate-temporary))
(syntax-local-bind-syntaxes