extending option contracts; now handle procedures, vectors, hashes, struct instances, adding support for invariants for vectors, hashes and struct instances

This commit is contained in:
chrdimo 2013-02-14 19:53:26 -05:00
parent 61e896c8d7
commit ee81c98ae9

View File

@ -1,34 +1,82 @@
#lang racket #lang racket
(provide option/c transfer-option exercise-option waive-option) (provide option/c transfer-option exercise-option waive-option invariant/c)
(require syntax/location (require syntax/location
(for-syntax racket/provide-transform)) (for-syntax racket/provide-transform)
(for-syntax racket/list)
(for-syntax racket/struct-info))
(define-for-syntax (get-struct-info id who)
(cond
[(and (identifier? id)
(struct-info? (syntax-local-value id (λ () #f))))
(let ([si (extract-struct-info (syntax-local-value id))])
(with-syntax ([id id]
[pred (third si)]
[(accessor ...) (fourth si)]
[(mutator ...) (fifth si)])
#'(list pred (list accessor ...) (list mutator ...) 'id)))]
[else
(raise-syntax-error who "expected a struct identifier" id)]))
(define-for-syntax (convert-args args this-one who)
(let loop ([args args]
[new-args null])
(cond
[(null? args) (reverse new-args)]
[(keyword? (syntax-e (car args)))
(if (null? (cdr args))
(reverse (cons (car args) new-args))
(cond [(eq? (syntax-e (car args)) '#:struct)
(loop (cddr args)
(list* (get-struct-info (cadr args) who) (car args) new-args))]
[else
(loop (cddr args)
(list* (cadr args) (car args) new-args))]))]
[else
(loop (cdr args)
(cons (syntax-property
(car args)
'racket/contract:positive-position
this-one)
new-args))])))
(define (same-type v s-info)
(let ([pred (first s-info)])
(and pred (pred v))))
(define (no-mutators? s-info)
(if (symbol? s-info)
#t
(andmap boolean? (third s-info))))
(struct info (val proj blame)) (struct info (val proj blame))
(define-values (impersonator-prop:proxy proxy? proxy-info) (define-values (impersonator-prop:proxy proxy? proxy-info)
(make-impersonator-property 'proxy)) (make-impersonator-property 'proxy))
(define (build-proxy val proj blame) (define (build-proxy ctc val proj blame)
(let ([proxy-info (info val proj blame)]) (let ([proxy-info (info val proj blame)])
(cond [(procedure? val) (cond [(procedure? val)
(chaperone-procedure (chaperone-procedure
val val
values values
impersonator-prop:proxy impersonator-prop:contracted ctc
proxy-info)] impersonator-prop:proxy proxy-info)]
[(vector? val) [(vector? val)
(chaperone-vector (chaperone-vector
val val
(λ (v i val) val) (λ (v i val) val)
(λ (v i val) val) (λ (v i val) val)
impersonator-prop:proxy impersonator-prop:contracted ctc
proxy-info)] impersonator-prop:proxy proxy-info)]
[(hash? val) [(hash? val)
(chaperone-hash (chaperone-hash
val val
@ -36,17 +84,15 @@
(λ (h k v) (values k v)) (λ (h k v) (values k v))
(λ (h k) k) (λ (h k) k)
(λ (h k) k) (λ (h k) k)
impersonator-prop:proxy impersonator-prop:contracted ctc
proxy-info)] impersonator-prop:proxy proxy-info)]
[(struct? val) [else
(chaperone-hash (chaperone-struct
val val
(λ (h k) (values k (λ (h k v) v))) (first (second (option-structid ctc)))
(λ (h k v) (values k v)) (λ (v f) f)
(λ (h k) k) impersonator-prop:contracted ctc
(λ (h k) k) impersonator-prop:proxy proxy-info)])))
impersonator-prop:proxy
proxy-info)])))
(define (run-tester tester val orig-ctc blame here) (define (run-tester tester val orig-ctc blame here)
@ -55,21 +101,66 @@
[option-blame [option-blame
(λ (blame context) (λ (blame context)
(blame-add-context blame context))]) (blame-add-context blame context))])
(unless (tester ((proj (unless (tester ((proj indy-blame) val))
(option-blame
blame
(format "in option contract tester ~e" tester)))
val))
(raise-blame-error (raise-blame-error
(option-blame indy-blame (format "option contract tester ~e failure" tester)) (option-blame indy-blame (format "option contract tester ~e of" tester))
val val
"")))) ""))))
(struct option (orig-ctc tester here)
(define (optionc-name c)
(apply build-compound-type-name 'option/c
(contract-name (option-orig-ctc c))
(append
(if (eq? (option-tester c) 'dont-care)
null
(list '#:tester (option-tester c)))
(if (eq? (option-flat c) #f)
null
(list '#:flat? #t))
(if (eq? (option-immutable c) 'dont-care)
null
(list '#:immutable (option-immutable c)))
(if (eq? (option-invariant c) 'dont-care)
null
(list '#:invariant (option-invariant c)))
(if (eq? (option-structid c) 'none)
null
(list '#:struct (fourth (option-structid c)))))))
(define (check-option c val fail)
(let ([orig-ctc (option-orig-ctc c)]
[structid (option-structid c)]
[invariant (option-invariant c)]
[immutable (option-immutable c)]
[flat (option-flat c)])
(when (and (eq? invariant 'dont-care)
(or (not (eq? immutable 'dont-care))
(not (eq? flat #f))))
(fail val '(expected "an invariant keyword argument (based on presence of other keyword arguments)")))
(unless (or (and (procedure? val) (eq? structid 'none))
(and (vector? val) (eq? structid 'none))
(and (hash? val) (eq? structid 'none))
(and (not (eq? structid 'none)) (same-type val structid)))
(if (eq? structid 'none)
(fail val '(expected "a procedure or a vector or a hash" given: "~e") val)
(fail val '(expected "a struct of type ~a" given: "~e") (fourth structid) val)))))
(define (build-orig-proj c inv flat immutable structid here)
(cond [(eq? inv 'dont-care) (option-orig-ctc c)]
[else
(invariantc (option-orig-ctc c) inv #:struct structid #:flat? flat #:immutable immutable here)]))
(struct option (orig-ctc tester invariant flat immutable structid here)
#:property prop:contract #:property prop:contract
(build-contract-property (build-contract-property
#:name #:name
(λ (ctc) (contract-name (option-orig-ctc ctc))) optionc-name
#:first-order #:first-order
(λ (ctc) (λ (ctc)
(λ (val) (λ (val)
@ -77,37 +168,55 @@
#:projection #:projection
(λ (ctc) (λ (ctc)
(λ (blame) (λ (blame)
(define raise-blame (λ (val . args)
(apply raise-blame-error blame val args)))
(λ (val) (λ (val)
(let ([tester (option-tester ctc)] (check-option ctc val raise-blame)
(let* ([tester (option-tester ctc)]
[invariant (option-invariant ctc)]
[flat (option-flat ctc)]
[immutable (option-immutable ctc)]
[structid (option-structid ctc)]
[here (option-here ctc)]
[orig-ctc (option-orig-ctc ctc)] [orig-ctc (option-orig-ctc ctc)]
[here (option-here ctc)]) [exec-ctc (build-orig-proj ctc invariant flat immutable structid here)])
(unless (symbol? tester) (unless (symbol? tester)
(run-tester tester val orig-ctc blame here)) (run-tester tester val orig-ctc blame here))
(build-proxy val (contract-projection orig-ctc) blame))))))) (build-proxy ctc val (contract-projection exec-ctc) blame)))))))
(define (build-option ctc
#:tester [tester 'dont-care]
#:invariant [invariant 'dont-care]
#:flat? [flat #f]
#:immutable [immutable 'dont-care]
#:struct [structid 'none]
here)
(option ctc tester invariant flat immutable structid here))
(define (build-option ctc here #:tester [tester 'dont-care])
(option ctc tester here))
(define-syntax (option/c stx) (define-syntax (option/c stx)
(syntax-case stx () (syntax-case stx ()
[(_ con) [(optionc arg ...)
(let ([args (syntax->list #'(arg ...))]
[this-one (gensym 'option-ctc)])
(with-syntax ([(new-arg ...) (convert-args args this-one 'option/c)])
(syntax-property (syntax-property
#`(build-option con (quote-module-name)) (syntax/loc stx
(build-option new-arg ... (quote-module-name)))
'racket/contract:contract 'racket/contract:contract
(vector (gensym 'option/c) (vector this-one (list #'optionc) null))))]))
(list (car (syntax-e stx)))
'()))]
[(_ con key tester)
(syntax-property
#`(build-option con (quote-module-name) key tester)
'racket/contract:contract
(vector (gensym 'option/c)
(list (car (syntax-e stx)))
'()))]))
(struct transfer ()
(define (transferc-name c)
(apply build-compound-type-name 'transfer-option (transfer-id c) empty))
(struct transfer (id)
#:property prop:contract #:property prop:contract
(build-contract-property (build-contract-property
#:name
transferc-name
#:projection #:projection
(λ (ctc) (λ (ctc)
(λ (blame) (λ (blame)
@ -115,23 +224,28 @@
(let ([option-blame (let ([option-blame
(blame-add-context (blame-add-context
blame blame
(format "option contract transfer failure: ~a does not have an option" val))] (format "~a does not have an option in" val)
#:important (format "~a" (transfer-id ctc)))]
[pos-blame (blame-positive blame)] [pos-blame (blame-positive blame)]
[neg-blame (blame-negative blame)]) [neg-blame (blame-negative blame)])
(cond [(proxy? val) (cond [(proxy? val)
(let ((info (proxy-info val))) (let ((info (proxy-info val)))
(build-proxy (build-proxy
(value-contract val)
(info-val info) (info-val info)
(info-proj info) (info-proj info)
(blame-update (info-blame info) pos-blame neg-blame)))] (blame-update (info-blame info) pos-blame neg-blame)))]
[else (raise-blame-error option-blame val "")]))))))) [else (raise-blame-error option-blame val "")])))))))
(define-syntax transfer/c (define-syntax (transfer/c stx)
(syntax-id-rules () (syntax-case stx ()
[_ (transfer)])) [(transferc id)
(let ([this-one (gensym 'transfer-ctc)])
(define-for-syntax (fresh-names l) (syntax-property
(map (lambda (x) (datum->syntax #f (gensym x))) (syntax->datum l))) (syntax/loc stx
(transfer 'id))
'racket/contract:contract
(vector this-one null (list #'transferc))))]))
(define-syntax transfer-option (define-syntax transfer-option
(make-provide-pre-transformer (make-provide-pre-transformer
@ -148,20 +262,255 @@
(with-syntax ([(new-id ...) (generate-temporaries #'(id ...))]) (with-syntax ([(new-id ...) (generate-temporaries #'(id ...))])
#`(begin #`(begin
(begin (define new-id id) ...) (begin (define new-id id) ...)
(provide (contract-out [rename new-id id transfer/c] ...)))))]) (provide (contract-out [rename new-id id (transfer/c id)] ...)))))])
#`(combine-out)))) #`(combine-out))))
(define (exercise-option val) (define (exercise-option val)
(cond [(proxy? val) (cond [(and (has-contract? val) (option? (value-contract val)))
(let ((info (proxy-info val))) (let ((info (proxy-info val)))
(((info-proj info) (((info-proj info)
(info-blame info)) (info-blame info))
(info-val info)))] (info-val info)))]
[else (error 'exercise-error "~a has no option to exercise" val)])) [else (error 'exercise-option-error "~a has no option to exercise" val)]))
(define (waive-option val) (define (waive-option val)
(cond [(proxy? val) (info-val (proxy-info val))] (cond [(and (has-contract? val) (option? (value-contract val))) (info-val (proxy-info val))]
[else (error 'exercise-error "~a has no option to exercise" val)])) [else (error 'waive-option-error "~a has no option to waive" val)]))
;
;
;
; ;
; ; ; ;
; ; ; ; ;
; ; ;
; ;;; ;; ;;; ;;; ;;; ;;;; ;;; ;; ;;; ;;;; ;; ;;; ;;;;;;; ; ;;; ;
; ; ;; ; ; ; ; ;;; ; ; ;; ; ; ; ; ;;
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
; ; ; ; ; ; ;;;;;; ; ; ;;;;;; ; ; ; ; ;
; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ; ;
; ; ; ; ; ; ; ;; ; ; ; ;; ; ; ; ; ; ; ;
; ;;;;;;; ;;; ;;; ;;; ;;;; ;; ;;;;;; ;;;;;;; ;;;; ;; ;;; ;;; ;;;; ; ;;;;
; ;
;
;
;
;
(define-struct invariant-info (ctc invariant structid flat immutable here))
(define (invariantc-name c)
(let ([immutable (invariant-info-immutable c)]
[flat (invariant-info-flat c)]
[invariant (invariant-info-invariant c)]
[structid (invariant-info-structid c)])
(apply build-compound-type-name 'invariant/c
(contract-name (invariant-info-ctc c))
invariant
(append
(if (eq? structid 'none)
null
(list '#:struct (fourth structid)))
(if flat
(list '#:flat? #t)
null)
(if (eq? immutable 'dont-care)
null
(list '#:immutable immutable))))))
(define (check-invariant c)
(let ([orig-ctc (invariant-info-ctc c)]
[immutable (invariant-info-immutable c)]
[invariant (invariant-info-invariant c)]
[structid (invariant-info-structid c)])
(λ (val fail first-order?)
(unless (or (and (vector? val) (eq? structid 'none))
(and (hash? val) (eq? structid 'none))
(and (not (eq? structid 'none)) (same-type val structid)))
(if (eq? structid 'none)
(fail val '(expected "a vector or a hash" given: "~e") val)
(fail val '(expected "a struct of type ~a" given: "~e") (fourth structid) val)))
(cond
[(eq? immutable #t)
(unless (or (immutable? val) (and (not (symbol? structid)) (no-mutators? structid)))
(fail val '(expected "immutable data" given: "~e") val))]
[(eq? immutable #f)
(when (or (immutable? val)
(and (not (symbol? structid)) (no-mutators? structid)))
(fail val '(expected "mutable data" given: "~e") val))]
[else (void)])
(when first-order?
(unless (contract-first-order-passes? orig-ctc val)
(let ([kind (cond [(vector? val) "vector"]
[(hash? val) "hash"]
[else "struct"])])
(fail val '(expected: "~s that satisfies ~s" given: "~e") kind (contract-name orig-ctc) val))))
#t)))
(define (invariantc-first-order ctc)
(let ([check (check-invariant ctc)])
(λ (val)
(let/ec return
(check val (λ _ (return #f)) #t)))))
(define-struct (immutable-invariantc invariant-info) ()
#:property prop:flat-contract
(build-flat-contract-property
#:name invariantc-name
#:first-order invariantc-first-order
#:projection
(λ (ctc)
(define check (check-invariant ctc))
(λ (blame)
(define raise-blame (λ (val . args)
(apply raise-blame-error blame val args)))
(λ (val)
(check val raise-blame #f)
(let ([orig-proj (contract-projection (invariant-info-ctc ctc))]
[indy-blame (blame-replace-negative blame (invariant-info-here ctc))]
[invariant (invariant-info-invariant ctc)])
(unless (invariant ((orig-proj indy-blame) val))
(let ([kind (cond [(vector? val) 'vector]
[(hash? val) 'hash]
[else 'struct])])
(raise-blame-error
blame
val
(format "expected ~s that satisfies ~s given: ~e" kind invariant val))))
((orig-proj blame) val)))))))
(define (build-inv-proxy ctc val inv proj blame indy-blame impersonate?)
(define (run-invariant kind blame)
(unless (inv ((proj indy-blame) val))
(raise-blame-error
blame
val
(format "expected ~s that satisfies ~s given: ~e" kind inv val))))
(cond [(vector? val)
(let ([vector-wrapper
(λ (wrapper )
(wrapper
((proj blame) val)
(λ (vec i v) (run-invariant 'vector blame) v)
(λ (vec i v) (vector-set! vec i v) (run-invariant 'vector (blame-swap blame)) v)
impersonator-prop:contracted ctc))])
(if impersonate?
(vector-wrapper impersonate-vector)
(vector-wrapper chaperone-vector)))]
[(hash? val)
(let ([hash-wrapper
(λ (wrapper)
(wrapper
((proj blame) val)
(λ (h k) (run-invariant 'hash blame) (values k (λ (h k v) v)))
(λ (h k v) (if (immutable? h) (hash-set h k v) (hash-set! h k v)) (run-invariant 'hash (blame-swap blame)) (values k v))
(λ (h k) (if (immutable? h) (hash-remove h k) (hash-remove! h k)) (run-invariant 'hash (blame-swap blame)) k)
(λ (h k) (run-invariant 'hash blame) k)
impersonator-prop:contracted ctc))])
(if impersonate?
(hash-wrapper impersonate-hash)
(hash-wrapper chaperone-hash)))]
[else
(let* ([s-info (invariant-info-structid ctc)]
[a-wrap (λ (v f) (run-invariant 'struct blame) f)]
[m-wrap (λ (m)
(λ (v f)
(m v f) (run-invariant 'struct (blame-swap blame)) f))]
[wrapped-accessors (foldr (λ (first rest)
(if (procedure? first)
(list* first a-wrap rest)
rest))
'()
(second s-info))]
[wrapped-mutators (foldr (λ (first rest)
(if (procedure? first)
(list* first (m-wrap first) rest)
rest))
'()
(third s-info))]
[struct-wrapper
(λ (wrapper)
(apply
wrapper
((proj blame) val)
(append wrapped-accessors wrapped-mutators (list impersonator-prop:contracted ctc))))])
(if impersonate?
(struct-wrapper impersonate-struct)
(struct-wrapper chaperone-struct)))]))
(define (invariantc-ho-projection impersonate?)
(λ (ctc)
(let ([orig-ctc (invariant-info-ctc ctc)]
[immutable (invariant-info-immutable ctc)]
[check (check-invariant ctc)]
[invariant (invariant-info-invariant ctc)]
[here (invariant-info-here ctc)])
(λ (blame)
(let ([indy-blame (blame-replace-negative blame here)]
[proj (contract-projection orig-ctc) ]
[raise-blame (λ (val . args)
(apply raise-blame-error blame val args))])
(λ (val)
(check val raise-blame #f)
(unless (invariant (((contract-projection orig-ctc) indy-blame) val))
(let ([kind (cond [(vector? val) 'vector]
[(hash? val) 'hash]
[else 'struct])])
(raise-blame-error
blame
val
(format "expected ~s that satisfies ~s given: ~e" kind invariant val))))
(build-inv-proxy ctc val invariant proj blame indy-blame impersonate?)))))))
(define-struct (chaperone-invariantc invariant-info) ()
#:property prop:chaperone-contract
(build-chaperone-contract-property
#:name invariantc-name
#:first-order invariantc-first-order
#:projection (invariantc-ho-projection #f)))
(define-struct (impersonator-invariantc invariant-info) ()
#:property prop:contract
(build-contract-property
#:name invariantc-name
#:first-order invariantc-first-order
#:projection (invariantc-ho-projection #t)))
(define-syntax (invariant/c stx)
(syntax-case stx ()
[(invc arg ...)
(let ([args (syntax->list #'(arg ...))]
[this-one (gensym 'invariant-ctc)])
(with-syntax ([(new-arg ...) (convert-args args this-one 'invariant/c)])
(syntax-property
(syntax/loc stx
(invariantc new-arg ... (quote-module-name)))
'racket/contract:contract
(vector this-one (list #'invc) null))))]))
(define (invariantc c inv #:struct [structid 'none] #:flat? [flat #f] #:immutable [immutable 'dont-care] here)
(cond
[(or flat
(and (eq? immutable #t)
(flat-contract? c)))
(make-immutable-invariantc c inv structid flat immutable here)]
[(chaperone-contract? c)
(make-chaperone-invariantc c inv structid flat immutable here)]
[else
(make-impersonator-invariantc c inv structid flat immutable here)]))