www

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

commit 98fdc8806cd78cc454cd2e59454797399494b08c
parent ad02e586ad05d753479764dd3ad76a9c75311316
Author: Stephen Chang <stchang@ccs.neu.edu>
Date:   Fri, 26 Jun 2015 18:56:01 -0400

convert type representation to fully expanded syntax

- type-eval = expand/df
- fomega tyapp and tylam not yet implemented

Diffstat:
Mtapl/ext-stlc.rkt | 2+-
Mtapl/fomega.rkt | 109++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Atapl/id-expand-experiment.rkt | 52++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtapl/notes.txt | 95+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtapl/stlc+box.rkt | 8+++++---
Mtapl/stlc+cons.rkt | 18+++++++++++-------
Mtapl/stlc+rec+sub.rkt | 16+++++++++++-----
Mtapl/stlc+sub.rkt | 11++++++++---
Mtapl/stlc+tup.rkt | 4++--
Mtapl/stlc+var.rkt | 51+++++++++++++++++++++++++--------------------------
Mtapl/stlc.rkt | 56++++++++++++++++++++++++++++++--------------------------
Mtapl/sysf.rkt | 55++++++++++++++++++++++++++++++++++---------------------
Mtapl/tests/fomega-tests.rkt | 52++++++++++++++++++++++++++++++++++++++++++----------
Mtapl/tests/rackunit-typechecking.rkt | 10++++------
Mtapl/tests/run-all-tests.rkt | 5+++--
Mtapl/tests/stlc+box-tests.rkt | 41++++++++++++++++++++++-------------------
Mtapl/tests/stlc+cons-tests.rkt | 39+++++++++++++++++++++------------------
Mtapl/tests/stlc+rec+sub-tests.rkt | 34++++++++++++++++++----------------
Mtapl/tests/stlc+var-tests.rkt | 36++++++++++++++++++------------------
Mtapl/tests/sysf-tests.rkt | 10++++++++--
Mtapl/typecheck.rkt | 122+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
21 files changed, 569 insertions(+), 257 deletions(-)

diff --git a/tapl/ext-stlc.rkt b/tapl/ext-stlc.rkt @@ -119,7 +119,7 @@ (string-append "letrec: type check fail, args have wrong type:\n" (string-join - (stx-map (λ (e τ τ-expect) (format "~a has type ~a, expected ~a")) + (stx-map (λ (e τ τ-expect) (format "~a has type ~a, expected ~a" e τ τ-expect)) #'(e ...) #'(τ ...) #'(b.τ ...)) "\n")) (⊢ #'(letrec ([x- e-] ...) e_body-) #'τ_body)])) diff --git a/tapl/fomega.rkt b/tapl/fomega.rkt @@ -1,13 +1,14 @@ #lang racket/base (require "typecheck.rkt") -(require (except-in "sysf.rkt" #%app λ + Λ inst Int → ∀ eval-τ type=?) - (prefix-in sysf: (only-in "sysf.rkt" Int → ∀ eval-τ type=?))) -(provide (rename-out [app/tc #%app] [λ/tc λ])) +(require (except-in "sysf.rkt" #%app λ Int #%datum → Λ inst ∀ + #;type=?) + (prefix-in sysf: (only-in "sysf.rkt" #%app λ Int → ∀ #;type=?))) +(provide (rename-out [app/tc #%app] [λ/tc λ] [datum/tc #%datum])) (provide (except-out (all-from-out "sysf.rkt") sysf:Int sysf:→ sysf:∀ - (for-syntax sysf:type=? sysf:eval-τ))) -(provide Int → ∀ inst Λ - (for-syntax eval-τ type=?)) + sysf:#%app sysf:λ + #;(for-syntax sysf:type=?))) +(provide Int → ∀ inst Λ tyλ tyapp + #;(for-syntax type=?)) ;; System F_omega ;; Type relation: @@ -18,18 +19,26 @@ (begin-for-syntax ;; Extend to handle ∀ with type annotations - (define (eval-τ τ [tvs #'()]) + #;(define (eval-τ τ [tvs #'()] . rst) (syntax-parse τ [((~literal ∀) (b:typed-binding ...) t-body) - #`(∀ (b ...) #,((current-τ-eval) #'t-body (stx-append tvs #'(b.x ...))))] + #:with new-tvs #'(b.x ...) + #`(∀ (b ...) #,(apply (current-τ-eval) #'t-body (stx-append tvs #'new-tvs) rst))] ;; need to manually handle type constructors now since they are macros [((~literal →) t ...) - #`(→ #,@(stx-map (λ (ty) ((current-τ-eval) ty tvs)) #'(t ...)))] - [_ (sysf:eval-τ τ tvs)])) - (current-τ-eval eval-τ) + #`(→ #,@(stx-map (λ (ty) (apply (current-τ-eval) ty tvs rst)) #'(t ...)))] + #;[((~literal #%app) x ...) + #:when (printf "~a\n" (syntax->datum #'(x ...))) + #'(void)] + [((~literal tyapp) ((~literal tyλ) (b:typed-binding ...) τ_body) τ_arg ...) + #:with (τ_arg+ ...) (stx-map (λ (ty) (apply (current-τ-eval) ty tvs rst)) #'(τ_arg ...)) + #:with τ_body+ (apply (current-τ-eval) #'τ_body tvs rst) + (substs #'(τ_arg+ ...) #'(b.x ...) #'τ_body+)] + [_ (apply sysf:eval-τ τ tvs rst)])) + ;(current-τ-eval eval-τ) ;; extend to handle ∀ with kind annotations - (define (type=? τ1 τ2) + #;(define (type=? τ1 τ2) (syntax-parse (list τ1 τ2) #:literals (∀) [((∀ (a:typed-binding ...) t1:type) (∀ (b:typed-binding ...) t2:type)) #:when (= (stx-length #'(a ...)) (stx-length #'(b ...))) @@ -38,8 +47,8 @@ ((current-type=?) (substs #'(z ...) #'(a.x ...) #'t1) (substs #'(z ...) #'(b.x ...) #'t2))] [_ (sysf:type=? τ1 τ2)])) - (current-type=? type=?) - (current-typecheck-relation type=?)) + #;(current-type=? type=?) + #;(current-typecheck-relation type=?)) (define-base-type ★) (define-type-constructor ⇒) @@ -48,6 +57,11 @@ ;; TODO: move kind checking to a common place (like #%app)? ;; when expanding: check and erase kinds +;; TODO: need some kind of "promote" abstraction, +;; so I dont have to manually add kinds to all types +(define-base-type Str) +(provide String) +(define-syntax String (syntax-parser [x:id (⊢ #'Str #'★)])) (define-syntax Int (syntax-parser [x:id (⊢ #'sysf:Int #'★)])) ;; → in Fω is not first-class, can't pass it around @@ -57,15 +71,26 @@ #:with ([τ- k] ... [τ_res- k_res]) (infers+erase #'(τ ... τ_res)) #:when (typecheck? #'k_res #'★) #:when (same-types? #'(k ... k_res)) - (⊢ #'(sysf:→ τ- ... τ_res-) #'★)])) + (⊢ #'(sysf:→ τ- ... τ_res-) #'★)])) (define-syntax (∀ stx) (syntax-parse stx [(∀ (b:typed-binding ...) τ) #:with (tvs- τ- k) (infer/type-ctxt+erase #'(b ...) #'τ) #:when (typecheck? #'k #'★) - (⊢ #'(sysf:∀ tvs- τ-) #'★)])) - + (⊢ #'(#%plain-lambda tvs- b.τ ... τ-) #;#'(sysf:∀ tvs- τ-) #'★)])) + +#;(define-syntax (Λ stx) + (syntax-parse stx + [(_ (tv:id ...) e) + #:with (tvs- e- τ) (infer/tvs+erase #'e #'(tv ...)) + (⊢ #'e- #'(∀ tvs- τ))])) +#;(define-syntax (inst stx) + (syntax-parse stx + [(_ e τ:type ...) + #:with (e- ∀τ) (infer+erase #'e) + #:with ((~literal #%plain-lambda) (tv ...) τ_body) #'∀τ + (⊢ #'e- (substs #'(τ.norm ...) #'(tv ...) #'τ_body))])) (define-syntax (Λ stx) (syntax-parse stx [(_ (b:typed-binding ...) e) @@ -73,12 +98,28 @@ (⊢ #'e- #'(∀ ([tv- : b.τ] ...) τ))])) (define-syntax (inst stx) (syntax-parse stx - [(_ e τ ...) + [(_ e τ:type ...) #:with ([τ- k] ...) (infers+erase #'(τ ...)) - #:with (e- τ_e) (infer+erase #'e) - #:with ((~literal ∀) (b:typed-binding ...) τ_body) #'τ_e - #:when (typechecks? #'(k ...) #'(b.τ ...)) - (⊢ #'e- (substs #'(τ ...) #'(b.x ...) #'τ_body))])) + #:with (e- ∀τ) (infer+erase #'e) + #:with ((~literal #%plain-lambda) (tv ...) k_tv ... τ_body) #'∀τ + #:when (typechecks? #'(k ...) #'(k_tv ...)) + (⊢ #'e- (substs #'(τ.norm ...) #'(tv ...) #'τ_body))])) + +;; TODO: merge with regular λ and app? +(define-syntax (tyλ stx) + (syntax-parse stx + ; b = [tv : k] + [(_ (b:typed-binding ...) τ) + #:with ((tv- ...) τ- k) (infer/type-ctxt+erase #'(b ...) #'τ) + ; TODO: Racket lambda? + (⊢ #'(λ (tv- ...) τ-) #'(⇒ b.τ ... k))])) +(define-syntax (tyapp stx) + (syntax-parse stx + [(_ τ_fn τ_arg ...) + #:with [τ_fn- ((~literal ⇒) k ... k_res)] (infer+erase #'τ_fn) + #:with ([τ_arg- k_arg] ...) (infers+erase #'(τ_arg ...)) + #:when (typechecks? #'(k_arg ...) #'(k ...)) + (⊢ #'(#%app τ_fn- τ_arg- ...) #'k_res)])) ;; must override #%app and λ and primops to use new → ;; TODO: parameterize →? @@ -93,14 +134,15 @@ (⊢ #'(λ xs- e-) #'(→ b.τ ... τ_res))])) (define-syntax (app/tc stx) - (syntax-parse stx #:literals (→) + (syntax-parse stx [(_ e_fn e_arg ...) - #:with (e_fn- ((~literal →) τ ... τ_res)) (infer+erase #'e_fn) -; #:fail-unless (→? #'τ_fn) -; (format "Type error: Attempting to apply a non-function ~a with type ~a\n" -; (syntax->datum #'e_fn) (syntax->datum #'τ_fn)) -; #:with (→ τ ... τ_res) #'τ_fn - #:with ((e_arg- τ_arg) ...) (infers+erase #'(e_arg ...)) + #:with [e_fn- τ_fn] (infer+erase #'e_fn) + ;; this is sysf:→?, dont need prefix bc it's not defined here + #:fail-unless (→? #'τ_fn) + (format "Type error: Attempting to apply a non-function ~a with type ~a\n" + (syntax->datum #'e_fn) (syntax->datum #'τ_fn)) + #:with ((~literal #%plain-app) _ τ ... τ_res) #'τ_fn + #:with ([e_arg- τ_arg] ...) (infers+erase #'(e_arg ...)) #:fail-unless (typechecks? #'(τ_arg ...) #'(τ ...)) (string-append (format @@ -115,3 +157,11 @@ ", ")) (⊢ #'(#%app e_fn- e_arg- ...) #'τ_res)])) +;; must override #%datum to use new kinded-Int, etc +(define-syntax (datum/tc stx) + (syntax-parse stx + [(_ . n:integer) (⊢ (syntax/loc stx (#%datum . n)) #'Int)] + [(_ . s:str) (⊢ (syntax/loc stx (#%datum . s)) #'String)] + [(_ . x) + #:when (type-error #:src #'x #:msg "Unsupported literal: ~v" #'x) + #'(#%datum . x)])) +\ No newline at end of file diff --git a/tapl/id-expand-experiment.rkt b/tapl/id-expand-experiment.rkt @@ -0,0 +1,51 @@ +#lang racket +(require (for-syntax syntax/parse)) +#;(define-syntax (test stx) + (syntax-parse stx + [(_ x) + #:with (_ (x2) x3) (local-expand #'(λ (x) (test2 x)) 'expression null) + #:with (_ (x4) x5) (local-expand #'(λ (x) x) 'expression null) + #:when (printf "test1: ~a\n" (free-identifier=? #'x2 #'x3)) + #'(void)])) +#;(define-syntax (test2 stx) + (syntax-parse stx + [(_ x) + #:with xx (local-expand #'x 'expression null) + #:when (printf "test2: ~a\n" (bound-identifier=? #'x #'xx)) + #:with (lam (xxx) xxxx) (local-expand #'(λ (x) xx) 'expression null) + #:when (printf "test2 post: ~a\n" (bound-identifier=? #'xxx #'xxxx)) + (local-expand #'x 'expression null)])) +#;(test x) + +(define x 1) +(define-for-syntax (f y) + (printf "f: ~a\n" (bound-identifier=? y #'x))) +(define-syntax (test stx) + (syntax-parse stx + [(_ y) + #:when (printf "test: ~a\n" (bound-identifier=? #'x #'y)) + #:when (printf "test: ~a\n" (bound-identifier=? (syntax-local-introduce #'x) #'y)) + #:when (printf "test: ~a\n" (bound-identifier=? ((make-syntax-delta-introducer #'y #'x) #'x) #'y)) + #:when (printf "test: ~a\n" (bound-identifier=? ((make-syntax-delta-introducer #'x #'y) #'x) #'y)) + #:when (printf "test: ~a\n" (bound-identifier=? (syntax-local-introduce #'y) #'x)) ; negates mark? + #:when (printf "test: ~a\n" (bound-identifier=? ((make-syntax-delta-introducer #'y #'x) #'y) #'x)) + #:when (printf "test: ~a\n" (bound-identifier=? ((make-syntax-delta-introducer #'x #'y) #'y) #'x)) + #:when (printf "test: ~a\n" (bound-identifier=? (syntax-local-introduce (syntax-local-introduce #'y)) #'x)) ; double negation means no again + #:when (f #'x) + #:when (local-expand #'(test3 y) 'expression null) + #'(test2 y)])) +(define-syntax (test2 stx) + (syntax-parse stx + [(_ z) + #:when (printf "test2: ~a\n" (bound-identifier=? #'x #'z)) + #:when (printf "test2: ~a\n" (bound-identifier=? #'x (syntax-local-introduce #'z))) + #:when (printf "test2: ~a\n" (bound-identifier=? #'z (syntax-local-introduce #'x))) + #'(void)])) +(define-syntax (test3 stx) + (syntax-parse stx + [(_ z) + #:when (printf "test3: ~a\n" (bound-identifier=? #'x #'z)) + #:when (printf "test3: ~a\n" (bound-identifier=? #'x (syntax-local-introduce #'z))) + #:when (printf "test3: ~a\n" (bound-identifier=? #'z (syntax-local-introduce #'x))) + #'(void)])) +(test x) +\ No newline at end of file diff --git a/tapl/notes.txt b/tapl/notes.txt @@ -1,3 +1,98 @@ +2015-06-26 +Random thought: Can kinds be "erased"? +- first thought is no, since they appear to be needed for type equality? + +2015-06-26 +syntax marks problem: +- in the type created by \Lambda, eg \x.(-> x x), the binding x and body x's + were free-id= but not bound-id= because the body x's had extra marks +- this is because (the syntax representing) types are marked going "into" an + expansion, but do not receive the cancelling mark coming out of the + expansion since they are attached as a syntax-property +- thus, when this lam goes through another expansion (via \vdash), the binding x + and body x's become neither free-id nor bound-id=? +Solution: +- undo the mark (using syntax-local-introduce) before attaching a type + as a syntax property (ie in \vdash) +- need one more syntax-local-introduce in infer/tvs+erase +- i guess the rule is I need as many syntax-local-introduce's as extra + expanding lambdas that I add + + +2015-06-18 +Design Decision: +Types must be fully expanded, if we wish to use expansion to typecheck types +(i.e., kinding). +Problems: +- Type constructors can't be both macro and function + - solution: macro that expands to tmp function + - must provide predicate to check +- Recursive types + - solution: ??? +- Displaying error msgs + - need to keep track of surface stx (similar to 'origin prop?) + - solution: ??? +- repeated expansion of already expanded types + - solution: ??? +- what to do about tuples, where there may be a string in the fn position + - solution: add an extra "type constructor" in front of each pair + - todo: come up with a nicer way to handle "compound" types +- what is the expansion of forall? + +2015-06-16 +Problem: +use expansion for typechecking of terms: ok +- specifically, ok to go under lambda, since eval happens later +use expansion for typechecking of types: doesnt work now +- doesnt work because types currently are not fully-expandable + - eg a type constructor application (tycon x) is a macro that expands to + (#%app tycon x), where this expression will error if expanded further + since tycon cannot be used as an identifier + - if I define tycon as a regular function, so (tycon x) expands to + (#%app tycon x), then it wont catch non-app uses of tycon + - should be fine? - this is a parsing, not typechecking problem + - but then then parser need to be updated with every new type constructor + - this kind of monolithic architecture is what the paper is trying + to avoid + - alternative: define tycon as a macro where (tycon x) expands to + (#%app tycon_new x) + - drawback: can't use ~literal to match/destructure the tycon + - does this make things like type checking or type=? more difficult? + - will have to go through a separately-defined interface, + like a \tau? predicate +Subproblem: must completely change the representation of types? +- must use fully-expanded forms instead of surface syntax +- in particular, will be using *fully expanded identifiers* + - in this scenario, can I still match with ~literal? +Subproblem: where/when to expand +- types now must all be expanded since that is when kind checking occurs +use expansion for eval of types: not ok +- dont want to go under binders +- expand then eval? + - but then type constructors cant be macros? +Possible Solution +- I think eval must be separate +Subproblem: Do I need a new eval that is different from current-eval? +- conflict: + - current-eval must go under forall to check for unbound ids + - but eval should not go under forall for the case of tyapps +- maybe tyapp should go into type=?, as in tapl? +Subproblem: where to eval +- currently call eval for user type annotations, and in \vdash +Subproblem: Can type=? assume fully expanded (ie, evaled) types? + +2015-06-15 +New Thesis: The abstractions provided by macro systems are effective at +implementing type systems + +2015-06-08 +Problem: how to represent \rightarrow kind operator +1) If the kind operator is the same as the function type operator then how do I +attach a kind to a function arrow, since the arrow is still undefined at the +time. +2) If the kind arrow is a different arrow then #%app must be parameterized over +the arrow + 2015-05-28 Problem: how to represent \forall types 1) (Racket) functions diff --git a/tapl/stlc+box.rkt b/tapl/stlc+box.rkt @@ -13,7 +13,7 @@ ;; Terms: ;; - terms from stlc+cons.rkt -(define-type-constructor Ref) +(define-type-constructor Ref #:arity 1) (define-syntax (ref stx) (syntax-parse stx @@ -23,12 +23,14 @@ (define-syntax (deref stx) (syntax-parse stx [(_ e) - #:with (e- ((~literal Ref) τ)) (infer+erase #'e) + #:with (e- ref-τ) (infer+erase #'e) + #:with (τ) (Ref-args #'ref-τ) (⊢ #'(unbox e-) #'τ)])) (define-syntax (:= stx) (syntax-parse stx [(_ e_ref e) - #:with (e_ref- ((~literal Ref) τ1)) (infer+erase #'e_ref) + #:with (e_ref- ref-τ) (infer+erase #'e_ref) + #:with (τ1) (Ref-args #'ref-τ) #:with (e- τ2) (infer+erase #'e) #:when (typecheck? #'τ1 #'τ2) (⊢ #'(set-box! e_ref- e-) #'Unit)])) \ No newline at end of file diff --git a/tapl/stlc+cons.rkt b/tapl/stlc+cons.rkt @@ -27,7 +27,7 @@ (define-syntax x (make-rename-transformer (⊢ #'y #'τ))) (define y e-))])) -(define-type-constructor List) +(define-type-constructor List #:arity 1) (define-syntax (nil stx) (syntax-parse stx @@ -40,21 +40,25 @@ (syntax-parse stx [(_ e1 e2) #:with (e1- τ1) (infer+erase #'e1) - #:with (e2- ((~literal List) τ2)) (infer+erase #'e2) + #:with (e2- τ-lst) (infer+erase #'e2) + #:with (τ2) (List-args #'τ-lst) #:when (typecheck? #'τ1 #'τ2) (⊢ #'(cons e1- e2-) #'(List τ1))])) (define-syntax (isnil stx) (syntax-parse stx [(_ e) - #:with (e- ((~literal List) τ)) (infer+erase #'e) + #:with (e- τ-lst) (infer+erase #'e) + #:when (List? #'τ-lst) (⊢ #'(null? e-) #'Bool)])) (define-syntax (head stx) (syntax-parse stx [(_ e) - #:with (e- ((~literal List) τ)) (infer+erase #'e) + #:with (e- τ-lst) (infer+erase #'e) + #:with (τ) (List-args #'τ-lst) (⊢ #'(car e-) #'τ)])) (define-syntax (tail stx) (syntax-parse stx [(_ e) - #:with (e- ((~literal List) τ)) (infer+erase #'e) - (⊢ #'(cdr e-) #'(List τ))])) -\ No newline at end of file + #:with (e- τ-lst) (infer+erase #'e) + #:when (List? #'τ-lst) + (⊢ #'(cdr e-) #'τ-lst)])) +\ No newline at end of file diff --git a/tapl/stlc+rec+sub.rkt b/tapl/stlc+rec+sub.rkt @@ -1,7 +1,7 @@ #lang racket/base (require "typecheck.rkt") -;; want to use type=? and eval-τ from stlc+var.rkt, not stlc+sub.rkt -(require (except-in "stlc+sub.rkt" #%app #%datum sub? type=? eval-τ) +;; want to use type=? and eval-type from stlc+var.rkt, not stlc+sub.rkt +(require (except-in "stlc+sub.rkt" #%app #%datum sub? type=? type-eval) (prefix-in stlc: (only-in "stlc+sub.rkt" #%app #%datum sub?)) (except-in "stlc+var.rkt" #%app #%datum +) (prefix-in var: (only-in "stlc+var.rkt" #%datum))) @@ -28,8 +28,11 @@ (begin-for-syntax (define (sub? τ1 τ2) (or - (syntax-parse (list τ1 τ2) #:literals (× ∨) - [((× [k:str τk] ...) (× [l:str τl] ...)) + (syntax-parse (list τ1 τ2) #:literals (× ∨ quote) + [(tup1 tup2) + #:when (and (×? #'tup1) (×? #'tup2)) + #:with (['k:str τk] ...) (stx-map :-args (×-args #'tup1)) + #:with (['l:str τl] ...) (stx-map :-args (×-args #'tup2)) #:when (subset? (stx-map syntax-e (syntax->list #'(l ...))) (stx-map syntax-e (syntax->list #'(k ...)))) (stx-andmap @@ -38,7 +41,10 @@ #:with (k_match τk_match) (str-stx-assoc #'l #'([k τk] ...)) ((current-sub?) #'τk_match #'τl)]) #'([l τl] ...))] - [((∨ [k:str τk] ...) (∨ [l:str τl] ...)) + [(var1 var2) + #:when (and (∨? #'var1) (∨? #'var2)) + #:with (['k:str τk] ...) (stx-map :-args (∨-args #'var1)) + #:with (['l:str τl] ...) (stx-map :-args (∨-args #'var2)) #:when (subset? (stx-map syntax-e (syntax->list #'(l ...))) (stx-map syntax-e (syntax->list #'(k ...)))) (stx-andmap diff --git a/tapl/stlc+sub.rkt b/tapl/stlc+sub.rkt @@ -2,7 +2,7 @@ (require "typecheck.rkt") (require (except-in "stlc+lit.rkt" #%datum + #%app) (prefix-in stlc: (only-in "stlc+lit.rkt" #%app #%datum))) -(provide (rename-out #;[app/tc #%app] [stlc:#%app #%app] [datum/tc #%datum])) +(provide (rename-out [stlc:#%app #%app] [datum/tc #%datum])) (provide (except-out (all-from-out "stlc+lit.rkt") stlc:#%app stlc:#%datum)) (provide (for-syntax sub? subs? current-sub?)) @@ -38,14 +38,19 @@ (begin-for-syntax (define (sub? τ1 τ2) +; (printf "t1 = ~a\n" (syntax->datum τ1)) +; (printf "t2 = ~a\n" (syntax->datum τ2)) (or ((current-type=?) τ1 τ2) - (syntax-parse (list τ1 τ2) #:literals (→ Nat Int Num Top) + (syntax-parse (list τ1 τ2) #:literals (Nat Int Num Top) [(_ Top) #t] [(Nat τ) ((current-sub?) #'Int #'τ)] [(Int τ) ((current-sub?) #'Num #'τ)] [(τ Num) ((current-sub?) #'τ #'Int)] [(τ Int) ((current-sub?) #'τ #'Nat)] - [((→ s1 ... s2) (→ t1 ... t2)) + [(arr1 arr2) + #:when (and (→? #'arr1) (→? #'arr2)) + #:with (s1 ... s2) (→-args #'arr1) + #:with (t1 ... t2) (→-args #'arr2) (and (subs? #'(t1 ...) #'(s1 ...)) ((current-sub?) #'s2 #'t2))] [_ #f]))) diff --git a/tapl/stlc+tup.rkt b/tapl/stlc+tup.rkt @@ -26,6 +26,6 @@ [(_ tup n:integer) #:with (tup- τ_tup) (infer+erase #'tup) #:fail-unless (×? #'τ_tup) "not tuple type" - #:fail-unless (< (add1 (syntax->datum #'n)) (stx-length #'τ_tup)) "proj index too large" - (⊢ #'(list-ref tup n) (stx-list-ref #'τ_tup (add1 (syntax->datum #'n))))])) + #:fail-unless (< (syntax-e #'n) (×-num-args #'τ_tup)) "proj index too large" + (⊢ #'(list-ref tup n) (×-ref #'τ_tup (syntax-e #'n)))])) \ No newline at end of file diff --git a/tapl/stlc+var.rkt b/tapl/stlc+var.rkt @@ -1,14 +1,13 @@ #lang racket/base (require "typecheck.rkt") -(require (prefix-in stlc: (only-in "stlc+tup.rkt" #%app λ tup proj let type=? eval-τ)) - (except-in "stlc+tup.rkt" #%app λ tup proj let type=? eval-τ)) +(require (prefix-in stlc: (only-in "stlc+tup.rkt" #%app λ tup proj let type=?)) + (except-in "stlc+tup.rkt" #%app λ tup proj let type=?)) (provide (rename-out [stlc:#%app #%app] [stlc:λ λ] [stlc:let let])) (provide (except-out (all-from-out "stlc+tup.rkt") stlc:#%app stlc:λ stlc:let stlc:tup stlc:proj - (for-syntax stlc:type=? stlc:eval-τ))) -;(provide define-type-alias define-variant module quote submod) + (for-syntax stlc:type=?))) (provide tup proj var case) -(provide (for-syntax type=? eval-τ)) +(provide (for-syntax type=?)) ;; Simply-Typed Lambda Calculus, plus variants @@ -27,11 +26,11 @@ (begin-for-syntax ;; type expansion ;; extend to handle strings - (define (eval-τ τ . rst) + #;(define (eval-τ τ . rst) (syntax-parse τ [s:str τ] ; record field [_ (apply stlc:eval-τ τ rst)])) - (current-τ-eval eval-τ) + #;(current-τ-eval eval-τ) ; extend to: ; 1) first eval types, to accomodate aliases @@ -47,49 +46,49 @@ (provide define-type-alias) (define-syntax define-type-alias (syntax-parser - [(_ alias:id τ) + [(_ alias:id τ:type) ; must eval, otherwise undefined types will be allowed - #'(define-syntax alias (syntax-parser [x:id ((current-τ-eval) #'τ)]))])) + #'(define-syntax alias (syntax-parser [x:id #'τ.norm]))])) + +(define-type-constructor : #:arity 2) ;; records (define-syntax (tup stx) (syntax-parse stx #:datum-literals (=) [(_ [l:str = e] ...) #:with ((e- τ) ...) (infers+erase #'(e ...)) - (⊢ #'(list (list l e-) ...) #'(× [l τ] ...))] + (⊢ #'(list (list l e-) ...) #'(× [: l τ] ...))] [(_ e ...) #'(stlc:tup e ...)])) (define-syntax (proj stx) - (syntax-parse stx + (syntax-parse stx #:literals (quote) [(_ rec l:str) #:with (rec- τ_rec) (infer+erase #'rec) #:fail-unless (×? #'τ_rec) "not record type" - #:with (× [l_τ τ] ...) #'τ_rec - #:with (l_match τ_match) (str-stx-assoc #'l #'((l_τ τ) ...)) + #:with (['l_τ:str τ] ...) (stx-map :-args (×-args #'τ_rec)) + #:with (l_match:str τ_match) (str-stx-assoc #'l #'([l_τ τ] ...)) (⊢ #'(cadr (assoc l rec)) #'τ_match)] - [(_ e ...) - #'(stlc:proj e ...)])) + [(_ e ...) #'(stlc:proj e ...)])) (define-type-constructor ∨) (define-syntax (var stx) - (syntax-parse stx #:datum-literals (as =) - [(_ l:str = e as τ) - #:with τ+ ((current-τ-eval) #'τ) - #:when (∨? #'τ+) - #:with (∨ (l_τ τ_l) ...) #'τ+ - #:with (l_match τ_match) (str-stx-assoc #'l #'((l_τ τ_l) ...)) + (syntax-parse stx #:datum-literals (as =) #:literals (quote) + [(_ l:str = e as τ:type) + #:when (∨? #'τ.norm) + #:with (['l_τ:str τ_l] ...) (stx-map :-args (∨-args #'τ.norm)) + #:with (l_match:str τ_match) (str-stx-assoc #'l #'((l_τ τ_l) ...)) #:with (e- τ_e) (infer+erase #'e) - #:when (typecheck? #'τ_match #'τ_e) - (⊢ #'(list l e) #'τ+)])) + #:when (typecheck? #'τ_e #'τ_match) + (⊢ #'(list l e) #'τ.norm)])) (define-syntax (case stx) - (syntax-parse stx #:datum-literals (of =>) + (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) #:when (∨? #'τ_e) - #:with (∨ (l_x τ_x) ...) #'τ_e - #:fail-when (null? (syntax->list #'(l ...))) "no clauses" + #:with (['l_x:str τ_x] ...) (stx-map :-args (∨-args #'τ_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) ...) diff --git a/tapl/stlc.rkt b/tapl/stlc.rkt @@ -1,7 +1,7 @@ #lang racket/base (require "typecheck.rkt") (provide (rename-out [λ/tc λ] [app/tc #%app])) -(provide (for-syntax type=? types=? same-types? current-type=? eval-τ)) +(provide (for-syntax type=? types=? same-types? current-type=? type-eval)) (provide #%module-begin #%top-interaction #%top require) ; from racket ;; Simply-Typed Lambda Calculus @@ -16,35 +16,35 @@ ;; type expansion ;; - must structurally recur to check nested identifiers ;; - rst allows adding extra args later - (define (eval-τ τ . rst) - ; app is #%app in τ's ctxt, not here (which is racket's #%app) - (define app (datum->syntax τ '#%app)) - ;; stop right before expanding: - ;; - #%app, this should mean tycon via define-type-constructor - ;; - app, other compound types, like variants - ;; - ow, the type checking in #%app will fail - ;; (could leave this case out until adding variants but it's general - ;; enough, so leave it here) - ;; could match specific type constructors like → before expanding - ;; but this is more general and wont require subsequent extensions for - ;; every defined type constructor - (syntax-parse (local-expand τ 'expression (list app #'#%app)) - ; full expansion checks for undefined types - [x:id (local-expand #'x 'expression null)] - [((~literal #%app) tcon t ...) - #`(tcon #,@(stx-map (λ (ty) (apply (current-τ-eval) ty rst)) #'(t ...)))] - ; else just structurually eval - [(t ...) (stx-map (λ (ty) (apply (current-τ-eval) ty rst)) #'(t ...))])) - (current-τ-eval eval-τ)) + (define (type-eval τ) (expand/df τ)) +; #;(define (eval-τ τ . rst) +; ; app is #%app in τ's ctxt, not here (which is racket's #%app) +; (define app (datum->syntax τ '#%app)) +; ;; stop right before expanding: +; ;; - #%app, this should mean tycon via define-type-constructor +; ;; - app, other compound types, like variants +; ;; - ow, the type checking in #%app will fail +; ;; (could leave this case out until adding variants but it's general +; ;; enough, so leave it here) +; ;; could match specific type constructors like → before expanding +; ;; but this is more general and wont require subsequent extensions for +; ;; every defined type constructor +; (syntax-parse (local-expand τ 'expression (list app #'#%app)) +; ; full expansion checks for undefined types +; [x:id (local-expand #'x 'expression null)] +; [((~literal #%app) tcon t ...) +; #`(tcon #,@(stx-map (λ (ty) (apply (current-τ-eval) ty rst)) #'(t ...)))] +; ; else just structurually eval +; [(t ...) (stx-map (λ (ty) (apply (current-τ-eval) ty rst)) #'(t ...))])) + (current-type-eval type-eval)) (begin-for-syntax ;; type=? : Type Type -> Boolean ;; Indicates whether two types are equal - ;; type equality = structurally recursive identifier equality - ;; structurally checks for free-identifier=? + ;; type equality = structurally free-identifier=? (define (type=? τ1 τ2) -; (printf "t1 = ~a\n" (syntax->datum τ1)) -; (printf "t2 = ~a\n" (syntax->datum τ2)) +; (printf "(τ=) t1 = ~a\n" τ1 #;(syntax->datum τ1)) +; (printf "(τ=) t2 = ~a\n" τ2 #;(syntax->datum τ2)) (syntax-parse (list τ1 τ2) [(x:id y:id) (free-identifier=? τ1 τ2)] [((τa ...) (τb ...)) (types=? #'(τa ...) #'(τb ...))] @@ -67,6 +67,10 @@ (syntax-parse stx [(_ (b:typed-binding ...) e) #:with (xs- e- τ_res) (infer/type-ctxt+erase #'(b ...) #'e) +; #:with (a as ...) #'(b.τ ...) +; #:when (printf "lam: ~a\n" (free-identifier=? #'a #'τ_res)) +; #:with (lam (aa) resres) (local-expand #'(λ (a) τ_res) 'expression null) +; #:when (printf "lam2: ~a\n" (free-identifier=? #'aa #'resres)) (⊢ #'(λ xs- e-) #'(→ b.τ ... τ_res))])) (define-syntax (app/tc stx) @@ -76,7 +80,7 @@ #:fail-unless (→? #'τ_fn) (format "Type error: Attempting to apply a non-function ~a with type ~a\n" (syntax->datum #'e_fn) (syntax->datum #'τ_fn)) - #:with (_ τ ... τ_res) #'τ_fn + #:with ((~literal #%plain-app) _ τ ... τ_res) #'τ_fn #:with ((e_arg- τ_arg) ...) (infers+erase #'(e_arg ...)) #:fail-unless (typechecks? #'(τ_arg ...) #'(τ ...)) (string-append diff --git a/tapl/sysf.rkt b/tapl/sysf.rkt @@ -1,12 +1,12 @@ #lang racket/base (require "typecheck.rkt") -(require (except-in "stlc+lit.rkt" #%app λ type=? eval-τ) - (prefix-in stlc: (only-in "stlc+lit.rkt" #%app λ type=? eval-τ))) +(require (except-in "stlc+lit.rkt" #%app λ type=?) + (prefix-in stlc: (only-in "stlc+lit.rkt" #%app λ type=?))) (provide (rename-out [stlc:#%app #%app] [stlc:λ λ])) (provide (except-out (all-from-out "stlc+lit.rkt") stlc:#%app - (for-syntax stlc:type=? stlc:eval-τ))) + (for-syntax stlc:type=?))) (provide Λ inst) -(provide (for-syntax type=? eval-τ)) +(provide (for-syntax type=?)) ;; System F @@ -19,25 +19,39 @@ ;; - terms from stlc+lit.rkt ;; - Λ and inst -(define-type-constructor ∀) +;; dont use define-type-constructor, instead define explicit macro +(provide ∀) +(define-syntax (∀ stx) + (syntax-parse stx + [(_ (x:id ...) body) ; cannot use :type annotation on body bc of unbound vars + ;; use #%plain-lambda to avoid insertion of #%expression + (syntax/loc stx (#%plain-lambda (x ...) body))])) (begin-for-syntax ;; Extend to handle ∀, skip typevars - (define (eval-τ τ [tvs #'()] . rst) - (syntax-parse τ - [x:id #:when (stx-member τ tvs) τ] - [((~literal ∀) ts t-body) - #`(∀ ts #,(apply (current-τ-eval) #'t-body (stx-append tvs #'ts) rst))] - ;; need to duplicate stlc:eval-τ here to pass extra params - [_ (apply stlc:eval-τ τ tvs rst)])) - (current-τ-eval eval-τ) +; #;(define (type-eval τ [tvs #'()] . rst) +; (syntax-parse τ +; [x:id #:when (stx-member τ tvs) τ] +; [((~literal ∀) ts t-body) +; #`(∀ ts #,(apply (current-τ-eval) #'t-body (stx-append tvs #'ts) rst))] +; ;; need to duplicate stlc:eval-τ here to pass extra params +; [_ (apply stlc:eval-τ τ tvs rst)])) +; #;(current-type-eval type-eval) ;; extend to handle ∀ (define (type=? τ1 τ2) - (syntax-parse (list τ1 τ2) #:literals (∀) - [((∀ (x:id ...) t1) (∀ (y:id ...) t2)) + (syntax-parse (list τ1 τ2) + [(((~literal #%plain-lambda) (x:id ...) k1 ... t1) + ((~literal #%plain-lambda) (y:id ...) k2 ... t2)) #:when (= (stx-length #'(x ...)) (stx-length #'(y ...))) #:with (z ...) (generate-temporaries #'(x ...)) + ;#:when (typechecks? #'(k1 ...) #'(k2 ...)) +; #:when (printf "t1 = ~a\n" (syntax->datum #'t1)) +; #:when (printf "t2 = ~a\n" (syntax->datum #'t2)) +; #:when (printf "~a\n" +; (map syntax->datum + ; (list (substs #'(z ...) #'(x ...) #'t1) +; (substs #'(z ...) #'(y ...) #'t2)))) ((current-type=?) (substs #'(z ...) #'(x ...) #'t1) (substs #'(z ...) #'(y ...) #'t2))] [_ (stlc:type=? τ1 τ2)])) @@ -51,8 +65,7 @@ (⊢ #'e- #'(∀ tvs- τ))])) (define-syntax (inst stx) (syntax-parse stx - [(_ e τ ...) - #:with (e- τ_e) (infer+erase #'e) - #:with ((~literal ∀) (tv ...) τ_body) #'τ_e - #:with (τ+ ...) (stx-map (current-τ-eval) #'(τ ...)) - (⊢ #'e- (substs #'(τ+ ...) #'(tv ...) #'τ_body))])) -\ No newline at end of file + [(_ e τ:type ...) + #:with (e- ∀τ) (infer+erase #'e) + #:with ((~literal #%plain-lambda) (tv ...) τ_body) #'∀τ + (⊢ #'e- (substs #'(τ.norm ...) #'(tv ...) #'τ_body))])) +\ No newline at end of file diff --git a/tapl/tests/fomega-tests.rkt b/tapl/tests/fomega-tests.rkt @@ -2,20 +2,53 @@ (require "rackunit-typechecking.rkt") (check-type Int : ★) +(check-type String : ★) +(typecheck-fail →) (check-type (→ Int Int) : ★) (typecheck-fail (→ →)) +(check-type 1 : Int) + +(check-type (∀ ([t : ★]) (→ t t)) : ★) +(check-type (→ (∀ ([t : ★]) (→ t t)) (→ Int Int)) : ★) + +(check-type ((λ ([x : (∀ ([X : ★]) (→ X X))]) x) (Λ ([X : ★]) (λ ([x : X]) x))) + : (∀ ([X : ★]) (→ X X ))) +(typecheck-fail ((λ ([x : (∀ ([X : ★]) (→ X X))]) x) (Λ ([X : (⇒ ★ ★)]) (λ ([x : X]) x)))) + +;(check-type (λ ([x : (∀ ([t : ★]) t)]) x) : (→ (∀ ([t : ★]) t) (∀ ([t : ★]) t))) +;(check-type (λ ([x : (∀ ([t : (⇒ ★ ★)]) (tyapp t Int))]) x) : Int #;(→ (∀ ([t : (⇒ ★ ★)]) t) (∀ ([t : (⇒ ★ ★)]) t))) -(typecheck-fail (λ ([x : (→ →)]) x)) +;(check-type 1 : (tyapp (tyλ ([X : ★]) X) Int)) +;(check-not-type 1 : (tyapp (tyλ ([X : ★]) X) String)) +; +;;(check-type (λ ([x : Int]) x) : (tyapp (tyλ ([X : ★]) (→ X X)) Int)) +; +;; Mono Pair +;#;(tyapp (tyλ ([Pair : (⇒ ★ ★)]) (λ ([p : (tyapp Pair Int)]) p)) +; (tyλ ([X : ★]) (∀ ([P : ★]) (→ (→ X X P) P)))) +; +; +;;(check-type Int : ★) +;;(check-type (→ Int Int) : ★) +;;(typecheck-fail (→ →)) +; +;(typecheck-fail (λ ([x : (→ →)]) x)) +; +;(typecheck-fail (λ ([x : (∀ (t) t)]) x)) ; missing kind annotation +;(check-type (∀ ([t : ★]) t) : ★) +;(check-type (∀ ([→ : ★]) →) : ★) ; should be ok +;(check-type (∀ ([t : ★]) (∀ ([s : ★]) (→ t s))) : ★) +;(check-type (∀ ([t : ★] [s : ★]) (→ t s)) : ★) +;;(check-type (∀ ([t : (⇒ ★ ★)] [s : ★]) (t s)) : ★) ; doesnt work yet +;(typecheck-fail (∀ (t) (→ →))) +; +;;; sysf tests wont work, unless augmented with kinds +(check-type (Λ ([X : ★]) (λ ([x : X]) x)) : (∀ ([X : ★]) (→ X X))) -(typecheck-fail (∀ (t) t)) ; missing kind annotation -(check-type (∀ ([t : ★]) t) : ★) -(check-type (∀ ([→ : ★]) →) : ★) ; should be ok -(check-type (∀ ([t : ★]) (∀ ([s : ★]) (→ t s))) : ★) -(check-type (∀ ([t : ★] [s : ★]) (→ t s)) : ★) -;(check-type (∀ ([t : (⇒ ★ ★)] [s : ★]) (t s)) : ★) ; doesnt work yet -(typecheck-fail (∀ (t) (→ →))) +(check-type (Λ ([X : ★]) (λ ([t : X] [f : X]) t)) : (∀ ([X : ★]) (→ X X X))) ; true +(check-type (Λ ([X : ★]) (λ ([t : X] [f : X]) f)) : (∀ ([X : ★]) (→ X X X))) ; false +(check-type (Λ ([X : ★]) (λ ([t : X] [f : X]) f)) : (∀ ([Y : ★]) (→ Y Y Y))) ; false, alpha equiv -;; sysf tests wont work, must be augmented with kinds (check-type (Λ ([t1 : ★]) (Λ ([t2 : ★]) (λ ([x : t1]) (λ ([y : t2]) y)))) : (∀ ([t1 : ★]) (∀ ([t2 : ★]) (→ t1 (→ t2 t2))))) @@ -58,7 +91,6 @@ ;; previous tests ------------------------------------------------------------- (check-type 1 : Int) (check-not-type 1 : (→ Int Int)) -(typecheck-fail "one") ; unsupported literal (typecheck-fail #f) ; unsupported literal (check-type (λ ([x : Int] [y : Int]) x) : (→ Int Int Int)) (check-not-type (λ ([x : Int]) x) : Int) diff --git a/tapl/tests/rackunit-typechecking.rkt b/tapl/tests/rackunit-typechecking.rkt @@ -5,10 +5,9 @@ (define-syntax (check-type stx) (syntax-parse stx #:datum-literals (:) [(_ e : τ ⇒ v) #'(check-type-and-result e : τ ⇒ v)] - [(_ e : τ-expected) + [(_ e : τ-expected:type) #:with τ (typeof (expand/df #'e)) - #:with τ-expected+ ((current-τ-eval) #'τ-expected) - #:fail-unless (typecheck? #'τ #'τ-expected+) + #:fail-unless (typecheck? #'τ #'τ-expected.norm) (format "Expression ~a [loc ~a:~a] has type ~a, expected ~a" (syntax->datum #'e) (syntax-line #'e) (syntax-column #'e) @@ -17,10 +16,9 @@ (define-syntax (check-not-type stx) (syntax-parse stx #:datum-literals (:) - [(_ e : not-τ) + [(_ e : not-τ:type) #:with τ (typeof (expand/df #'e)) - #:with not-τ+ ((current-τ-eval) #'not-τ) - #:fail-when (typecheck? #'τ #'not-τ) + #:fail-when (typecheck? #'τ #'not-τ.norm) (format "(~a:~a) Expression ~a should not have type ~a" (syntax-line stx) (syntax-column stx) diff --git a/tapl/tests/run-all-tests.rkt b/tapl/tests/run-all-tests.rkt @@ -10,4 +10,5 @@ (require "stlc+sub-tests.rkt") (require "stlc+rec+sub-tests.rkt") -(require "sysf-tests.rkt") -\ No newline at end of file +;(require "sysf-tests.rkt") +;(require "fomega-tests.rkt") +\ No newline at end of file diff --git a/tapl/tests/stlc+box-tests.rkt b/tapl/tests/stlc+box-tests.rkt @@ -13,6 +13,9 @@ (check-type ((λ ([b : (Ref Int)]) (deref b)) (ref 10)) : Int ⇒ 10) (check-type ((λ ([b : (Ref Int)]) (begin (begin (:= b 20) (deref b)))) (ref 10)) : Int ⇒ 20) +(typecheck-fail (λ ([lst : (Ref Int Int)]) lst)) ; type constructor wrong arity +(typecheck-fail (λ ([lst : (Ref)]) lst)) ; type constructor wrong arity + ;; previous tests: ------------------------------------------------------------ (typecheck-fail (cons 1 2)) (typecheck-fail (cons 1 nil)) @@ -45,10 +48,9 @@ (check-type + : ArithBinOp) (check-type (λ ([f : ArithBinOp]) (f 1 2)) : (→ (→ Int Int Int) Int)) -;; 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") @@ -58,47 +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]) diff --git a/tapl/tests/stlc+cons-tests.rkt b/tapl/tests/stlc+cons-tests.rkt @@ -7,6 +7,8 @@ (typecheck-fail nil) (typecheck-fail (nil Int)) (typecheck-fail (nil (Int))) +(typecheck-fail (λ ([lst : (List Int Int)]) lst)) ; type constructor wrong arity +(typecheck-fail (λ ([lst : (List)]) lst)) ; type constructor wrong arity ; passes bc ⇒-rhs is only used for its runtime value (check-type (nil {Int}) : (List Int) ⇒ (nil {Bool})) (check-not-type (nil {Bool}) : (List Int)) @@ -35,7 +37,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") @@ -45,47 +47,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]) diff --git a/tapl/tests/stlc+rec+sub-tests.rkt b/tapl/tests/stlc+rec+sub-tests.rkt @@ -3,27 +3,29 @@ ;; record subtyping tests (check-type "coffee" : String) -(check-type (tup ["coffee" = 3]) : (× ["coffee" Int])) ; element subtyping -(check-type (tup ["coffee" = 3]) : (× ["coffee" Nat])) -(check-type (tup ["coffee" = 3]) : (× ["coffee" Top])) -(check-type (tup ["coffee" = 3]) : (× ["coffee" Num])) -(check-not-type (tup ["coffee" = -3]) : (× ["coffee" Nat])) -(check-type (tup ["coffee" = -3]) : (× ["coffee" Num])) -(check-type (tup ["coffee" = -3] ["tea" = 3]) : (× ["coffee" Int])) ; width subtyping -(check-type (tup ["coffee" = -3] ["tea" = 3]) : (× ["coffee" Num])) ; width+element subtyping +(check-type (tup ["coffee" = 3]) : (× [: "coffee" Int])) ; element subtyping +(check-type (var "coffee" = 3 as (∨ [: "coffee" Nat])) : (∨ [: "coffee" Int])) ; element subtyping +(check-type (tup ["coffee" = 3]) : (× [: "coffee" Nat])) +(check-type (tup ["coffee" = 3]) : (× [: "coffee" Top])) +(check-type (var "coffee" = 3 as (∨ [: "coffee" Int])) : (∨ [: "coffee" Top])) ; element subtyping (twice) +(check-type (tup ["coffee" = 3]) : (× [: "coffee" Num])) +(check-not-type (tup ["coffee" = -3]) : (× [: "coffee" Nat])) +(check-type (tup ["coffee" = -3]) : (× [: "coffee" Num])) +(check-type (tup ["coffee" = -3] ["tea" = 3]) : (× [: "coffee" Int])) ; width subtyping +(check-type (tup ["coffee" = -3] ["tea" = 3]) : (× [: "coffee" Num])) ; width+element subtyping ;; record + fns -(check-type (tup ["plus" = +]) : (× ["plus" (→ Num Num Num)])) +(check-type (tup ["plus" = +]) : (× [: "plus" (→ Num Num Num)])) (check-type + : (→ Num Num Num)) -(check-type (tup ["plus" = +]) : (× ["plus" (→ Int Num Num)])) -(check-type (tup ["plus" = +]) : (× ["plus" (→ Int Num Top)])) -(check-type (tup ["plus" = +] ["mul" = *]) : (× ["plus" (→ Int Num Top)])) +(check-type (tup ["plus" = +]) : (× [: "plus" (→ Int Num Num)])) +(check-type (tup ["plus" = +]) : (× [: "plus" (→ Int Num Top)])) +(check-type (tup ["plus" = +] ["mul" = *]) : (× [: "plus" (→ Int Num Top)])) ;; previous record tests ;; 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") @@ -33,11 +35,11 @@ (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])) ;; previous basic subtyping tests ------------------------------------------------------ diff --git a/tapl/tests/stlc+var-tests.rkt b/tapl/tests/stlc+var-tests.rkt @@ -15,7 +15,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") @@ -25,47 +25,47 @@ (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] ["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]) diff --git a/tapl/tests/sysf-tests.rkt b/tapl/tests/sysf-tests.rkt @@ -1,6 +1,12 @@ #lang s-exp "../sysf.rkt" (require "rackunit-typechecking.rkt") +(check-type (Λ (X) (λ ([x : X]) x)) : (∀ (X) (→ X X))) + +(check-type (Λ (X) (λ ([t : X] [f : X]) t)) : (∀ (X) (→ X X X))) ; true +(check-type (Λ (X) (λ ([t : X] [f : X]) f)) : (∀ (X) (→ X X X))) ; false +(check-type (Λ (X) (λ ([t : X] [f : X]) f)) : (∀ (Y) (→ Y Y Y))) ; false, alpha equiv + (check-type (Λ (t1) (Λ (t2) (λ ([x : t1]) (λ ([y : t2]) y)))) : (∀ (t1) (∀ (t2) (→ t1 (→ t2 t2))))) @@ -17,7 +23,7 @@ ; second inst is discarded (check-type (inst (inst (Λ (t1) (Λ (t2) (λ ([x : t1]) x))) Int) (→ Int Int)) : (→ Int Int)) -;; polymorphic arguments +;;; polymorphic arguments (check-type (Λ (t) (λ ([x : t]) x)) : (∀ (t) (→ t t))) (check-type (Λ (t) (λ ([x : t]) x)) : (∀ (s) (→ s s))) (check-type (Λ (s) (Λ (t) (λ ([x : t]) x))) : (∀ (s) (∀ (t) (→ t t)))) @@ -40,7 +46,7 @@ : Int ⇒ 10) -;; previous tests ------------------------------------------------------------- +;;; previous tests ------------------------------------------------------------- (check-type 1 : Int) (check-not-type 1 : (→ Int Int)) (typecheck-fail "one") ; unsupported literal diff --git a/tapl/typecheck.rkt b/tapl/typecheck.rkt @@ -31,34 +31,76 @@ (define τ (void)) (define-for-syntax (τ? τ1) (typecheck? τ1 #'τ)))])) +(struct exn:fail:type:runtime exn:fail:user ()) + ;; 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) + [(_ τ:id (~optional (~seq #:arity n:exact-positive-integer))) #:with τ? (format-id #'τ "~a?" #'τ) - #'(begin - (provide τ (for-syntax τ?)) + #:with τ-ref (format-id #'τ "~a-ref" #'τ) + #:with τ-num-args (format-id #'τ "~a-num-args" #'τ) + #:with τ-args (format-id #'τ "~a-args" #'τ) + #: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 in non-application position")] - [(_) (type-error #:src stx - #:msg "Type constructor must have at least one argument.")] + #: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 (... ...)) #'(#%app τ x (... ...))])) + [(_ x (... ...)) #'(tmp x (... ...))])) + ; TODO: ok to assume type in canonical (ie, fully expanded) form? + ;; yes for now (define-for-syntax (τ? stx) - (syntax-parse ((current-τ-eval) stx) - [(τcons τ_arg (... ...)) (typecheck? #'τcons #'τ)] - [_ #f])))])) + (syntax-parse 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 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 (define-syntax-class type - (pattern τ:expr)) + ;; stx = surface syntax, as written + ;; norm = canonical form for the type + (pattern stx:expr + #:with norm ((current-type-eval) #'stx) + #:with τ #'norm)) ; backwards compat +; #:with τ #'stx)) ; backwards compat (define-syntax-class typed-binding #:datum-literals (:) - (pattern [x:id : τ:type]) + (pattern [x:id : stx:type] #:with τ #'stx.τ) (pattern (~and any (~not [x:id : τ:type])) #:with x #f #:with τ #f @@ -78,11 +120,13 @@ ;; ⊢ : Syntax Type -> Syntax ;; Attaches type τ to (expanded) expression e. ;; must eval here, to catch unbound types - (define (⊢ e τ) (syntax-property e 'type ((current-τ-eval) τ))) + (define (⊢ e τ) + (syntax-property e 'type (syntax-local-introduce ((current-type-eval) τ)))) + ;; typeof : Syntax -> Type or #f ;; Retrieves type of given stx, or #f if input has not been assigned a type. (define (typeof stx) (syntax-property stx 'type)) - + ;; infers type and erases types in a single expression, ;; in the context of the given bindings and their types (define (infer/type-ctxt+erase x+τs e) @@ -105,27 +149,9 @@ #'(λ (x ...) (let-syntax ([x (make-rename-transformer (⊢ #'x #'τ))] ...) (#%expression e) ...))) - (list #'xs+ #'(e+ ...) (stx-map typeof #'(e+ ...)))] + (list #'xs+ #'(e+ ...) (stx-map syntax-local-introduce (stx-map typeof #'(e+ ...))))] [([x τ] ...) (infers/type-ctxt+erase #'([x : τ] ...) es)])) - #;(define (eval-τ τ [tvs #'()]) - (syntax-parse τ - [x:id #:when (stx-member τ tvs) τ] - [s:str τ] ; record field - [((~and (~datum ∀) forall) ts τ) #`(forall ts #,(eval-τ #'τ #'ts))] - [_ - (define app (datum->syntax τ '#%app)) ; #%app in τ's ctxt - ;; stop right before expanding #%app - (define maybe-app-τ (local-expand τ 'expression (list app))) - ;; manually remove app and recursively expand - (if (identifier? maybe-app-τ) ; base type - ;; full expansion checks that type is a bound name - (local-expand maybe-app-τ 'expression null) - (syntax-parse maybe-app-τ - [(τ1 ...) - #:with (τ-exp ...) (stx-map (λ (t) (eval-τ t tvs)) #'(τ1 ...)) - #'(τ-exp ...)]))])) - ;; infers the type and erases types in an expression (define (infer+erase e) (define e+ (expand/df e)) @@ -136,15 +162,17 @@ ;; infers and erases types in an expression, in the context of given type vars (define (infer/tvs+erase e [tvs #'()]) (syntax-parse (expand/df #`(λ #,tvs (#%expression #,e))) #:literals (#%expression) - [(lam tvs+ (#%expression e+)) (list #'tvs+ #'e+ (typeof #'e+))])) + [(lam tvs+ (#%expression e+)) + (list #'tvs+ #'e+ (syntax-local-introduce (typeof #'e+)))])) (define current-typecheck-relation (make-parameter #f)) (define (typecheck? t1 t2) ((current-typecheck-relation) t1 t2)) (define (typechecks? τs1 τs2) (stx-andmap (current-typecheck-relation) τs1 τs2)) - (define current-τ-eval (make-parameter #f)) + (define current-type-eval (make-parameter #f)) + (require inspect-syntax) ;; term expansion ;; expand/df : Syntax -> Syntax ;; Local expands the given syntax object. @@ -156,16 +184,20 @@ (define (expand/df e) (local-expand e 'expression null)) - ;; type-error #:src Syntax #:msg String Syntax ... + (struct exn:fail:type:check exn:fail:user ()) + +;; type-error #:src Syntax #:msg String Syntax ... ;; usage: ;; type-error #:src src-stx ;; #:msg msg-string msg-args ... (define-syntax-rule (type-error #:src stx-src #:msg msg args ...) - (raise-user-error - 'TYPE-ERROR - (format (string-append "~a (~a:~a): " msg) - (syntax-source stx-src) (syntax-line stx-src) (syntax-column stx-src) - (syntax->datum args) ...)))) + (raise + (exn:fail:type:check + (format (string-append "TYPE-ERROR: ~a (~a:~a): " msg) + (syntax-source stx-src) (syntax-line stx-src) (syntax-column stx-src) + (syntax->datum args) ...) + (current-continuation-marks))))) + (define-syntax (define-primop stx) (syntax-parse stx #:datum-literals (:) @@ -186,11 +218,15 @@ (define-for-syntax (subst τ x e) (syntax-parse e - [y:id #:when (free-identifier=? e x) τ] + [y:id +; #:when (printf "~a = ~a? = ~a\n" #'y x (free-identifier=? e x)) +; #:when (printf "~a = ~a? = ~a\n" #'y x (bound-identifier=? e x)) + #:when (bound-identifier=? e x) + τ] [y:id #'y] [(esub ...) #:with (esub_subst ...) (stx-map (λ (e1) (subst τ x e1)) #'(esub ...)) - #'(esub_subst ...)])) + (syntax-track-origin #'(esub_subst ...) e x)])) (define-for-syntax (substs τs xs e) (stx-fold subst e τs xs))