Make tc-lambda-unit have one centralized definition of how to extend the environment.

This commit is contained in:
Eric Dobson 2014-06-22 17:16:17 -07:00
parent 3c179e0217
commit b6e96f0bf0

View File

@ -3,8 +3,9 @@
(require "../utils/utils.rkt"
racket/dict racket/list syntax/parse racket/syntax syntax/stx
racket/match syntax/id-table racket/set
unstable/sequence
(contract-req)
(rep type-rep object-rep)
(rep type-rep object-rep rep-utils)
(rename-in (types abbrev utils union)
[-> t:->]
[->* t:->*]
@ -13,26 +14,14 @@
(types type-table)
(typecheck signatures tc-metafunctions tc-subst)
(env lexical-env tvar-env index-env scoped-tvar-env)
(utils tc-utils))
(utils tc-utils)
(for-syntax
syntax/parse
racket/base))
(import tc-expr^)
(export tc-lambda^)
(define-struct/cond-contract lam-result ([args (listof (list/c identifier? Type/c))]
[rest (or/c #f (list/c identifier? Type/c))]
[drest (or/c #f (list/c identifier? (cons/c Type/c symbol?)))]
[body tc-results/c])
#:transparent)
(define (lam-result->type lr)
(match lr
[(struct lam-result ((list (list arg-ids arg-tys) ...) rest drest body))
(make-arr*
arg-tys
(abstract-results body arg-ids
#:rest-id (or (and rest (first rest))) (and drest (first drest)))
#:rest (and rest (second rest))
#:drest (and drest (second drest)))]))
(define-syntax-class cl-rhs
#:literal-sets (kernel-literals)
@ -64,13 +53,57 @@
(if (= arg-len 1) "" "s")
(if rest " and a rest arg" "")))
;; listof[id] option[id] block listof[type] option[type] option[(cons type var)] tc-result -> lam-result
(define/cond-contract (check-clause arg-list rest body arg-tys rest-ty drest ret-ty)
;; tc-lambda-body: Typechecks the body with the given args and names and returns the resulting arr?.
;; arg-names: The identifiers of the positional args
;; arg-types: The types of the positional args
;; raw-rest: Either #f for no rest argument or (list rest-id rest-type) where rest-id is the
;; identifier of the rest arg, and rest-type is the type.
;; expected: The expected type of the body forms.
;; body: The body of the lambda to typecheck.
(define/cond-contract
(tc-lambda-body arg-names arg-types #:rest [raw-rest #f] #:expected [expected #f] body)
(->* ((listof identifier?) (listof Type/c) syntax?)
(#:rest (or/c #f (list/c identifier? (or/c Type/c (cons/c Type/c symbol?))))
#:expected (or/c #f tc-results/c))
arr?)
(define-values (rest-id rest)
(if raw-rest
(values (first raw-rest) (second raw-rest))
(values #f #f)))
(define rest-types
(cond
[(not rest) (list)]
[(cons? rest) (list (make-ListDots (car rest) (cdr rest)))]
[else (list (-lst rest))]))
(define rest-names
(if rest-id (list rest-id) null))
(make-arr*
arg-types
(abstract-results
(with-lexical-env/extend
(append rest-names arg-names)
(append rest-types arg-types)
(tc-body/check body expected))
arg-names #:rest-id rest-id)
#:rest (and (Type? rest) rest)
#:drest (and (cons? rest) rest)))
;; check-clause: Checks that a lambda clause has arguments and body matching the expected type
;; arg-list: The identifiers of the positional args in the lambda form
;; rest-id: The identifier of the rest arg, or #f for no rest arg
;; body: The body of the lambda to typecheck.
;; arg-tys: The expected positional argument types.
;; rest-ty: The expected base type of the rest arg (not poly dotted case)
;; drest: The expected base type of the rest arg and its bound (poly dotted case)
;; ret-ty: The expected type of the body of the lambda.
(define/cond-contract (check-clause arg-list rest-id body arg-tys rest-ty drest ret-ty)
((listof identifier?)
(or/c #f identifier?) syntax? (listof Type/c) (or/c #f Type/c)
(or/c #f (cons/c Type/c symbol?)) tc-results/c
. -> .
lam-result?)
arr?)
(let* ([arg-len (length arg-list)]
[tys-len (length arg-tys)]
[arg-types (if (andmap type-annotation arg-list)
@ -81,63 +114,48 @@
[(> arg-len tys-len) (append arg-tys
(map (lambda _ (or rest-ty (Un)))
(drop arg-list tys-len)))]))])
(define (check-body [rest-ty rest-ty])
(with-lexical-env/extend
arg-list arg-types
(make-lam-result (for/list ([al (in-list arg-list)]
[at (in-list arg-types)])
(list al at))
(and rest-ty (list (or rest (generate-temporary)) rest-ty))
(and drest (list (or rest (generate-temporary)) drest))
(tc-body/check body ret-ty))))
;; Check that the number of formal arguments is valid for the expected type.
;; Thus it must be able to accept the number of arguments that the expected
;; type has. So we check for two cases: if the function doesn't accept
;; enough arguments, or if it requires too many arguments.
;; This allows a form like (lambda args body) to have the type (-> Symbol
;; Number) with out a rest arg.
(when (or (and (< arg-len tys-len) (not rest))
(when (or (and (< arg-len tys-len) (not rest-id))
(and (> arg-len tys-len) (not (or rest-ty drest))))
(tc-error/delayed (expected-str tys-len rest-ty drest arg-len rest)))
(cond
[(not rest)
(check-body)]
[drest
(with-lexical-env/extend
(list rest) (list (make-ListDots (car drest) (cdr drest)))
(check-body))]
[(dotted? rest)
=>
(lambda (b)
(let ([dty (extend-tvars (list b) (get-type rest #:default Univ))])
(with-lexical-env/extend
(list rest) (list (make-ListDots dty b))
(check-body))))]
[else
(define base-rest-type
(cond
[(type-annotation rest) (get-type rest #:default Univ)]
[rest-ty rest-ty]
[else Univ]))
(define extra-types
(if (<= arg-len tys-len)
(drop arg-tys arg-len)
null))
(define rest-type (apply Un base-rest-type extra-types))
(tc-error/delayed (expected-str tys-len rest-ty drest arg-len rest-id)))
(define rest-type
(cond
[(not rest-id) #f]
[drest drest]
[(dotted? rest-id)
=> (λ (b) (cons (extend-tvars (list b) (get-type rest-id #:default Univ)) b))]
[else
(define base-rest-type
(cond
[(type-annotation rest-id) (get-type rest-id #:default Univ)]
[rest-ty rest-ty]
[else Univ]))
(define extra-types
(if (<= arg-len tys-len)
(drop arg-tys arg-len)
null))
(apply Un base-rest-type extra-types)]))
(with-lexical-env/extend
(list rest) (list (-lst rest-type))
(check-body rest-type))])))
(tc-lambda-body arg-list arg-types
#:rest (and rest-type (list rest-id rest-type))
#:expected ret-ty
body)))
;; typecheck a single lambda, with argument list and body
;; drest-ty and drest-bound are both false or not false
;; tc/lambda-clause/check: formals syntax listof[Type/c] tc-result
;; option[Type/c] option[(cons Type/c symbol)] -> lam-result
;; option[Type/c] option[(cons Type/c symbol)] -> arr?
(define (tc/lambda-clause/check formals body arg-tys ret-ty rest-ty drest)
(check-clause (formals-positional formals) (formals-rest formals) body arg-tys rest-ty drest ret-ty))
;; typecheck a single opt-lambda clause with argument list and body
;; tc/opt-lambda-clause: listof[identifier] syntax -> listof[lam-result]
;; tc/opt-lambda-clause: listof[identifier] syntax -> listof[arr?]
(define (tc/opt-lambda-clause arg-list body aux-table flag-table)
;; arg-types: Listof[Type/c]
(define arg-types
@ -166,17 +184,11 @@
[(free-identifier=? i v) (-val #f)]
[else t])))))))
(for/list ([arg-types (in-list new-arg-types)])
(with-lexical-env/extend
arg-list arg-types
(lam-result
(map list arg-list arg-types)
#f
#f
(tc-body/check body #f)))))
(tc-lambda-body arg-list arg-types body)))
;; formals syntax -> listof[lam-result]
;; formals syntax -> listof[arr?]
(define (tc/lambda-clause formals body)
(define-values (aux-table flag-table)
(syntax-parse body
@ -190,8 +202,7 @@
(tc/opt-lambda-clause arg-list body aux-table flag-table)]
[else
(define arg-types (get-types arg-list #:default Univ))
(define combined-args (map list arg-list arg-types))
(list
(define rest-type
(cond
;; Lambda with poly dotted rest argument
[(and rest-id (dotted? rest-id))
@ -201,36 +212,18 @@
(if (bound-tvar? bound)
(tc-error "Bound on ... type (~a) is not an appropriate type variable" bound)
(tc-error/stx rest-id "Bound on ... type (~a) was not in scope" bound)))
(let ([rest-type (extend-tvars (list bound)
(get-type rest-id #:default Univ))])
(with-lexical-env/extend
(cons rest-id arg-list)
(cons (make-ListDots rest-type bound) arg-types)
(lam-result
combined-args
#f
(list rest-id (cons rest-type bound))
(tc-body/check body #f)))))]
(cons (extend-tvars (list bound) (get-type rest-id #:default Univ)) bound))]
;; Lambda with regular rest argument
[rest-id
(let ([rest-type (get-type rest-id #:default Univ)])
(with-lexical-env/extend
(cons rest-id arg-list)
(cons (make-Listof rest-type) arg-types)
(lam-result
combined-args
(list rest-id rest-type)
#f
(tc-body/check body #f))))]
(get-type rest-id #:default Univ)]
;; Lambda with no rest argument
[else
(with-lexical-env/extend
arg-list arg-types
(lam-result
combined-args
#f
#f
(tc-body/check body #f)))]))]))
[else #f]))
(list
(tc-lambda-body arg-list arg-types
#:rest (and rest-type (list rest-id rest-type))
body))]))
;; positional: natural? - the number of positional arguments
;; rest: boolean? - if there is a positional argument
@ -290,7 +283,7 @@
(and (member new-positional positionals) (not new-rest))))
;; tc/mono-lambda : (listof (list formals syntax?)) (or/c #f tc-results) -> (listof lam-result)
;; tc/mono-lambda : (listof (list formals syntax?)) (or/c #f tc-results) -> (listof arr?)
;; typecheck a sequence of case-lambda clauses
(define (tc/mono-lambda formals+bodies expected)
(define expected-type
@ -349,7 +342,7 @@
[(Function: (list)) #f]
[_ #t]))
;; TODO improve error message.
(tc-error/expr #:return (list (lam-result null (list (generate-temporary) Univ) #f (ret (Un))))
(tc-error/expr #:return (list (make-arr* null (Un) #:rest Univ))
"Expected a function of type ~a, but got a function with the wrong arity"
expected-type)
(apply append
@ -363,12 +356,11 @@
f* b* args (values->tc-results ret (formals->objects f*)) rest drest))])))))
(define (tc/mono-lambda/type formals bodies expected)
(make-Function (map lam-result->type
(tc/mono-lambda
(map list
(stx-map make-formals formals)
(syntax->list bodies))
expected))))
(make-Function
(tc/mono-lambda
(for/list ([f (in-syntax formals)] [b (in-syntax bodies)])
(list (make-formals f) b))
expected)))
(define (plambda-prop stx)
(define d (plambda-property stx))