new define-type-constructor: support binding forms

- tests passing up to stlc+rec-iso.rkt
This commit is contained in:
Stephen Chang 2015-08-07 17:26:53 -04:00
parent 75b4dae95f
commit 0dde065949
16 changed files with 383 additions and 279 deletions

View File

@ -47,26 +47,31 @@
(define-syntax (and/tc stx)
(syntax-parse stx
[(_ e1 e2)
#:with (e1- τ1) (infer+erase #'e1)
#:fail-unless (Bool? #'τ1) (format "given non-Bool arg: ~a\n" (syntax->datum #'e1))
#:with (e2- τ2) (infer+erase #'e2)
#:fail-unless (Bool? #'τ2) (format "given non-Bool arg: ~a\n" (syntax->datum #'e2))
#:with e1- (inferBool+erase #'e1)
#:with e2- (inferBool+erase #'e2)
; #:with (e1- τ1) (infer+erase #'e1)
; #:fail-unless (Bool? #'τ1) (format "given non-Bool arg: ~a\n" (syntax->datum #'e1))
; #:with (e2- τ2) (infer+erase #'e2)
; #:fail-unless (Bool? #'τ2) (format "given non-Bool arg: ~a\n" (syntax->datum #'e2))
( (and e1- e2-) : Bool)]))
(define-syntax (or/tc stx)
(syntax-parse stx
[(_ e1 e2)
#:with (e1- τ1) (infer+erase #'e1)
#:fail-unless (Bool? #'τ1) (format "given non-Bool arg: ~a\n" (syntax->datum #'e1))
#:with (e2- τ2) (infer+erase #'e2)
#:fail-unless (Bool? #'τ2) (format "given non-Bool arg: ~a\n" (syntax->datum #'e2))
#:with e1- (inferBool+erase #'e1)
#:with e2- (inferBool+erase #'e2)
; #:with (e1- τ1) (infer+erase #'e1)
; #:fail-unless (Bool? #'τ1) (format "given non-Bool arg: ~a\n" (syntax->datum #'e1))
; #:with (e2- τ2) (infer+erase #'e2)
; #:fail-unless (Bool? #'τ2) (format "given non-Bool arg: ~a\n" (syntax->datum #'e2))
( (or e1- e2-) : Bool)]))
(define-syntax (if/tc stx)
(syntax-parse stx
[(_ e_tst e1 e2)
#:with (e_tst- τ_tst) (infer+erase #'e_tst)
#:fail-unless (Bool? #'τ_tst) (format "given non-Bool test: ~a\n" (syntax->datum #'e_tst))
#:with e_tst- (inferBool+erase #'e_tst)
; #:with (e_tst- τ_tst) (infer+erase #'e_tst)
; #:fail-unless (Bool? #'τ_tst) (format "given non-Bool test: ~a\n" (syntax->datum #'e_tst))
#:with (e1- τ1) (infer+erase #'e1)
#:with (e2- τ2) (infer+erase #'e2)
#:when ((current-type=?) #'τ1 #'τ2)
@ -118,7 +123,7 @@
[(_ ([b:typed-binding e] ...) e_body)
#:with ((x- ...) (e- ... e_body-) (τ ... τ_body))
(infers/type-ctxt+erase #'(b ...) #'(e ... e_body))
#:fail-unless (typechecks? (type-evals #'(b.τ ...)) #'(τ ...))
#:fail-unless (typechecks? #'(b.τ ...) #;(type-evals #'(b.τ ...)) #'(τ ...))
(string-append
"type check fail, args have wrong type:\n"
(string-join

View File

@ -23,14 +23,16 @@
(define-syntax (deref stx)
(syntax-parse stx
[(_ e)
#:with (e- ref-τ) (infer+erase #'e)
#:with τ (Ref-get τ from ref-τ)
#:with (e- (τ)) (inferRef+erase #'e)
; #:with (e- ref-τ) (infer+erase #'e)
; #:with τ (Ref-get τ from ref-τ)
( (unbox e-) : τ)]))
(define-syntax (:= stx)
(syntax-parse stx
[(_ e_ref e)
#:with (e_ref- ref-τ) (infer+erase #'e_ref)
#:with τ1 (Ref-get τ from ref-τ)
#:with (e_ref- (τ1)) (inferRef+erase #'e_ref)
; #:with (e_ref- ref-τ) (infer+erase #'e_ref)
; #:with τ1 (Ref-get τ from ref-τ)
#:with (e- τ2) (infer+erase #'e)
#:when (typecheck? #'τ1 #'τ2)
( (set-box! e_ref- e-) : Unit)]))

View File

@ -28,23 +28,24 @@
(syntax-parse stx
[(_ e1 e2)
#:with (e1- τ1) (infer+erase #'e1)
#:with (e2- τ-lst) (infer+erase #'e2)
; #:when (displayln #'τ-lst)
#:with τ2 (List-get τ from τ-lst)
; #:when (displayln #'τ2)
#:with (e2- (~List τ2)) (infer+erase #'e2)
; #:with (e2- τ-lst) (infer+erase #'e2)
; #:with τ2 (List-get τ from τ-lst)
#:when (typecheck? #'τ1 #'τ2)
( (cons e1- e2-) : (List τ1))]))
(define-syntax (isnil stx)
(syntax-parse stx
[(_ e)
#:with (e- τ-lst) (infer+erase #'e)
#:fail-unless (List? #'τ-lst) "expected argument of List type"
#:with (e- _) (inferList+erase #'e)
; #:with (e- τ-lst) (infer+erase #'e)
; #:fail-unless (List? #'τ-lst) "expected argument of List type"
( (null? e-) : Bool)]))
(define-syntax (head stx)
(syntax-parse stx
[(_ e)
#:with (e- τ-lst) (infer+erase #'e)
#:with τ (List-get τ from τ-lst)
#:with (e- (τ)) (inferList+erase #'e)
; #:with (e- τ-lst) (infer+erase #'e)
; #:with τ (List-get τ from τ-lst)
( (car e-) : τ)]))
(define-syntax (tail stx)
(syntax-parse stx

View File

@ -1,10 +1,12 @@
#lang racket/base
(require "typecheck.rkt")
(require (prefix-in stlc: (only-in "stlc+reco+var.rkt" #%app λ type=?))
(except-in "stlc+reco+var.rkt" #%app λ type=?))
(except-in "stlc+reco+var.rkt" #%app λ type=? × tup proj)
(only-in "stlc+tup.rkt" × tup proj))
(provide (rename-out [stlc:#%app #%app] [stlc:λ λ]))
(provide (except-out (all-from-out "stlc+reco+var.rkt")
stlc:#%app stlc:λ (for-syntax stlc:type=?)))
stlc:#%app stlc:λ (for-syntax stlc:type=?))
(all-from-out "stlc+tup.rkt"))
(provide μ fld unfld (for-syntax type=?))
;; stlc + (iso) recursive types
@ -15,11 +17,23 @@
;; - terms from stlc+reco+var.rkt
;; - fld/unfld
(define-type-constructor
(μ [[tv]] τ_body))
; can't enforce this because bound ids wont have #%type tag
;#:declare τ_body type)
#;(define-syntax (μ stx)
(syntax-parse stx
[(_ (tv:id) τ_body)
#'(#%type
(λ (tv)
(let-syntax ([tv (syntax-parser [tv:id #'(#%type tv)])])
τ_body)))]))
(begin-for-syntax
;; extend to handle μ
(define (type=? τ1 τ2)
;(printf "(τ=) t1 = ~a\n" #;τ1 (syntax->datum τ1))
;(printf "(τ=) t2 = ~a\n" #;τ2 (syntax->datum τ2))
; (printf "(τ=) t1 = ~a\n" #;τ1 (syntax->datum τ1))
; (printf "(τ=) t2 = ~a\n" #;τ2 (syntax->datum τ2))
(syntax-parse (list τ1 τ2)
[(((~literal #%plain-lambda) (x:id ...) k1 ... t1)
((~literal #%plain-lambda) (y:id ...) k2 ... t2))
@ -31,22 +45,19 @@
(current-type=? type=?)
(current-typecheck-relation type=?))
(define-syntax (μ stx)
(syntax-parse stx
[(_ (tv:id) τ_body)
#'(λ (tv) τ_body)]))
(define-syntax (unfld stx)
(syntax-parse stx
[(_ τ:ann e)
#:with ((~literal #%plain-lambda) (tv:id) τ_body) #'τ.norm
#:with ( [[tv]] τ_body) #'τ.norm
; #:with ((~literal #%plain-lambda) (tv:id) τ_body) #'τ.norm
#:with [e- τ_e] (infer+erase #'e)
#:when ((current-typecheck-relation) #'τ_e #'τ.norm)
( #'e- (subst #'τ.norm #'tv #'τ_body))]))
#:when (typecheck? #'τ_e #'τ.norm)
( e- : #,(subst #'τ.norm #'tv #'τ_body))]))
(define-syntax (fld stx)
(syntax-parse stx
[(_ τ:ann e)
#:with ((~literal #%plain-lambda) (tv:id) τ_body) #'τ.norm
#:with ( [[tv]] τ_body) #'τ.norm
; #:with ((~literal #%plain-type) ((~literal #%plain-lambda) (tv:id) τ_body)) #'τ.norm
#:with [e- τ_e] (infer+erase #'e)
#:when ((current-typecheck-relation) #'τ_e (subst #'τ.norm #'tv #'τ_body))
( #'e- #'τ.norm)]))
#:when (typecheck? #'τ_e (subst #'τ.norm #'tv #'τ_body))
( e- : τ.τ)]))

View File

@ -1,7 +1,7 @@
#lang racket/base
(require "typecheck.rkt")
(require (prefix-in stlc: (only-in "stlc+tup.rkt" #%app begin tup proj let type=?))
(except-in "stlc+tup.rkt" #%app begin tup proj let type=? ×))
(except-in "stlc+tup.rkt" #%app begin tup proj let type=? × ~×))
(provide (rename-out [stlc:#%app #%app] [stlc:let let] [stlc:begin begin]
[define/tc define]))
(provide (except-out (all-from-out "stlc+tup.rkt")
@ -56,7 +56,8 @@
; re-define tuples as records
(define-type-constructor
(× [~× label τ_fld] ...) #:lits (~×)
;(× [~× label τ_fld] ...) #:lits (~×)
(× [: label τ_fld] ...) #:lits (:)
#:declare label str
#:declare τ_fld type
)
@ -68,26 +69,24 @@
(syntax-parse stx #:datum-literals (=)
[(_ [l:str = e] ...)
#:with ([e- τ] ...) (infers+erase #'(e ...))
( (list (list l e-) ...) : (× [~× l τ] ...))]
;(⊢ (list (list l e-) ...) : (× [~× l τ] ...))]
( (list (list l e-) ...) : (× [: l τ] ...))]
#;[(_ e ...)
#'(stlc:tup e ...)]))
(define-syntax (proj stx)
(syntax-parse stx #:literals (quote)
[(_ rec l:str)
#:with [rec- τ_rec] (infer+erase #'rec)
; #:when (printf "inferred type: ~a\n" (syntax->datum #'τ_rec))
; #:when (printf "inferred type eval ~a\n" (syntax->datum ((current-type-eval) #'τ_rec)))
#:with ('l_τ:str ...) (×-get label from τ_rec)
#:with (τ ...) (×-get τ_fld from τ_rec)
; #:fail-unless (×? #'τ_rec) (format "not record type: ~a" (syntax->datum #'τ_rec))
; #:with (['l_τ:str τ] ...) (stx-map :-args (×-args #'τ_rec))
[(_ e_rec l:str)
#:with (e_rec- (~× [: 'l_τ τ] ...)) (infer+erase #'e_rec)
; #:with [rec- τ_rec] (infer+erase #'e_rec) ; match method #2: get
; #:with ('l_τ:str ...) (×-get label from τ_rec)
; #:with (τ ...) (×-get τ_fld from τ_rec)
#:with (l_match:str τ_match) (str-stx-assoc #'l #'([l_τ τ] ...))
( (cadr (assoc l rec)) : τ_match)]
( (cadr (assoc l e_rec-)) : τ_match)]
#;[(_ e ...) #'(stlc:proj e ...)]))
(define-type-constructor
( [~ label τ_var] ...) #:lits (~)
( [<> label τ_var] ...) #:lits (<>)
#:declare label str
#:declare τ_var type)
@ -96,8 +95,9 @@
[(_ l:str = e as τ:type)
; #:when (? #'τ.norm)
; #:with (['l_τ:str τ_l] ...) (stx-map :-args (-args #'τ.norm))
#:with ('l_τ:str ...) (-get label from τ)
#:with (τ_l ...) (-get τ_var from τ)
#:with (~ [<> 'l_τ τ_l] ...) #'τ.norm
; #:with ('l_τ:str ...) (-get label from τ)
; #:with (τ_l ...) (-get τ_var from τ)
#:with (l_match:str τ_match) (str-stx-assoc #'l #'((l_τ τ_l) ...))
#:with (e- τ_e) (infer+erase #'e)
#:when (typecheck? #'τ_e #'τ_match)
@ -106,11 +106,9 @@
(syntax-parse stx #:datum-literals (of =>) #:literals (quote)
[(_ e [l:str x => e_l] ...)
#:fail-when (null? (syntax->list #'(l ...))) "no clauses"
#:with (e- τ_e) (infer+erase #'e)
#:with ('l_x:str ...) (-get label from τ_e)
#:with (τ_x ...) (-get τ_var from τ_e)
; #:when (? #'τ_e)
; #:with (['l_x:str τ_x] ...) (stx-map :-args (-args #'τ_e))
#:with (e- (~ [<> 'l_x τ_x] ...)) (infer+erase #'e)
; #:with ('l_x:str ...) (-get label from τ_e)
; #:with (τ_x ...) (-get τ_var from τ_e)
#:fail-unless (= (stx-length #'(l ...)) (stx-length #'(l_x ...))) "wrong number of case clauses"
#:fail-unless (typechecks? #'(l ...) #'(l_x ...)) "case clauses not exhaustive"
#:with (((x-) e_l- τ_el) ...)

View File

@ -23,8 +23,8 @@
( (list e- ...) : (× τ ...))]))
(define-syntax (proj stx)
(syntax-parse stx
[(_ e_tup n:integer)
#:with [e_tup- τs_tup] (×-match+erase #'e_tup)
#:fail-unless (< (syntax-e #'n) (stx-length #'τs_tup)) "proj index too large"
[(_ e_tup n:nat)
#:with [e_tup- τs_tup] (infer×+erase #'e_tup)
#:fail-unless (< (syntax-e #'n) (stx-length #'τs_tup)) "index too large"
( (list-ref e_tup- n) : #,(stx-list-ref #'τs_tup (syntax-e #'n)))]))

View File

@ -61,10 +61,12 @@
(define-syntax (app/tc stx)
(syntax-parse stx
[(_ e_fn e_arg ...)
; #:with [e_fn- τ_fn] (infer+erase #'e_fn)
; #:with (τ_in ...) (→-get τ_in from #'τ_fn)
; #:with τ_out (→-get τ_out from #'τ_fn)
#:with [e_fn- (τ_in ... τ_out)] (→-match+erase #'e_fn)
;; 2015-08-06: there are currently three alternative tycon matching syntaxes
#:with [e_fn- (~→ τ_in ... τ_out)] (infer+erase #'e_fn) ; #1 (external) pattern expander
;#:with [e_fn- τ_fn] (infer+erase #'e_fn) ; #2 get, via (internal) pattern expander
;#:with (τ_in ...) (→-get τ_in from #'τ_fn)
;#:with τ_out (→-get τ_out from #'τ_fn)
;#:with [e_fn- (τ_in ... τ_out)] (infer→+erase #'e_fn) ; #3 work directly on term
#:with ([e_arg- τ_arg] ...) (infers+erase #'(e_arg ...))
#:fail-unless (typechecks? #'(τ_arg ...) #'(τ_in ...))
(string-append

View File

@ -2,7 +2,6 @@
(require "rackunit-typechecking.rkt")
;; tests for stlc extensions
;; new literals and base types
(check-type "one" : String) ; literal now supported
(check-type #f : Bool) ; literal now supported
@ -95,6 +94,14 @@
(is-even? (sub1 n))))])
(is-odd? 11)) : Bool #t)
;; check some more err msgs
(typecheck-fail (and "1" #f) #:with-msg "Expected type of.+to be Bool")
(typecheck-fail (and #t "2") #:with-msg "Expected type of.+to be Bool")
(typecheck-fail (or "1" #f) #:with-msg "Expected type of.+to be Bool")
(typecheck-fail (or #t "2") #:with-msg "Expected type of.+to be Bool")
(typecheck-fail (if "true" 1 2) #:with-msg "Expected type of.+to be Bool")
;; tests from stlc+lit-tests.rkt --------------------------
; most should pass, some failing may now pass due to added types/forms
(check-type 1 : Int)
@ -115,7 +122,7 @@
(typecheck-fail
(λ ([f : Int]) (f 1 2))
#:with-msg
"Expected type of expression f to match pattern \\(→ τ_in ... τ_out\\), got: Int")
"Expected type of expression to match pattern \\(→ τ_in ... τ_out\\), got: Int")
(check-type (λ ([f : ( Int Int Int)] [x : Int] [y : Int]) (f x y))
: ( ( Int Int Int) Int Int Int))

View File

@ -7,10 +7,10 @@
(require "stlc+tup-tests.rkt")
(require "stlc+reco+var-tests.rkt")
(require "stlc+cons-tests.rkt")
;(require "stlc+box-tests.rkt")
;
;(require "stlc+rec-iso-tests.rkt")
;
(require "stlc+box-tests.rkt")
(require "stlc+rec-iso-tests.rkt")
;(require "exist-tests.rkt")
;
;;; subtyping

View File

@ -50,7 +50,7 @@
(check-type "Stephen" : String)
(check-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "name" String] [~× "phone" Int] [~× "male?" Bool]))
(× [: "name" String] [: "phone" Int] [: "male?" Bool]))
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "name")
: String "Stephen")
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "name")
@ -60,48 +60,48 @@
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "male?")
: Bool #t)
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "my-name" String] [~× "phone" Int] [~× "male?" Bool]))
(× [: "my-name" String] [: "phone" Int] [: "male?" Bool]))
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "name" String] [~× "my-phone" Int] [~× "male?" Bool]))
(× [: "name" String] [: "my-phone" Int] [: "male?" Bool]))
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "name" String] [~× "phone" Int] [~× "is-male?" Bool]))
(× [: "name" String] [: "phone" Int] [: "is-male?" Bool]))
(check-type (var "coffee" = (void) as ( [~ "coffee" Unit])) : ( [~ "coffee" Unit]))
(check-not-type (var "coffee" = (void) as ( [~ "coffee" Unit])) : ( [~ "coffee" Unit] [~ "tea" Unit]))
(typecheck-fail ((λ ([x : ( [~ "coffee" Unit] [~ "tea" Unit])]) x)
(var "coffee" = (void) as ( [~ "coffee" Unit]))))
(check-type (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
: ( [~ "coffee" Unit] [~ "tea" Unit]))
(check-type (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit] [~ "coke" Unit]))
: ( [~ "coffee" Unit] [~ "tea" Unit] [~ "coke" Unit]))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit])) : ( [<> "coffee" Unit]))
(check-not-type (var "coffee" = (void) as ( [<> "coffee" Unit])) : ( [<> "coffee" Unit] [<> "tea" Unit]))
(typecheck-fail ((λ ([x : ( [<> "coffee" Unit] [<> "tea" Unit])]) x)
(var "coffee" = (void) as ( [<> "coffee" Unit]))))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
: ( [<> "coffee" Unit] [<> "tea" Unit]))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit] [<> "coke" Unit]))
: ( [<> "coffee" Unit] [<> "tea" Unit] [<> "coke" Unit]))
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1])) ; not enough clauses
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1]
["teaaaaaa" x => 2])) ; wrong clause
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1]
["tea" x => 2]
["coke" x => 3])) ; too many clauses
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => "1"]
["tea" x => 2])) ; mismatched branch types
(check-type
(case (var "coffee" = 1 as ( [~ "coffee" Int] [~ "tea" Unit]))
(case (var "coffee" = 1 as ( [<> "coffee" Int] [<> "tea" Unit]))
["coffee" x => x]
["tea" x => 2]) : Int 1)
(define-type-alias Drink ( [~ "coffee" Int] [~ "tea" Unit] [~ "coke" Bool]))
(define-type-alias Drink ( [<> "coffee" Int] [<> "tea" Unit] [<> "coke" Bool]))
(check-type ((λ ([x : Int]) (+ x x)) 10) : Int 20)
(check-type (λ ([x : Int]) (+ (+ x x) (+ x x))) : ( Int Int))
(check-type
(case ((λ ([d : Drink]) d)
(var "coffee" = 1 as ( [~ "coffee" Int] [~ "tea" Unit] [~ "coke" Bool])))
(var "coffee" = 1 as ( [<> "coffee" Int] [<> "tea" Unit] [<> "coke" Bool])))
["coffee" x => (+ (+ x x) (+ x x))]
["tea" x => 2]
["coke" y => 3])

View File

@ -25,8 +25,10 @@
(define fn-lst (cons (λ ([x : Int]) (+ 10 x)) (nil {( Int Int)})))
(check-type fn-lst : (List ( Int Int)))
(check-type (isnil fn-lst) : Bool #f)
(typecheck-fail (isnil (head fn-lst))
#:with-msg "expected argument of List type")
(typecheck-fail
(isnil (head fn-lst))
#:with-msg
"Expected type of expression \\(head fn-lst) to match pattern \\(List τ), got: \\(→ Int Int)")
(check-type (isnil (tail fn-lst)) : Bool #t)
(check-type (head fn-lst) : ( Int Int))
(check-type ((head fn-lst) 25) : Int 35)
@ -46,7 +48,7 @@
;; records (ie labeled tuples)
(check-type "Stephen" : String)
(check-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "name" String] [~× "phone" Int] [~× "male?" Bool]))
(× [: "name" String] [: "phone" Int] [: "male?" Bool]))
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "name")
: String "Stephen")
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "name")
@ -56,48 +58,48 @@
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "male?")
: Bool #t)
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "my-name" String] [~× "phone" Int] [~× "male?" Bool]))
(× [: "my-name" String] [: "phone" Int] [: "male?" Bool]))
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "name" String] [~× "my-phone" Int] [~× "male?" Bool]))
(× [: "name" String] [: "my-phone" Int] [: "male?" Bool]))
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "name" String] [~× "phone" Int] [~× "is-male?" Bool]))
(× [: "name" String] [: "phone" Int] [: "is-male?" Bool]))
(check-type (var "coffee" = (void) as ( [~ "coffee" Unit])) : ( [~ "coffee" Unit]))
(check-not-type (var "coffee" = (void) as ( [~ "coffee" Unit])) : ( [~ "coffee" Unit] [~ "tea" Unit]))
(typecheck-fail ((λ ([x : ( [~ "coffee" Unit] [~ "tea" Unit])]) x)
(var "coffee" = (void) as ( [~ "coffee" Unit]))))
(check-type (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
: ( [~ "coffee" Unit] [~ "tea" Unit]))
(check-type (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit] [~ "coke" Unit]))
: ( [~ "coffee" Unit] [~ "tea" Unit] [~ "coke" Unit]))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit])) : ( [<> "coffee" Unit]))
(check-not-type (var "coffee" = (void) as ( [<> "coffee" Unit])) : ( [<> "coffee" Unit] [<> "tea" Unit]))
(typecheck-fail ((λ ([x : ( [<> "coffee" Unit] [<> "tea" Unit])]) x)
(var "coffee" = (void) as ( [<> "coffee" Unit]))))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
: ( [<> "coffee" Unit] [<> "tea" Unit]))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit] [<> "coke" Unit]))
: ( [<> "coffee" Unit] [<> "tea" Unit] [<> "coke" Unit]))
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1])) ; not enough clauses
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1]
["teaaaaaa" x => 2])) ; wrong clause
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1]
["tea" x => 2]
["coke" x => 3])) ; too many clauses
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => "1"]
["tea" x => 2])) ; mismatched branch types
(check-type
(case (var "coffee" = 1 as ( [~ "coffee" Int] [~ "tea" Unit]))
(case (var "coffee" = 1 as ( [<> "coffee" Int] [<> "tea" Unit]))
["coffee" x => x]
["tea" x => 2]) : Int 1)
(define-type-alias Drink ( [~ "coffee" Int] [~ "tea" Unit] [~ "coke" Bool]))
(define-type-alias Drink ( [<> "coffee" Int] [<> "tea" Unit] [<> "coke" Bool]))
(check-type ((λ ([x : Int]) (+ x x)) 10) : Int 20)
(check-type (λ ([x : Int]) (+ (+ x x) (+ x x))) : ( Int Int))
(check-type
(case ((λ ([d : Drink]) d)
(var "coffee" = 1 as ( [~ "coffee" Int] [~ "tea" Unit] [~ "coke" Bool])))
(var "coffee" = 1 as ( [<> "coffee" Int] [<> "tea" Unit] [<> "coke" Bool])))
["coffee" x => (+ (+ x x) (+ x x))]
["tea" x => 2]
["coke" y => 3])

View File

@ -33,7 +33,7 @@
(typecheck-fail
(λ ([f : Int]) (f 1 2))
#:with-msg
"Expected type of expression f to match pattern \\(→ τ_in ... τ_out\\), got: Int")
"Expected type of expression to match pattern \\(→ τ_in ... τ_out\\), got: Int")
(check-type (λ ([f : ( Int Int Int)] [x : Int] [y : Int]) (f x y))
: ( ( Int Int Int) Int Int Int))

View File

@ -1,19 +1,21 @@
#lang s-exp "../stlc+rec-iso.rkt"
(require "rackunit-typechecking.rkt")
(define-type-alias IntList (μ (X) ( [: "nil" Unit] [: "cons" (× Int X)])))
(define-type-alias ILBody ( [: "nil" Unit] [: "cons" (× Int IntList)]))
(define-type-alias IntList (μ [[X]] ( [<> "nil" Unit] [<> "cons" (× Int X)])))
(define-type-alias ILBody ( [<> "nil" Unit] [<> "cons" (× Int IntList)]))
;; nil
(define nil (fld {IntList} (var "nil" = (void) as ILBody)))
(check-type nil : IntList)
; cons
;; cons
(define cons (λ ([n : Int] [lst : IntList]) (fld {IntList} (var "cons" = (tup n lst) as ILBody))))
(check-type cons : ( Int IntList IntList))
(check-type (cons 1 nil) : IntList)
(typecheck-fail (cons 1 2))
(typecheck-fail (cons "1" nil))
; isnil
;; isnil
(define isnil
(λ ([lst : IntList])
(case (unfld {IntList} lst)
@ -27,7 +29,7 @@
(check-type (λ ([f : ( IntList Bool)]) (f nil)) : ( ( IntList Bool) Bool))
(check-type ((λ ([f : ( IntList Bool)]) (f nil)) isnil) : Bool #t)
; hd
;; hd
(define hd
(λ ([lst : IntList])
(case (unfld {IntList} lst)
@ -38,7 +40,7 @@
(typecheck-fail (hd 1))
(check-type (hd (cons 11 nil)) : Int 11)
; tl
;; tl
(define tl
(λ ([lst : IntList])
(case (unfld {IntList} lst)
@ -63,59 +65,60 @@
(check-type (λ ([f : ArithBinOp]) (f 1 2)) : ( ( Int Int Int) Int))
;; records (ie labeled tuples)
; no records, only tuples
(check-type "Stephen" : String)
(check-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [: "name" String] [: "phone" Int] [: "male?" Bool]))
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "name")
: String "Stephen")
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "name")
: String "Stephen")
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "phone")
: Int 781)
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "male?")
: Bool #t)
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [: "my-name" String] [: "phone" Int] [: "male?" Bool]))
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [: "name" String] [: "my-phone" Int] [: "male?" Bool]))
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [: "name" String] [: "phone" Int] [: "is-male?" Bool]))
;(check-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
; (× [: "name" String] [: "phone" Int] [: "male?" Bool]))
;(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "name")
; : String ⇒ "Stephen")
;(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "name")
; : String ⇒ "Stephen")
;(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "phone")
; : Int ⇒ 781)
;(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "male?")
; : Bool ⇒ #t)
;(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
; (× [: "my-name" String] [: "phone" Int] [: "male?" Bool]))
;(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
; (× [: "name" String] [: "my-phone" Int] [: "male?" Bool]))
;(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
; (× [: "name" String] [: "phone" Int] [: "is-male?" Bool]))
;; variants
(check-type (var "coffee" = (void) as ( [: "coffee" Unit])) : ( [: "coffee" Unit]))
(check-not-type (var "coffee" = (void) as ( [: "coffee" Unit])) : ( [: "coffee" Unit] [: "tea" Unit]))
(typecheck-fail ((λ ([x : ( [: "coffee" Unit] [: "tea" Unit])]) x)
(var "coffee" = (void) as ( [: "coffee" Unit]))))
(check-type (var "coffee" = (void) as ( [: "coffee" Unit] [: "tea" Unit])) : ( [: "coffee" Unit] [: "tea" Unit]))
(check-type (var "coffee" = (void) as ( [: "coffee" Unit] [: "tea" Unit] [: "coke" Unit]))
: ( [: "coffee" Unit] [: "tea" Unit] [: "coke" Unit]))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit])) : ( [<> "coffee" Unit]))
(check-not-type (var "coffee" = (void) as ( [<> "coffee" Unit])) : ( [<> "coffee" Unit] [<> "tea" Unit]))
(typecheck-fail ((λ ([x : ( [<> "coffee" Unit] [<> "tea" Unit])]) x)
(var "coffee" = (void) as ( [<> "coffee" Unit]))))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit])) : ( [<> "coffee" Unit] [<> "tea" Unit]))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit] [<> "coke" Unit]))
: ( [<> "coffee" Unit] [<> "tea" Unit] [<> "coke" Unit]))
(typecheck-fail
(case (var "coffee" = (void) as ( [: "coffee" Unit] [: "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1])) ; not enough clauses
(typecheck-fail
(case (var "coffee" = (void) as ( [: "coffee" Unit] [: "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1]
["teaaaaaa" x => 2])) ; wrong clause
(typecheck-fail
(case (var "coffee" = (void) as ( [: "coffee" Unit] [: "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1]
["tea" x => 2]
["coke" x => 3])) ; too many clauses
(typecheck-fail
(case (var "coffee" = (void) as ( [: "coffee" Unit] [: "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => "1"]
["tea" x => 2])) ; mismatched branch types
(check-type
(case (var "coffee" = 1 as ( [: "coffee" Int] [: "tea" Unit]))
(case (var "coffee" = 1 as ( [<> "coffee" Int] [<> "tea" Unit]))
["coffee" x => x]
["tea" x => 2]) : Int 1)
(define-type-alias Drink ( [: "coffee" Int] [: "tea" Unit] [: "coke" Bool]))
(define-type-alias Drink ( [<> "coffee" Int] [<> "tea" Unit] [<> "coke" Bool]))
(check-type ((λ ([x : Int]) (+ x x)) 10) : Int 20)
(check-type (λ ([x : Int]) (+ (+ x x) (+ x x))) : ( Int Int))
(check-type
(case ((λ ([d : Drink]) d)
(var "coffee" = 1 as ( [: "coffee" Int] [: "tea" Unit] [: "coke" Bool])))
(var "coffee" = 1 as ( [<> "coffee" Int] [<> "tea" Unit] [<> "coke" Bool])))
["coffee" x => (+ (+ x x) (+ x x))]
["tea" x => 2]
["coke" y => 3])
@ -141,7 +144,10 @@
(check-type (proj (tup 1 "2" #f) 1) : String "2")
(check-type (proj (tup 1 "2" #f) 2) : Bool #f)
(typecheck-fail (proj (tup 1 "2" #f) 3)) ; index too large
(typecheck-fail (proj 1 2)) ; not tuple
(typecheck-fail
(proj 1 2)
#:with-msg
"Expected type of expression 1 to match pattern \\(× τ ...), got: Int")
;; ext-stlc.rkt tests ---------------------------------------------------------
;; should still pass

View File

@ -15,10 +15,10 @@
; records (ie labeled tuples)
(check-type "Stephen" : String)
(check-type (tup) : (×))
(check-type (tup ["name" = "Stephen"]) : (× [~× "name" String]))
(check-type (tup ["name" = "Stephen"]) : (× [: "name" String]))
(check-type (proj (tup ["name" = "Stephen"]) "name") : String "Stephen")
(check-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "name" String] [~× "phone" Int] [~× "male?" Bool]))
(× [: "name" String] [: "phone" Int] [: "male?" Bool]))
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "name")
: String "Stephen")
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "name")
@ -28,53 +28,53 @@
(check-type (proj (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) "male?")
: Bool #t)
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "my-name" String] [~× "phone" Int] [~× "male?" Bool]))
(× [: "my-name" String] [: "phone" Int] [: "male?" Bool]))
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "name" String] [~× "my-phone" Int] [~× "male?" Bool]))
(× [: "name" String] [: "my-phone" Int] [: "male?" Bool]))
(check-not-type (tup ["name" = "Stephen"] ["phone" = 781] ["male?" = #t]) :
(× [~× "name" String] [~× "phone" Int] [~× "is-male?" Bool]))
(× [: "name" String] [: "phone" Int] [: "is-male?" Bool]))
;; variants
(check-type (var "coffee" = (void) as ( [~ "coffee" Unit])) : ( [~ "coffee" Unit]))
(check-not-type (var "coffee" = (void) as ( [~ "coffee" Unit])) : ( [~ "coffee" Unit] [~ "tea" Unit]))
(typecheck-fail ((λ ([x : ( [~ "coffee" Unit] [~ "tea" Unit])]) x)
(var "coffee" = (void) as ( [~ "coffee" Unit])))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit])) : ( [<> "coffee" Unit]))
(check-not-type (var "coffee" = (void) as ( [<> "coffee" Unit])) : ( [<> "coffee" Unit] [<> "tea" Unit]))
(typecheck-fail ((λ ([x : ( [<> "coffee" Unit] [<> "tea" Unit])]) x)
(var "coffee" = (void) as ( [<> "coffee" Unit])))
#:with-msg "Arguments to function.+have wrong type")
(check-type (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit])) :
( [~ "coffee" Unit] [~ "tea" Unit]))
(check-type (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit] [~ "coke" Unit]))
: ( [~ "coffee" Unit] [~ "tea" Unit] [~ "coke" Unit]))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit])) :
( [<> "coffee" Unit] [<> "tea" Unit]))
(check-type (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit] [<> "coke" Unit]))
: ( [<> "coffee" Unit] [<> "tea" Unit] [<> "coke" Unit]))
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1])
#:with-msg "wrong number of case clauses")
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1]
["teaaaaaa" x => 2])
#:with-msg "case clauses not exhaustive")
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => 1]
["tea" x => 2]
["coke" x => 3])
#:with-msg "wrong number of case clauses")
(typecheck-fail
(case (var "coffee" = (void) as ( [~ "coffee" Unit] [~ "tea" Unit]))
(case (var "coffee" = (void) as ( [<> "coffee" Unit] [<> "tea" Unit]))
["coffee" x => "1"]
["tea" x => 2])
#:with-msg "branches have different types")
(check-type
(case (var "coffee" = 1 as ( [~ "coffee" Int] [~ "tea" Unit]))
(case (var "coffee" = 1 as ( [<> "coffee" Int] [<> "tea" Unit]))
["coffee" x => x]
["tea" x => 2]) : Int 1)
(define-type-alias Drink ( [~ "coffee" Int] [~ "tea" Unit] [~ "coke" Bool]))
(define-type-alias Drink ( [<> "coffee" Int] [<> "tea" Unit] [<> "coke" Bool]))
(check-type ((λ ([x : Int]) (+ x x)) 10) : Int 20)
(check-type (λ ([x : Int]) (+ (+ x x) (+ x x))) : ( Int Int))
(check-type
(case ((λ ([d : Drink]) d)
(var "coffee" = 1 as ( [~ "coffee" Int] [~ "tea" Unit] [~ "coke" Bool])))
(var "coffee" = 1 as ( [<> "coffee" Int] [<> "tea" Unit] [<> "coke" Bool])))
["coffee" x => (+ (+ x x) (+ x x))]
["tea" x => 2]
["coke" y => 3])
@ -89,18 +89,18 @@
;; previous tuple tests: ------------------------------------------------------------
;; wont work anymore
;(check-type (tup 1 2 3) : (× Int Int Int))
;(check-type (tup 1 "1" #f +) : (× Int String Bool (→ Int Int Int)))
;(check-not-type (tup 1 "1" #f +) : (× Unit String Bool (→ Int Int Int)))
;(check-not-type (tup 1 "1" #f +) : (× Int Unit Bool (→ Int Int Int)))
;(check-not-type (tup 1 "1" #f +) : (× Int String Unit (→ Int Int Int)))
;(check-not-type (tup 1 "1" #f +) : (× Int String Bool (→ Int Int Unit)))
;
;(check-type (proj (tup 1 "2" #f) 0) : Int ⇒ 1)
;(check-type (proj (tup 1 "2" #f) 1) : String ⇒ "2")
;(check-type (proj (tup 1 "2" #f) 2) : Bool ⇒ #f)
;(typecheck-fail (proj (tup 1 "2" #f) 3)) ; index too large
;(typecheck-fail (proj 1 2)) ; not tuple
;;(check-type (tup 1 2 3) : (× Int Int Int))
;;(check-type (tup 1 "1" #f +) : (× Int String Bool (→ Int Int Int)))
;;(check-not-type (tup 1 "1" #f +) : (× Unit String Bool (→ Int Int Int)))
;;(check-not-type (tup 1 "1" #f +) : (× Int Unit Bool (→ Int Int Int)))
;;(check-not-type (tup 1 "1" #f +) : (× Int String Unit (→ Int Int Int)))
;;(check-not-type (tup 1 "1" #f +) : (× Int String Bool (→ Int Int Unit)))
;;
;;(check-type (proj (tup 1 "2" #f) 0) : Int ⇒ 1)
;;(check-type (proj (tup 1 "2" #f) 1) : String ⇒ "2")
;;(check-type (proj (tup 1 "2" #f) 2) : Bool ⇒ #f)
;;(typecheck-fail (proj (tup 1 "2" #f) 3)) ; index too large
;;(typecheck-fail (proj 1 2)) ; not tuple
;; ext-stlc.rkt tests ---------------------------------------------------------
;; should still pass
@ -139,7 +139,7 @@
(check-type (let* ([x 10] [y (+ x 1)]) (+ x y)) : Int 21)
(typecheck-fail (let* ([x #t] [y (+ x 1)]) 1))
; letrec
;; letrec
(typecheck-fail (letrec ([(x : Int) #f] [(y : Int) 1]) y))
(typecheck-fail (letrec ([(y : Int) 1] [(x : Int) #f]) x))

View File

@ -12,6 +12,7 @@
(check-type (proj (tup 1 "2" #f) 0) : Int 1)
(check-type (proj (tup 1 "2" #f) 1) : String "2")
(check-type (proj (tup 1 "2" #f) 2) : Bool #f)
(typecheck-fail (proj (tup 1 "2" #f) -1) #:with-msg "expected exact-nonnegative-integer")
(typecheck-fail (proj (tup 1 "2" #f) 3) #:with-msg "index too large")
(typecheck-fail
(proj 1 2)

View File

@ -4,7 +4,7 @@
syntax/parse racket/syntax syntax/stx
"stx-utils.rkt"
syntax/parse/debug)
(for-meta 2 racket/base syntax/parse racket/syntax)
(for-meta 2 racket/base syntax/parse racket/syntax syntax/stx "stx-utils.rkt")
(for-meta 3 racket/base syntax/parse racket/syntax)
racket/provide)
(provide
@ -66,8 +66,10 @@
[(_ τ:id)
#:with τ? (format-id #'τ "~a?" #'τ)
#:with τ-internal (generate-temporary #'τ)
#:with inferτ+erase (format-id #'τ "infer~a+erase" #'τ)
#:with τ-cls (generate-temporary #'τ)
#'(begin
(provide τ (for-syntax τ?))
(provide τ (for-syntax τ? inferτ+erase))
(define τ-internal
(λ () (raise (exn:fail:type:runtime
(format "~a: Cannot use type at run time" 'τ)
@ -75,10 +77,25 @@
(define-syntax (τ stx)
(syntax-parse stx
[x:id (add-orig #'(#%type (τ-internal)) #'τ)]))
(define-for-syntax (τ? t)
(syntax-parse ((current-type-eval) t)
[((~literal #%plain-type) ((~literal #%plain-app) ty))
(typecheck? #'ty #'τ-internal)])))]))
(begin-for-syntax
; expanded form of τ
(define-syntax-class τ-cls
(pattern ((~literal #%plain-type) ((~literal #%plain-app) ty))))
(define (τ? t)
(syntax-parse ((current-type-eval) t)
[expanded-type
#:declare expanded-type τ-cls
(typecheck? #'expanded-type.ty #'τ-internal)]))
(define (inferτ+erase e)
(syntax-parse (infer+erase e) #:context e
[(e- expanded-type)
#:declare expanded-type τ-cls
#:fail-unless (typecheck? #'expanded-type.ty #'τ-internal)
(format
"~a (~a:~a): Expected type of expression ~v to be ~a, got: ~a"
(syntax-source e) (syntax-line e) (syntax-column e)
(syntax->datum e) (type->str #'τ) (type->str #'expanded-type))
#'e-]))))]))
(begin-for-syntax
;; type validation
@ -114,7 +131,10 @@
(syntax-parse stx
[(_ ty tycon cls)
#'(syntax-parse ((current-type-eval) ty)
[((~literal #%plain-type) ((~literal #%plain-app) t . args))
[((~literal #%plain-type)
((~literal #%plain-lambda) (tv:id (... ...))
((~literal let-values) () ((~literal let-values) ()
((~literal #%plain-app) t . args)))))
#:declare args cls ; check shape of arguments
#:fail-unless (typecheck? #'t #'tycon) ; check tycons match
(format "Type error: expected ~a type, got ~a"
@ -123,9 +143,26 @@
[_ #f])]))
)
(begin-for-syntax
(define (bracket? stx)
(define paren-shape/#f (syntax-property stx 'paren-shape))
(and paren-shape/#f (char=? paren-shape/#f #\[)))
(define-syntax-class bound-vars
(pattern (~and stx [[x:id ...]])
#:when (and (bracket? #'stx)
(bracket? (stx-car #'stx)))))
(begin-for-syntax
(define (bracket? stx)
(define paren-shape/#f (syntax-property stx 'paren-shape))
(and paren-shape/#f (char=? paren-shape/#f #\[)))
(define-syntax-class bound-vars
(pattern (~and stx [[x:id ...]])
#:when (and (bracket? #'stx)
(bracket? (stx-car #'stx)))))))
(define-syntax define-type-constructor
(syntax-parser
[(_ (τ:id . pat)
[(_ (τ:id (~optional bvs-pat:bound-vars #:defaults ([bvs-pat.stx #'[[]]])) . pat)
; lits must have ~ prefix (for syntax-parse compat) for now
(~optional (~seq #:lits (lit ...)) #:defaults ([(lit 1) null]))
decls ...
@ -134,7 +171,8 @@
#:with τ-match (format-id #'τ "~a-match" #'τ)
#:with τ? (format-id #'τ "~a?" #'τ)
#:with τ-get (format-id #'τ "~a-get" #'τ)
#:with τ-match+erase (format-id #'τ "~a-match+erase" #'τ)
#:with τ-match+erase (format-id #'τ "infer~a+erase" #'τ)
#:with τ-expander (format-id #'τ "~~~a" #'τ)
#:with pat-class (generate-temporary #'τ) ; syntax-class name
#:with tycon (generate-temporary #'τ) ; need a runtime id for expansion
#:with (lit-tmp ...) (generate-temporaries #'(lit ...))
@ -145,35 +183,45 @@
(define lit-tmp void) ...
(define-syntax lit (syntax-parser [(_ . xs) #'(lit-tmp . xs)])) ...
(provide lit ...)
(provide τ)
(provide τ (for-syntax τ-expander))
(begin-for-syntax
(define-syntax lit
#'(define-syntax lit
(pattern-expander
(syntax-parser
[(_ . xs)
;#:when (displayln "pattern expanding")
#'((~literal #%plain-app) (~literal lit-tmp) . xs)]))) ...
(define-syntax τ-expander
(pattern-expander
(syntax-parser
[(_ (~optional
(~and bvs:bound-vars bvs-pat.stx) #:defaults ([(bvs.x 1) null]))
. match-pat)
#:with pat-from-constructor (quote-syntax (τ . pat))
;; manually replace literals with expanded form, to get around ~ restriction
#:with new-match-pat
#`#,(subst-datum-lits
#`((#,(quote-syntax ~seq) (~literal #%plain-app) (~literal lit-tmp)) ...)
#'(lit ...)
#'match-pat)
#'(~and
(~or
((~literal #%plain-type)
((~literal #%plain-lambda) (bvs.x (... ...))
((~literal let-values) () ((~literal let-values) ()
((~literal #%plain-app) (~literal tycon) . new-match-pat)))))
(~and any
(~do
(type-error #:src #'any
#:msg
"Expected type of expression to match pattern ~a, got: ~a"
(quote-syntax pat-from-constructor) #'any))))
~!)])))
(define-syntax-class pat-class
;; dont check is-type? here; should already be types
;; only check is-type? for user-entered types, eg tycon call
;; thus, dont include decls here, only want shape
#;(pattern (~and pre (~do (printf "trying to match: ~a\n"(syntax->datum #'pre))) pat (~do (displayln "no")))) ; uses "lit" pattern expander
(pattern pat)
#;(pattern any
#:when (printf "trying to match: ~a\n" (syntax->datum #'any))
#:when (printf "orig: ~a\n" (type->str #'any))
#:when (printf "against pattern: ~a\n" (syntax->datum (quote-syntax pat)))
#:when (displayln #`(#,(stx-cdr (stx-car #'any))))
#:when (pretty-print (debug-parse #`(#,(stx-cdr (stx-car #'any))) pat))
#:with pat #'any) ;#`(#,(stx-cdr (stx-car #'any))))
#;(pattern any
#:when (displayln "match failed")
#:when (displayln "pat: ")
#:when (displayln (quote-syntax pat))
#:when (displayln #'any)
#:when (displayln "orig")
#:when (displayln (type->str #'any))
#:with pat #'any))
; uses "lit" pattern expander
(pattern pat))
(define (τ-match ty)
(or (match-type ty tycon pat-class)
;; error msg should go in specific macro def?
@ -206,7 +254,10 @@
(format "~a (~a:~a) Expected type with pattern: ~a, got: ~a"
(syntax-source #'typ) (syntax-line #'typ) (syntax-column #'typ)
(type->str (quote-syntax the-pat)) (type->str #'typ))
#:with ((~literal #%plain-type) ((~literal #%plain-app) f . args))
#:with ((~literal #%plain-type)
((~literal #%plain-lambda) tvs
((~literal let-values) () ((~literal let-values) ()
((~literal #%plain-app) f . args)))))
((current-type-eval) #'typ)
#:declare args pat-class ; check shape of arguments
; #:fail-unless (typecheck? #'f #'tycon) ; check tycons match
@ -218,12 +269,16 @@
(current-continuation-marks)))))
(define-syntax (τ stx)
(syntax-parse stx #:literals (lit ...)
[(_ . (~and pat !~ args)) ; first check shape
[(_ (~optional (~and bvs:bound-vars bvs-pat.stx) #:defaults ([(bvs.x 1) null]))
. (~and pat !~ args)) ; first check shape
; this inner syntax-parse gets the ~! to register
; otherwise, apparently #:declare's get subst into pat (before ~!)
(syntax-parse #'args #:literals (lit ...)
[pat #,@#'(decls ...) ; then check declarations (eg, valid type)
#'(#%type (tycon . args))])]
#'(#%type
(λ (bvs.x (... ...))
(let-syntax ([bvs.x (syntax-parser [bvs.x:id #'(#%type bvs.x)])] (... ...))
(tycon . args))))])]
[_
(type-error #:src stx
#:msg (string-append
@ -239,61 +294,61 @@
;; TODO: refine this to enable specifying arity information
;; type constructors currently must have 1+ arguments
#;(define-syntax (define-type-constructor stx)
(syntax-parse stx
[(_ τ:id (~optional (~seq #:arity n:exact-positive-integer)))
#:with τ? (format-id #'τ "~a?" #'τ)
#:with τ-ref (format-id #'τ "~a-ref" #'τ)
#:with τ-num-args (format-id #'τ "~a-num-args" #'τ)
#:with τ-args (format-id #'τ "~a-args" #'τ)
#:with τ-match (format-id #'τ "~a-match" #'τ)
#:with tmp (generate-temporary #'τ)
#`(begin
;; TODO: define syntax class instead of these separate tycon fns
(provide τ (for-syntax τ? τ-ref τ-num-args τ-args))
(define tmp (λ _ (raise (exn:fail:type:runtime
(format "~a: Cannot use type at run time" 'τ)
(current-continuation-marks)))))
(define-syntax (τ stx)
(syntax-parse stx
[x:id
(type-error #:src #'x
#:msg "Cannot use type constructor ~a in non-application position"
#'τ)]
[(_) ; default tycon requires 1+ args
#:when (not #,(attribute n))
(type-error #:src stx
#:msg "Type constructor must have at least 1 argument.")]
[(_ x (... ...))
#:when #,(and (attribute n)
#'(not (= n (stx-length #'(x (... ...))))))
#:with m #,(and (attribute n) #'n)
(type-error #:src stx
#:msg "Type constructor ~a expected ~a argument(s), given: ~a"
#'τ #'m #'(x (... ...)))]
; this is racket's #%app
[(_ x (... ...)) #'(tmp x (... ...))]))
; TODO: ok to assume type in canonical (ie, fully expanded) form?
;; yes for now
(define-for-syntax (τ? stx)
(syntax-parse ((current-promote) stx)
[((~literal #%plain-app) tycon τ_arg (... ...)) (typecheck? #'tycon #'tmp)]
[_ #f]))
(define-for-syntax (τ-ref stx m)
(syntax-parse stx
[((~literal #%plain-app) tycon τ_arg (... ...))
#:when (typecheck? #'tycon #'tmp)
(stx-list-ref #'(τ_arg (... ...)) m)]))
(define-for-syntax (τ-args stx)
(syntax-parse ((current-promote) stx)
[((~literal #%plain-app) tycon τ_arg (... ...))
#:when (typecheck? #'tycon #'tmp)
#'(τ_arg (... ...))]))
(define-for-syntax (τ-num-args stx)
(syntax-parse stx
[((~literal #%plain-app) tycon τ_arg (... ...))
#:when (typecheck? #'tycon #'tmp)
(stx-length #'(τ_arg (... ...)))])))]))
;#;(define-syntax (define-type-constructor stx)
; (syntax-parse stx
; [(_ τ:id (~optional (~seq #:arity n:exact-positive-integer)))
; #:with τ? (format-id #'τ "~a?" #'τ)
; #:with τ-ref (format-id #'τ "~a-ref" #'τ)
; #:with τ-num-args (format-id #'τ "~a-num-args" #'τ)
; #:with τ-args (format-id #'τ "~a-args" #'τ)
; #:with τ-match (format-id #'τ "~a-match" #'τ)
; #:with tmp (generate-temporary #'τ)
; #`(begin
; ;; TODO: define syntax class instead of these separate tycon fns
; (provide τ (for-syntax τ? τ-ref τ-num-args τ-args))
; (define tmp (λ _ (raise (exn:fail:type:runtime
; (format "~a: Cannot use type at run time" 'τ)
; (current-continuation-marks)))))
; (define-syntax (τ stx)
; (syntax-parse stx
; [x:id
; (type-error #:src #'x
; #:msg "Cannot use type constructor ~a in non-application position"
; #'τ)]
; [(_) ; default tycon requires 1+ args
; #:when (not #,(attribute n))
; (type-error #:src stx
; #:msg "Type constructor must have at least 1 argument.")]
; [(_ x (... ...))
; #:when #,(and (attribute n)
; #'(not (= n (stx-length #'(x (... ...))))))
; #:with m #,(and (attribute n) #'n)
; (type-error #:src stx
; #:msg "Type constructor ~a expected ~a argument(s), given: ~a"
; #'τ #'m #'(x (... ...)))]
; ; this is racket's #%app
; [(_ x (... ...)) #'(tmp x (... ...))]))
; ; TODO: ok to assume type in canonical (ie, fully expanded) form?
; ;; yes for now
; (define-for-syntax (τ? stx)
; (syntax-parse ((current-promote) stx)
; [((~literal #%plain-app) tycon τ_arg (... ...)) (typecheck? #'tycon #'tmp)]
; [_ #f]))
; (define-for-syntax (τ-ref stx m)
; (syntax-parse stx
; [((~literal #%plain-app) tycon τ_arg (... ...))
; #:when (typecheck? #'tycon #'tmp)
; (stx-list-ref #'(τ_arg (... ...)) m)]))
; (define-for-syntax (τ-args stx)
; (syntax-parse ((current-promote) stx)
; [((~literal #%plain-app) tycon τ_arg (... ...))
; #:when (typecheck? #'tycon #'tmp)
; #'(τ_arg (... ...))]))
; (define-for-syntax (τ-num-args stx)
; (syntax-parse stx
; [((~literal #%plain-app) tycon τ_arg (... ...))
; #:when (typecheck? #'tycon #'tmp)
; (stx-length #'(τ_arg (... ...)))])))]))
;; syntax classes
(begin-for-syntax
@ -471,17 +526,31 @@
(define-for-syntax (mk-pred x) (format-id x "~a?" x))
(define-for-syntax (mk-acc base field) (format-id base "~a-~a" base field))
; subst τ for y in e, if (bound-id=? x y)
(define-for-syntax (subst τ x e)
(syntax-parse e
[y:id #:when (bound-identifier=? e x) τ]
[(esub ...)
#:with (esub_subst ...) (stx-map (λ (e1) (subst τ x e1)) #'(esub ...))
(syntax-track-origin #'(esub_subst ...) e x)]
[_ e]))
(begin-for-syntax
; subst τ for y in e, if (bound-id=? x y)
(define (subst τ x e)
(syntax-parse e
[y:id #:when (bound-identifier=? e x) τ]
[(esub ...)
#:with (esub_subst ...) (stx-map (λ (e1) (subst τ x e1)) #'(esub ...))
(syntax-track-origin #'(esub_subst ...) e x)]
[_ e]))
(define (substs τs xs e)
(stx-fold subst e τs xs))
; subst τ for y in e, if (equal? (syntax-e x) (syntax-e y))
(define-for-syntax (subst-datum-lit τ x e)
(syntax-parse e
[y:id #:when (equal? (syntax-e e) (syntax-e x)) τ]
[(esub ...)
#:with (esub_subst ...) (stx-map (λ (e1) (subst-datum-lit τ x e1)) #'(esub ...))
(syntax-track-origin #'(esub_subst ...) e x)]
[_ e]))
(define-for-syntax (subst-datum-lits τs xs e)
(stx-fold subst-datum-lit e τs xs)))
(define-for-syntax (substs τs xs e)
(stx-fold subst e τs xs))
;; type environment -----------------------------------------------------------
#;(begin-for-syntax