add #:pre/desc and #:post/desc to ->i

This commit is contained in:
Robby Findler 2014-12-22 22:31:17 -06:00
parent 9d58a067e3
commit 94d80f0171
6 changed files with 213 additions and 99 deletions

View File

@ -1049,6 +1049,8 @@ symbols, and that return a symbol.
[pre-condition (code:line)
(code:line #:pre (id ...)
boolean-expr pre-condition)
(code:line #:pre/desc (id ...)
expr pre-condition)
(code:line #:pre/name (id ...)
string boolean-expr pre-condition)]
[dependent-range any
@ -1059,6 +1061,8 @@ symbols, and that return a symbol.
[post-condition (code:line)
(code:line #:post (id ...)
boolean-expr post-condition)
(code:line #:post/desc (id ...)
expr post-condition)
(code:line #:post/name (id ...)
string boolean-expr post-condition)]
[id+ctc [id contract-expr]
@ -1068,10 +1072,9 @@ symbols, and that return a symbol.
)]{
The @racket[->i] contract combinator differs from the @racket[->*]
combinator in that the support pre- and post-condition clauses and
in that each argument and result is named. These names can then
combinator in that each argument and result is named and these names can
be used in the subcontracts and in the pre-/post-condition clauses.
In short, contracts now express dependencies among arguments and results.
In other words, @racket[->i] expresses dependencies among arguments and results.
The first sub-form of a @racket[->i] contract covers the mandatory and the
second sub-form covers the optional arguments. Following that is an optional
@ -1079,6 +1082,8 @@ rest-args contract, and an optional pre-condition. The pre-condition is
introduced with the @racket[#:pre] keyword followed by the list of names on
which it depends. If the @racket[#:pre/name] keyword is used, the string
supplied is used as part of the error message; similarly with @racket[#:post/name].
If @racket[#:pre/desc] or @racket[#:post/desc] is used, the the result of
the expression is treated the same way as @racket[->*].
The @racket[dependent-range] non-terminal specifies the possible result
contracts. If it is @racket[any], then any value is allowed. Otherwise, the

View File

@ -646,6 +646,20 @@
'pos 'neg)
(cons #f 1)))
(test/neg-blame
'->i35-g
'((contract (->i ([x any/c]) #:pre/desc (x) (pair? x) #:pre/desc (x) (car x) any)
(λ (x) 1)
'pos 'neg)
(cons #f 1)))
(test/neg-blame
'->i35-h
'((contract (->i ([x any/c]) #:pre/desc (x) '("x") any)
(λ (x) 1)
'pos 'neg)
(cons #f 1)))
(test/spec-passed/result
'->i36
'((contract (->i ([f (-> number? number?)]) [res number?])
@ -802,6 +816,28 @@
'neg)
(cons #f 1)))
(test/pos-blame
'->i47-f
'((contract (->i ([x any/c])
[y () any/c]
#:post/desc (y) (pair? y)
#:post/desc (y) (car y))
(lambda (x) x)
'pos
'neg)
(cons #f 1)))
(test/pos-blame
'->i47-g
'((contract (->i ([x any/c])
[y () any/c]
#:post/desc (y) (pair? y)
#:post/desc (y) "x")
(lambda (x) x)
'pos
'neg)
(cons #f 1)))
(test/spec-passed/result
'->i48
'(let ([x '()])

View File

@ -98,6 +98,8 @@
(->i () #:pre () #t [q number?]))
(test-name '(->i () #:pre () #t [q () number?] #:post () #t)
(->i () #:pre () #t [q () number?] #:post () #t))
(test-name '(->i () #:pre () #t [q () number?] #:post/desc () #t)
(->i () #:pre () #t [q () number?] #:post/desc () #t))
(test-name '(->i ([x integer?]) #:pre (x) #t [q (x) number?] #:post (x) #t)
(->i ([x integer?]) #:pre (x) #t [q (x) number?] #:post (x) #t))
(test-name '(->i ([x real?]) [_ (x) (>/c x)])
@ -108,6 +110,8 @@
#:post/name (y) "car" (car y))
(->i ([x any/c]) [y () any/c] #:post/name (y) "pair" (pair? y)
#:post/name (y) "car" (car y)))
(test-name '(->i () #:pre/desc () #t [q number?])
(->i () #:pre/desc () #t [q number?]))
(test-name '(->i ([p any/c]
[q (p) (if (equal? p 10) 'aha any/c)])
#:rest [rest (p) (if (equal? p 11) 'aha any/c)]

View File

@ -49,8 +49,11 @@ code does the parsing and validation of the syntax.
;; vars : (listof identifier?)
;; exp : syntax[expr]
;; str : (or/c #f syntax[expr])
(struct pre/post (vars str exp quoted-dep-src-code) #:transparent)
;; kind : (or/c syntax[expr] 'desc 'bool)
;; syntax => #:pre/name, where the syntax object holds the literal string
;; 'desc => #:pre/desc or #:post/desc
;; 'bool => #:pre or #:post
(struct pre/post (vars kind exp quoted-dep-src-code) #:transparent)
(define (parse-->i stx)
(if (identifier? stx)
@ -375,6 +378,8 @@ code does the parsing and validation of the syntax.
(values '() leftover)]
[(dep-range #:post . stuff)
(values '() leftover)]
[(dep-range #:post/desc . stuff)
(values '() leftover)]
[(dep-range #:post/name . stuff)
(values '() leftover)]
[((opts ...) . rest)
@ -412,27 +417,38 @@ code does the parsing and validation of the syntax.
(let loop ([leftover leftover]
[conditions '()])
(syntax-case leftover ()
[(#:pre (id ...) pre-cond . pre-leftover)
[(kwd (id ...) pre-cond . pre-leftover)
(or (equal? (syntax-e #'kwd) '#:pre)
(equal? (syntax-e #'kwd) '#:pre/desc))
(begin
(syntax-case #'pre-leftover ()
[() (raise-syntax-error
#f
(format
(string-append
"expected #:pre to be followed by at least three subterms"
"expected ~a to be followed by at least three subterms"
" (a sequence of identifiers, the pre-condition, and the"
" range contract), but found only two")
(syntax-e #'kwd))
stx
(car (syntax->list leftover)))]
[x (void)])
(for-each (λ (x) (check-id stx x)) (syntax->list #'(id ...)))
(loop #'pre-leftover
(cons (pre/post (syntax->list #'(id ...)) #f #'pre-cond
(cons (pre/post (syntax->list #'(id ...))
(if (equal? '#:pre/desc (syntax-e #'kwd))
'desc
'bool)
#'pre-cond
(compute-quoted-src-expression #'pre-cond))
conditions)))]
[(#:pre . rest)
[(kwd . rest)
(or (equal? (syntax-e #'kwd) '#:pre)
(equal? (syntax-e #'kwd) '#:pre/desc))
(raise-syntax-error
#f
"expected a sequence of identifiers and an expression to follow #:pre"
(format "expected a sequence of identifiers and an expression to follow ~a"
(syntax-e #'kwd))
stx
(car (syntax->list leftover)))]
[(#:pre/name (id ...) str pre-cond . pre-leftover)
@ -455,7 +471,9 @@ code does the parsing and validation of the syntax.
stx
#'str))
(loop #'pre-leftover
(cons (pre/post (syntax->list #'(id ...)) (syntax-e #'str) #'pre-cond
(cons (pre/post (syntax->list #'(id ...))
(syntax-e #'str)
#'pre-cond
(compute-quoted-src-expression #'pre-cond))
conditions)))]
[(#:pre/name . rest)
@ -481,25 +499,43 @@ code does the parsing and validation of the syntax.
(let loop ([leftover leftover]
[post-conds '()])
(syntax-case leftover ()
[(#:post (id ...) post-cond . leftover)
[(kwd (id ...) post-cond . leftover)
(or (equal? (syntax-e #'kwd) '#:post/desc)
(equal? (syntax-e #'kwd) '#:post))
(begin
(for-each (λ (x) (check-id stx x)) (syntax->list #'(id ...)))
(syntax-case range (any)
[any (raise-syntax-error
#f "cannot have a #:post with any as the range" stx #'post-cond)]
#f
(format "cannot have a ~a with any as the range"
(syntax-e #'kwd))
stx #'post-cond)]
[_ (void)])
(loop #'leftover
(cons (pre/post (syntax->list #'(id ...)) #f #'post-cond
(cons (pre/post (syntax->list #'(id ...))
(if (equal? (syntax-e #'kwd) '#:post/desc)
'desc
'bool)
#'post-cond
(compute-quoted-src-expression #'post-cond))
post-conds)))]
[(#:post a b . stuff)
[(kwd a b . stuff)
(or (equal? (syntax-e #'kwd) '#:post/desc)
(equal? (syntax-e #'kwd) '#:post))
(begin
(raise-syntax-error
#f "expected a sequence of variables to follow #:post" stx #'a))]
[(#:post a)
#f
(format "expected a sequence of variables to follow ~a"
(syntax-e #'kwd))
stx #'a))]
[(kwd a)
(or (equal? (syntax-e #'kwd) '#:post/desc)
(equal? (syntax-e #'kwd) '#:post))
(begin
(raise-syntax-error
#f "expected a sequence of variables and an expression to follow #:post"
#f
(format "expected a sequence of variables and an expression to follow ~a"
(syntax-e #'kwd))
stx #'a))]
[(#:post/name (id ...) str post-cond . leftover)
(begin

View File

@ -7,6 +7,7 @@
"misc.rkt"
"blame.rkt"
"generate.rkt"
"arrow-higher-order.rkt"
syntax/location
racket/private/performance-hint
(for-syntax racket/base
@ -263,9 +264,13 @@
(define ids (list-ref pre-info 0))
(define name (list-ref pre-info 1))
(define code (list-ref pre-info 2))
(if name
`(#:pre/name ,ids ,name ,code)
`(#:pre ,ids ,code))))
(cond
[(string? name)
`(#:pre/name ,ids ,name ,code)]
[(equal? name 'bool)
`(#:pre ,ids ,code)]
[(equal? name 'desc)
`(#:pre/desc ,ids ,code)])))
,(cond
[(not rng-info)
'any]
@ -285,9 +290,13 @@
(define ids (list-ref post-info 0))
(define name (list-ref post-info 1))
(define code (list-ref post-info 2))
(if name
`(#:post/name ,ids ,name ,code)
`(#:post ,ids ,code)))))))
(cond
[(string? name)
`(#:post/name ,ids ,name ,code)]
[(equal? name 'bool)
`(#:post ,ids ,code)]
[(equal? name 'desc)
`(#:post/desc ,ids ,code)]))))))
#:first-order
(λ (ctc)
(let ([has-rest (->i-rest ctc)]
@ -540,44 +549,58 @@ evaluted left-to-right.)
(define-for-syntax (maybe-generate-temporary x)
(and x (car (generate-temporaries (list x)))))
(define (signal-pre/post pre? val str blame . var-infos)
(define pre-str (or str
(define (signal-pre/post pre? val kind blame condition-result . var-infos)
(define vars-str
(apply
string-append
(for/list ([var-info (in-list var-infos)])
(format "\n ~s: ~e"
(list-ref var-info 0)
(list-ref var-info 1)))))
(define msg
(cond
[(string? kind) (string-append kind vars-str)]
[(or (equal? kind 'bool)
(and (equal? kind 'desc)
(equal? condition-result #f)))
(string-append
(if pre? "#:pre" "#:post")
" condition violation"
(if (null? var-infos)
""
"; variables are:"))))
(raise-blame-error blame val
(apply
string-append
pre-str
(for/list ([var-info (in-list var-infos)])
(format "\n ~s: ~e"
(list-ref var-info 0)
(list-ref var-info 1))))))
"; variables are:")
vars-str)]
[else
(pre-post/desc-result->string condition-result pre? '->i)]))
(raise-blame-error blame val "~a" msg))
(define-for-syntax (add-pre-cond an-istx indy-arg-vars ordered-args indy-res-vars ordered-ress
call-stx)
#`(begin #,@(for/list ([pre (in-list (istx-pre an-istx))]
[i (in-naturals)])
(define id (string->symbol (format "pre-proc~a" i)))
#`(unless (#,id #,@(map (λ (var) (arg/res-to-indy-var indy-arg-vars
#`(let ([condition-result
(#,id #,@(map (λ (var) (arg/res-to-indy-var indy-arg-vars
ordered-args
indy-res-vars
ordered-ress
var))
(pre/post-vars pre)))
(pre/post-vars pre)))])
(unless #,(if (equal? (pre/post-kind pre) 'desc)
#'(equal? condition-result #t)
#'condition-result)
(signal-pre/post #t
val
#,(pre/post-str pre)
'#,(pre/post-kind pre)
swapped-blame
#,@(map (λ (x) #`(list '#,x #,(arg/res-to-indy-var indy-arg-vars
condition-result
#,@(map (λ (x) #`(list '#,x
#,(arg/res-to-indy-var indy-arg-vars
ordered-args
indy-res-vars
ordered-ress
x)))
(pre/post-vars pre)))))
(pre/post-vars pre))))))
#,call-stx))
(define-for-syntax (add-post-cond an-istx indy-arg-vars ordered-args indy-res-vars ordered-ress
@ -585,23 +608,28 @@ evaluted left-to-right.)
#`(begin #,@(for/list ([post (in-list (istx-post an-istx))]
[i (in-naturals)])
(define id (string->symbol (format "post-proc~a" i)))
#`(unless (#,id #,@(map (λ (var) (arg/res-to-indy-var indy-arg-vars
#`(let ([condition-result
(#,id #,@(map (λ (var) (arg/res-to-indy-var indy-arg-vars
ordered-args
indy-res-vars
ordered-ress
var))
(pre/post-vars post)))
(pre/post-vars post)))])
(unless #,(if (equal? (pre/post-kind post) 'desc)
#'(equal? condition-result #t)
#'condition-result)
(signal-pre/post
#f
val
#,(pre/post-str post)
'#,(pre/post-kind post)
blame
condition-result
#,@(map (λ (x) #`(list '#,x #,(arg/res-to-indy-var indy-arg-vars
ordered-args
indy-res-vars
ordered-ress
x)))
(pre/post-vars post)))))
(pre/post-vars post))))))
#,call-stx))
;; add-wrapper-let :
@ -1254,7 +1282,7 @@ evaluted left-to-right.)
#f)
#,(for/list ([pre (in-list (istx-pre an-istx))])
(list (map syntax-e (pre/post-vars pre))
(pre/post-str pre)
(pre/post-kind pre)
(pre/post-quoted-dep-src-code pre)))
#,(and (istx-ress an-istx)
(for/list ([a-res (in-list (istx-ress an-istx))])
@ -1270,7 +1298,7 @@ evaluted left-to-right.)
,(arg/res-quoted-dep-src-code a-res))))
#,(for/list ([post (in-list (istx-post an-istx))])
(list (map syntax-e (pre/post-vars post))
(pre/post-str post)
(pre/post-kind post)
(pre/post-quoted-dep-src-code post)))))
'racket/contract:contract
(let ()

View File

@ -14,7 +14,8 @@
(provide (for-syntax build-chaperone-constructor/real)
->-proj
check-pre-cond
check-post-cond)
check-post-cond
pre-post/desc-result->string)
(define-for-syntax (build-chaperone-constructor/real this-args
mandatory-dom-projs
@ -79,12 +80,19 @@
(void)]
[else
(define msg
(pre-post/desc-result->string condition-result pre? '->*))
(raise-blame-error (if pre? (blame-swap blame) blame)
#:missing-party neg-party
val "~a" msg)]))
(define (pre-post/desc-result->string condition-result pre? who)
(cond
[(equal? condition-result #f)
(if pre?
"#:pre condition"
"#:post condition")]
[(string? condition-result) condition-result]
[(string? condition-result)
condition-result]
[(and (list? condition-result)
(andmap string? condition-result))
(apply
@ -98,13 +106,10 @@
(loop (cdr s)))])))]
[else
(error
'->*
who
"expected #:~a/desc to produce (or/c boolean? string? (listof string?)), got ~e"
(if pre? "pre" "post")
condition-result)]))
(raise-blame-error (if pre? (blame-swap blame) blame)
#:missing-party neg-party
val "~a" msg)]))
(define-for-syntax (create-chaperone blame val
this-args