added exists to provide/contract

svn: r15871
This commit is contained in:
Robby Findler 2009-09-04 04:52:02 +00:00
parent 5d000d425e
commit 937d2efab3
3 changed files with 282 additions and 19 deletions

View File

@ -15,7 +15,8 @@ improve method arity mismatch contract violation error messages?
define-struct/contract define-struct/contract
define/contract define/contract
with-contract with-contract
current-contract-region) current-contract-region
new-/c)
(require (for-syntax scheme/base) (require (for-syntax scheme/base)
(for-syntax "contract-opt-guts.ss") (for-syntax "contract-opt-guts.ss")
@ -683,9 +684,6 @@ improve method arity mismatch contract violation error messages?
(quasisyntax/loc stx (#%expression #,stx))))))) (quasisyntax/loc stx (#%expression #,stx)))))))
;; (provide/contract p/c-ele ...)
;; p/c-ele = (id expr) | (rename id id expr) | (struct id (id expr) ...)
;; provides each `id' with the contract `expr'.
(define-syntax (provide/contract provide-stx) (define-syntax (provide/contract provide-stx)
(syntax-case provide-stx (struct) (syntax-case provide-stx (struct)
[(_ p/c-ele ...) [(_ p/c-ele ...)
@ -725,19 +723,62 @@ improve method arity mismatch contract violation error messages?
;; code-for-each-clause : (listof syntax) -> (listof syntax) ;; code-for-each-clause : (listof syntax) -> (listof syntax)
;; constructs code for each clause of a provide/contract ;; constructs code for each clause of a provide/contract
(define (code-for-each-clause clauses) (define (code-for-each-clause clauses)
(let loop ([clauses clauses]
[exists-binders '()])
(cond (cond
[(null? clauses) null] [(null? clauses) null]
[else [else
(let ([clause (car clauses)]) (let ([clause (car clauses)])
;; compare raw identifiers for `struct' and `rename' just like provide does ;; compare raw identifiers for `struct' and `rename' just like provide does
(syntax-case* clause (struct rename) (λ (x y) (eq? (syntax-e x) (syntax-e y))) (syntax-case* clause (struct rename) (λ (x y) (eq? (syntax-e x) (syntax-e y)))
[exists
(or (eq? '#:exists (syntax-e #'exists)) (eq? '#: (syntax-e #'exists)))
(cond
[(null? (cdr clauses))
(raise-syntax-error 'provide/conract
(format "expected either a single variable or a sequence of variables to follow ~a, but found nothing"
(syntax-e #'exists))
provide-stx
clause)]
[else
(syntax-case (cadr clauses) ()
[x
(identifier? #'x)
(with-syntax ([(x-gen) (generate-temporaries #'(x))])
(cons (code-for-one-exists-id #'x #'x-gen)
(loop (cddr clauses)
(add-a-binder #'x #'x-gen exists-binders))))]
[(x ...)
(andmap identifier? (syntax->list #'(x ...)))
(with-syntax ([(x-gen ...) (generate-temporaries #'(x ...))])
(append (map code-for-one-exists-id
(syntax->list #'(x ...))
(syntax->list #'(x-gen ...)))
(loop (cddr clauses)
(let loop ([binders exists-binders]
[xs (syntax->list #'(x ...))]
[x-gens (syntax->list #'(x-gen ...))])
(cond
[(null? xs) binders]
[else (loop (add-a-binder (car xs) (car x-gens) binders)
(cdr xs)
(cdr x-gens))])))))]
[else
(raise-syntax-error 'provide/contract
(format "expected either a single variable or a sequence of variables to follow ~a"
(syntax-e #'exists))
provide-stx
(cadr clauses))])])]
[(rename this-name new-name contract) [(rename this-name new-name contract)
(and (identifier? (syntax this-name)) (and (identifier? (syntax this-name))
(identifier? (syntax new-name))) (identifier? (syntax new-name)))
(begin (begin
(add-to-dups-table #'new-name) (add-to-dups-table #'new-name)
(cons (code-for-one-id provide-stx (syntax this-name) (syntax contract) (syntax new-name)) (cons (code-for-one-id provide-stx
(code-for-each-clause (cdr clauses))))] (syntax this-name)
(add-exists-binders (syntax contract) exists-binders)
(syntax new-name))
(loop (cdr clauses) exists-binders)))]
[(rename this-name new-name contract) [(rename this-name new-name contract)
(identifier? (syntax this-name)) (identifier? (syntax this-name))
(raise-syntax-error 'provide/contract (raise-syntax-error 'provide/contract
@ -758,9 +799,10 @@ improve method arity mismatch contract violation error messages?
(let ([sc (build-struct-code provide-stx (let ([sc (build-struct-code provide-stx
(syntax struct-name) (syntax struct-name)
(syntax->list (syntax (field-name ...))) (syntax->list (syntax (field-name ...)))
(syntax->list (syntax (contract ...))))]) (map (λ (x) (add-exists-binders x exists-binders))
(syntax->list (syntax (contract ...)))))])
(add-to-dups-table #'struct-name) (add-to-dups-table #'struct-name)
(cons sc (code-for-each-clause (cdr clauses))))] (cons sc (loop (cdr clauses) exists-binders)))]
[(struct name) [(struct name)
(identifier? (syntax name)) (identifier? (syntax name))
(raise-syntax-error 'provide/contract (raise-syntax-error 'provide/contract
@ -801,8 +843,12 @@ improve method arity mismatch contract violation error messages?
(identifier? (syntax name)) (identifier? (syntax name))
(begin (begin
(add-to-dups-table #'name) (add-to-dups-table #'name)
(cons (code-for-one-id provide-stx (syntax name) (syntax contract) #f) (cons (code-for-one-id provide-stx
(code-for-each-clause (cdr clauses))))] (syntax name)
(add-exists-binders (syntax contract)
exists-binders)
#f)
(loop (cdr clauses) exists-binders)))]
[(name contract) [(name contract)
(raise-syntax-error 'provide/contract (raise-syntax-error 'provide/contract
"expected identifier" "expected identifier"
@ -812,7 +858,7 @@ improve method arity mismatch contract violation error messages?
(raise-syntax-error 'provide/contract (raise-syntax-error 'provide/contract
"malformed clause" "malformed clause"
provide-stx provide-stx
(syntax unk))]))])) (syntax unk))]))])))
;; well-formed-struct-name? : syntax -> bool ;; well-formed-struct-name? : syntax -> bool
(define (well-formed-struct-name? stx) (define (well-formed-struct-name? stx)
@ -1159,6 +1205,16 @@ improve method arity mismatch contract violation error messages?
field-contract-id field-contract-id
void?)))) void?))))
;; code-for-one-exists-id : syntax -> syntax
(define (code-for-one-exists-id x x-gen)
#`(define #,x-gen (new-/c '#,x)))
(define (add-exists-binders stx exists-binders)
#`(let #,exists-binders #,stx))
(define (add-a-binder id id-gen binders)
(cons #`[#,id #,id-gen] binders))
;; code-for-one-id : syntax syntax syntax (union syntax #f) -> syntax ;; code-for-one-id : syntax syntax syntax (union syntax #f) -> syntax
;; given the syntax for an identifier and a contract, ;; given the syntax for an identifier and a contract,
;; builds a begin expression for the entire contract and provide ;; builds a begin expression for the entire contract and provide
@ -1209,7 +1265,9 @@ improve method arity mismatch contract violation error messages?
#,@(if no-need-to-check-ctrct? #,@(if no-need-to-check-ctrct?
(list) (list)
(list #'(define contract-id (verify-contract 'provide/contract ctrct)))) (list #'(define contract-id
(let ([id ctrct]) ;; let is here to give the right name.
(verify-contract 'provide/contract id)))))
(define-syntax id-rename (define-syntax id-rename
(make-provide/contract-transformer (quote-syntax contract-id) (make-provide/contract-transformer (quote-syntax contract-id)
(quote-syntax id) (quote-syntax id)
@ -2571,3 +2629,33 @@ improve method arity mismatch contract violation error messages?
#:property stronger-prop #:property stronger-prop
(λ (this that) (λ (this that)
#f)) #f))
(define (-proj ctc)
(let ([in (/c-in ctc)]
[out (/c-out ctc)]
[pred? (/c-pred? ctc)])
(λ (pos-blame neg-blame src-info orig-str positive-position?)
(if positive-position?
in
(λ (val)
(if (pred? val)
(out val)
(raise-contract-error val src-info pos-blame orig-str
"non-polymorphic value: ~e"
val)))))))
(define-struct /c (in out pred? name)
#:omit-define-syntaxes
#:property proj-prop -proj
#:property name-prop (λ (ctc) (/c-name ctc))
#:property first-order-prop
(λ (ctc) (λ (x) #t)) ;; ???
#:property stronger-prop
(λ (this that) #f))
(define (new-/c raw-name)
(define name (string->symbol (format "~a/∃" raw-name)))
(define-values (struct-type constructor predicate accessor mutator)
(make-struct-type name #f 1 0))
(make-/c constructor (λ (x) (accessor x 0)) predicate raw-name))

View File

@ -11,6 +11,10 @@ another. Programmers specify the behavior of a module exports via
@scheme[provide/contract] and the contract system enforces those @scheme[provide/contract] and the contract system enforces those
constraints. constraints.
@note-lib[scheme/contract #:use-sources (scheme/private/contract-ds
scheme/private/contract
scheme/private/contract-guts)]
@deftech{Contracts} come in two forms: those constructed by the @deftech{Contracts} come in two forms: those constructed by the
various operations listed in this section of the manual, and various various operations listed in this section of the manual, and various
ordinary Scheme values that double as contracts, including ordinary Scheme values that double as contracts, including
@ -34,10 +38,6 @@ failed, and anything else to indicate it passed.}
] ]
@note-lib[scheme/contract #:use-sources (scheme/private/contract-ds
scheme/private/contract
scheme/private/contract-guts)]
@local-table-of-contents[] @local-table-of-contents[]
@; ---------------------------------------- @; ----------------------------------------
@ -368,6 +368,27 @@ Constructs a contract on a promise. The contract does not force the
promise, but when the promise is forced, the contract checks that the promise, but when the promise is forced, the contract checks that the
result value meets the contract produced by @scheme[expr].} result value meets the contract produced by @scheme[expr].}
@defproc[(new-∃/c [name symbol?]) contract?]{
Constructs a new existential contract.
Existential contracts accept all values when in positive positions (e.g., function
returns) and wraps the value in an opaque struct, hiding the precise value.
In negative positions (e.g. function inputs),
it accepts only values that were previously accepted in negative positions (by checking
for the wrappers).
For example, this contract:
@schemeblock[(let ([a (new-∃/c 'a)])
(-> (-> a a)
any/c))]
describes a function that accepts the identity function (or a non-terminating function)
and returns an arbitrary value. That is, the first use of the @scheme[a] appears in a
positive position and thus inputs to that function are wrapped with an opaque struct.
Then, when the function returns, it is checked to see if the result is wrapped, since
the second @scheme[a] appears in a negative position.
}
@; ------------------------------------------------------------------------ @; ------------------------------------------------------------------------
@section{Function Contracts} @section{Function Contracts}
@ -669,7 +690,11 @@ lazy contract.}
(struct id ((id contract-expr) ...)) (struct id ((id contract-expr) ...))
(struct (id identifier) ((id contract-expr) ...)) (struct (id identifier) ((id contract-expr) ...))
(rename orig-id id contract-expr) (rename orig-id id contract-expr)
(id contract-expr)])]{ (id contract-expr)
(code:line #:∃ exists-variables)
(code:line #:exists exists-variables)]
[exists-variables identifier
(identifier ...)])]{
Can only appear at the top-level of a @scheme[module]. As with Can only appear at the top-level of a @scheme[module]. As with
@scheme[provide], each @scheme[id] is provided from the module. In @scheme[provide], each @scheme[id] is provided from the module. In
@ -699,7 +724,13 @@ referring to the parent struct. Unlike @scheme[define-struct],
however, all of the fields (and their contracts) must be listed. The however, all of the fields (and their contracts) must be listed. The
contract on the fields that the sub-struct shares with its parent are contract on the fields that the sub-struct shares with its parent are
only used in the contract for the sub-struct's maker, and the selector only used in the contract for the sub-struct's maker, and the selector
or mutators for the super-struct are not provided.} or mutators for the super-struct are not provided.
The @scheme[#:∃] and @scheme[#:exists] clauses define new abstract
contracts. The variables are bound in the remainder of the @scheme[provide/contract]
expression to new contracts that hide the values they accept and
ensure that the exported functions are treated parametrically.
}
@defform/subs[ @defform/subs[
(with-contract blame-id (wc-export ...) free-var-list body ...+) (with-contract blame-id (wc-export ...) free-var-list body ...+)

View File

@ -6153,6 +6153,52 @@ so that propagation occurs.
'neg)) 'neg))
(f 10))) (f 10)))
;
;
;
;
;
; ;;;;;;
; ; ;
; ; ;
; ; ;; ;;;;
; ;;;;;; ; ;;;;;;
; ; ; ;;;
; ; ; ;;;
; ; ;; ;;;;;;
; ;;;;;; ; ;;;;
;
;
;
;
(test/spec-passed
'1
'(contract (new-/c 'pair)
1
'pos
'neg))
(test/neg-blame
'2
'((contract (-> (new-/c 'pair) any/c)
(λ (x) x)
'pos
'neg)
1))
(test/spec-passed/result
'3
'(let ([pair (new-/c 'pair)])
((contract (-> (-> pair pair) any/c)
(λ (f) (f 11))
'pos
'neg)
(λ (x) x)))
11)
; ;
; ;
; ;
@ -6683,6 +6729,104 @@ so that propagation occurs.
(eval 'provide/contract29-res)) (eval 'provide/contract29-res))
(list #t 3)) (list #t 3))
;; for this test I have to be able to track back thru the requirees to find the right
;; name for the negative blame (currently it blames m3, but it should blame m2).
#;
(test/spec-failed
'provide/contract30
'(begin
(eval '(module provide/contract30-m1 scheme/base
(require scheme/contract)
(provide/contract [f (-> integer? integer?)])
(define (f x) x)))
(eval '(module provide/contract30-m2 scheme/base
(require 'provide/contract30-m1)
(provide f)))
(eval '(module provide/contract30-m3 scheme/base
(require 'provide/contract30-m2)
(f #f)))
(eval '(require 'provide/contract30-m3)))
"'provide/contract30-m2")
(test/spec-passed/result
'provide/contract31
'(begin
(eval '(module provide/contract31-m1 scheme/base
(require scheme/contract)
(provide/contract
#: x
[f (-> (-> x x) 10)])
(define (f g) (g 10))))
(eval '(module provide/contract31-m2 scheme/base
(require scheme/contract 'provide/contract31-m1)
(provide provide/contract31-x)
(define provide/contract31-x (f (λ (x) x)))))
(eval '(require 'provide/contract31-m2))
(eval 'provide/contract31-x))
10)
(test/spec-passed/result
'provide/contract32
'(begin
(eval '(module provide/contract32-m1 scheme/base
(require scheme/contract)
(provide/contract
#:exists x
[f (-> (-> x x) 10)])
(define (f g) (g 10))))
(eval '(module provide/contract32-m2 scheme/base
(require scheme/contract 'provide/contract32-m1)
(provide provide/contract32-x)
(define provide/contract32-x (f (λ (x) x)))))
(eval '(require 'provide/contract32-m2))
(eval 'provide/contract32-x))
10)
(test/spec-passed/result
'provide/contract33
'(begin
(eval '(module provide/contract33-m1 scheme/base
(require scheme/contract)
(provide/contract
#:exists (x)
[f (-> (-> x x) 10)])
(define (f g) (g 10))))
(eval '(module provide/contract33-m2 scheme/base
(require scheme/contract 'provide/contract33-m1)
(provide provide/contract33-x)
(define provide/contract33-x (f (λ (x) x)))))
(eval '(require 'provide/contract33-m2))
(eval 'provide/contract33-x))
10)
(test/spec-passed/result
'provide/contract34
'(begin
(eval '(module provide/contract34-m1 scheme/base
(require scheme/contract)
(define x integer?)
(define g 11)
(provide/contract
[g x]
#:exists (x)
[f (-> (-> x x) 10)])
(define (f g) (g 10))))
(eval '(module provide/contract34-m2 scheme/base
(require scheme/contract 'provide/contract34-m1)
(provide provide/contract34-x)
(define provide/contract34-x (f (λ (x) x)))))
(eval '(require 'provide/contract34-m2))
(eval 'provide/contract34-x))
10)
(contract-error-test (contract-error-test
#'(begin #'(begin
(eval '(module pce1-bug scheme/base (eval '(module pce1-bug scheme/base