start adding struct/dc contracts

This commit is contained in:
Robby Findler 2012-04-17 14:27:54 -05:00
parent a4381dd1f6
commit 04017d83d5
7 changed files with 1203 additions and 390 deletions

View File

@ -7,6 +7,7 @@
"private/hash.rkt"
"private/vector.rkt"
"private/struct.rkt"
"private/struct-dc.rkt"
"private/struct-prop.rkt"
"private/misc.rkt"
"private/provide.rkt"
@ -31,6 +32,7 @@
"private/hash.rkt"
"private/vector.rkt"
"private/struct.rkt"
"private/struct-dc.rkt"
"private/struct-prop.rkt")
(except-out (all-from-out "private/base.rkt")
current-contract-region)

View File

@ -209,6 +209,7 @@
;; sorts the arguments according to the dependency order.
;; returns them in the reverse of that order, ie expressions that need
;; to be evaluted first come later in the list.
;; BAD: this seem wrong, as it doesn't consider transitive dependencies
(define-for-syntax (find-ordering args)
(define (comes-before? x y)

File diff suppressed because it is too large Load Diff

View File

@ -13,8 +13,7 @@
"misc.rkt"
"opt.rkt")
(provide struct/c
(rename-out [-struct/dc struct/dc]))
(provide struct/c)
(define-syntax (struct/c stx)
(syntax-case stx ()
@ -245,371 +244,3 @@
[else
(make-impersonator-struct/c struct-name predicate immutables mutables)]))
(define unique (box #f))
(define (un-dep ctc obj blame immutable-field)
(let ([ctc (coerce-contract 'struct/dc ctc)])
(when immutable-field
(check-chaperone-contract immutable-field ctc))
(((contract-projection ctc) blame) obj)))
(define (struct/dc-name ctc)
(define info (struct/dc-name-info ctc))
`(struct/dc ,(vector-ref info 0)
,@(for/list ([x (in-list (vector-ref info 1))]
[subctc (in-list (struct/dc-procs/ctcs ctc))])
`[,@(vector-ref x 1)
,(if (vector-ref x 0)
(contract-name subctc)
'...)])))
(define (struct/dc-first-order ctc)
(struct/dc-pred ctc))
(define (struct/dc-proj ctc)
(define pred? (struct/dc-pred ctc))
(define mk-proj ((struct/dc-apply-proj ctc) ctc))
(λ (blame)
(define proj (mk-proj blame))
(λ (v)
(cond
[(and (struct/dc-imp-prop-pred? v)
(contract-stronger? (struct/dc-imp-prop-get v) ctc))
v]
[else
(unless (pred? v)
(raise-blame-error blame v "expected a ~a"
(struct/dc-struct-name ctc)))
(proj v)]))))
(define (struct/dc-stronger? this that)
(and (struct/dc? that)
(eq? (struct/dc-pred this)
(struct/dc-pred that))
(let loop ([this-procs/ctcs (struct/dc-procs/ctcs this)]
[that-procs/ctcs (struct/dc-procs/ctcs that)])
(cond
[(and (null? this-procs/ctcs) (null? that-procs/ctcs)) #t]
[(and (pair? this-procs/ctcs) (pair? that-procs/ctcs))
(define fst-this (car this-procs/ctcs))
(define fst-that (car that-procs/ctcs))
(cond
[(and (contract-struct? fst-this) (contract-struct? fst-that))
(and (contract-stronger? fst-this fst-that)
(loop (cdr this-procs/ctcs) (cdr that-procs/ctcs)))]
[(and (procedure? fst-this) (procedure? fst-that))
(and (procedure-closure-contents-eq? fst-this fst-that)
(loop (cdr this-procs/ctcs) (cdr that-procs/ctcs)))]
[else #f])]
[else #f]))))
(define-struct struct/dc (apply-proj procs/ctcs pred struct-name here name-info)
#:property prop:chaperone-contract
(parameterize ([skip-projection-wrapper? #t])
(build-chaperone-contract-property
#:name struct/dc-name
#:first-order struct/dc-first-order
#:projection struct/dc-proj
#:stronger struct/dc-stronger?)))
(define-for-syntax (get-struct-info id stx)
(define inf (syntax-local-value id (λ () #f)))
(unless (struct-info? inf)
(raise-syntax-error 'struct/dc "expected a struct" stx id))
(define the-info (extract-struct-info inf))
(unless (list-ref the-info 2)
(raise-syntax-error 'struct/dc
"expected a struct with a known predicate"
stx id))
the-info)
(define-values (struct/dc-imp-prop-desc
struct/dc-imp-prop-pred?
struct/dc-imp-prop-get)
(make-impersonator-property 'struct/dc))
(define-for-syntax (clause->chap-proc struct-id info stx clause-stx)
(define sel-id (syntax-case clause-stx ()
[(sel-id . rest) #'sel-id]))
(define (add-prefix id)
(datum->syntax id
(string->symbol (format "~a-~a"
(syntax-e sel-id)
(syntax-e id)))))
(define immutable-field
(for/or ([mutator (in-list (list-ref info 4))]
[selector (in-list (list-ref info 3))])
(cond
[(and (not mutator) (not selector))
;; end, with some hidden info
;; just assume not immutable
#f]
[else
(and (not mutator)
(let ([id (id->sel-id struct-id sel-id)])
(and (free-identifier=? id selector)
id)))])))
(define (add-immutable-check ctc-id stx)
(if immutable-field
(list stx
#`(check-chaperone-contract '#,immutable-field #,ctc-id))
(list stx)))
(syntax-case clause-stx ()
;; with caching
[(sel-id #:lazy (id ...) exp)
(with-syntax ([(dep-sel-id ...) (map (λ (x) (id->sel-id struct-id x)) (syntax->list #'(id ...)))])
(with-syntax ([dep-proc (add-prefix #'dep-proc)])
#`(((define dep-proc (λ (id ...) #,(defeat-inlining #'exp))))
(begin)
(begin)
(begin)
(let ([cached unique])
(λ (strct fld)
(if (eq? cached unique)
(begin
(set! cached (un-dep (dep-proc (dep-sel-id strct) ...) fld blame '#,immutable-field))
cached)
cached)))
#(#f (sel-id #:lazy (id ...))))))]
[(sel-id (id ...) exp)
(with-syntax ([(dep-sel-id ...) (map (λ (x) (id->sel-id struct-id x)) (syntax->list #'(id ...)))])
(with-syntax ([dep-proc (add-prefix #'dep-proc)])
#`(((define dep-proc (λ (id ...) #,(defeat-inlining #'exp))))
(begin)
(begin)
(un-dep (dep-proc (dep-sel-id v) ...) (#,(id->sel-id struct-id #'sel-id) v) blame '#,immutable-field)
(λ (strct fld)
(un-dep (dep-proc (dep-sel-id strct) ...) fld blame '#,immutable-field))
#(#f (sel-id (id ...))))))]
[(sel-id #:lazy exp)
(with-syntax ([ctc (add-prefix #'ctc)]
[blame-to-proj (add-prefix #'blame-to-proj)]
[proj (add-prefix #'proj)])
#`(#,(add-immutable-check #'ctc #'(define ctc (coerce-contract 'struct/dc exp)))
(define blame-to-proj (contract-struct-projection ctc))
(define proj (blame-to-proj blame))
(begin)
(let ([cached unique])
(λ (strct fld)
(if (eq? cached unique)
(begin
(set! cached (proj fld))
cached)
cached)))
#(#t (sel-id #:lazy))))]
[(sel-id exp)
(with-syntax ([ctc (add-prefix #'ctc)]
[blame-to-proj (add-prefix #'blame-to-proj)]
[proj (add-prefix #'proj)])
#`(#,(add-immutable-check #'ctc #'(define ctc (coerce-contract 'struct/dc exp)))
(define blame-to-proj (contract-struct-projection ctc))
(define proj (blame-to-proj blame))
(proj (#,(id->sel-id struct-id #'sel-id) v))
(if (flat-contract? ctc)
(λ (strct fld) fld)
(λ (strct fld) (proj fld)))
#(#t (sel-id))))]
[_ (raise-syntax-error #f "malformed clause" stx clause-stx)]))
(define (check-chaperone-contract immutable-field ctc)
(unless (chaperone-contract? ctc)
(error 'struct/dc "expected a chaperone contract for the immutable field ~a, got ~e"
immutable-field
ctc)))
(define-for-syntax (id->sel-id struct-id id)
(datum->syntax
id
(string->symbol
(format "~a-~a"
(syntax-e struct-id)
(syntax-e id)))))
(define-syntax (-struct/dc stx)
(syntax-case stx ()
[(_ struct-id clause ...)
(let ()
(define info (get-struct-info #'struct-id stx))
(with-syntax ([(((before-ctc-bound ...) after-ctc-bound after-blame-bound first-order-check chap-proc name-info) ...)
(for/list ([clause (in-list (syntax->list #'(clause ...)))])
(clause->chap-proc #'struct-id info stx clause))])
(with-syntax ([(id ...) (syntax-case #'((before-ctc-bound ...) ...) ()
[(((define id exp) . whatever) ...) #'(id ...)])]
[(selectors+chap-procs ...)
(apply
append
(for/list ([clause (in-list (syntax->list #'(clause ...)))]
[chap-proc (in-list (syntax->list #'(chap-proc ...)))])
(list (id->sel-id
#'struct-id
(syntax-case clause ()
[(x . rest) #'x]))
chap-proc)))])
#`(let ()
before-ctc-bound ... ...
(letrec ([me
(make-struct/dc
(λ (ctc)
after-ctc-bound ...
(λ (blame)
after-blame-bound ...
(λ (v)
first-order-check ...
(chaperone-struct
v
selectors+chap-procs ...
struct/dc-imp-prop-desc
me))))
(list id ...)
#,(list-ref info 2)
'struct-id
(quote-module-name)
'#(struct-id (name-info ...)))])
me)))))]))
(define/opter (-struct/dc opt/i opt/info stx)
(syntax-case stx ()
[(_ struct-id clause ...)
(let ()
(define info (get-struct-info #'struct-id stx))
(cond
[(ormap values (list-ref info 4))
;; any mutable struct, just give up (could generate impersonator code, but
;; would have to check that the compiled subcontracts are all chaperones/flats)
(opt/unknown opt/i opt/info stx)]
[else
(define-values (s-chap-code s-flat-code s-lifts s-super-lifts s-partially-applied can-be-optimized? stronger-ribs chaperone?)
(for/fold ([s-chap-code '()]
[s-flat-code '()]
[s-lifts '()]
[s-super-lifts '()]
[s-partially-applied '()]
[can-be-optimized? #t]
[stronger-ribs '()]
[chaperone? #t])
([clause (in-list (syntax->list #'(clause ...)))])
(define-values (sel-id lazy? dep-vars exp)
(syntax-case clause ()
[(sel-id #:lazy exp) (values #'sel-id #t #f #'exp)]
[(sel-id exp) (values #'sel-id #f #f #'exp)]
[(sel-id #:lazy (dep-id ...) exp) (values #'sel-id #t #'(dep-id ...) #'exp)]
[(sel-id (dep-id ...) exp) (values #'sel-id #f #'(dep-id ...) #'exp)]))
(define-values (this-code
this-lifts this-super-lifts this-partially-applied
this-flat? this-can-be-optimized? this-stronger-ribs
this-chaperone?)
(opt/i opt/info exp))
(values (cond
[(and this-flat? (not lazy?) (not dep-vars))
s-chap-code]
[else
(with-syntax ([(strct cache) (generate-temporaries '(struct cache))]
[proc-name (string->symbol
(format "~a-~a-chap/dep"
(syntax-e #'struct-id)
(syntax-e sel-id)))])
(list* (cond
[dep-vars
(with-syntax ([(sel ...) (map (λ (var) (id->sel-id #'struct-id var))
(syntax->list dep-vars))]
[(dep-var ...) dep-vars])
(with-syntax ([this-code+lifts
#`(let ([dep-var (sel strct)] ...)
#,(bind-superlifts
this-super-lifts
(bind-lifts
this-lifts
(bind-lifts
this-partially-applied
this-code))))])
(if lazy?
#`(let ([cache unique])
(let ([proc-name
(λ (strct #,(opt/info-val opt/info))
(cond
[(eq? cache unique)
(set! cache this-code+lifts)
cache]
[else cache]))])
proc-name))
#`(let ([proc-name
(λ (strct #,(opt/info-val opt/info))
this-code+lifts)])
proc-name))))]
[else
(if lazy?
#`(let ([cache unique])
(let ([proc-name
(λ (strct #,(opt/info-val opt/info))
(cond
[(eq? cache unique)
(set! cache #,this-code)
cache]
[else cache]))])
proc-name))
#`(let ([proc-name
(λ (strct #,(opt/info-val opt/info))
#,this-code)])
proc-name))])
(id->sel-id #'struct-id sel-id)
s-chap-code))])
(cond
[lazy?
s-flat-code]
[dep-vars
(with-syntax ([(sel ...) (map (λ (var) (id->sel-id #'struct-id var))
(syntax->list dep-vars))]
[(dep-var ...) dep-vars])
(cons #` (let ([dep-var (sel #,(opt/info-val opt/info))] ...)
(let ([#,(opt/info-val opt/info) (#,(id->sel-id #'struct-id sel-id)
#,(opt/info-val opt/info))])
#,this-code))
s-flat-code))]
[else
(cons #`(let ([#,(opt/info-val opt/info) (#,(id->sel-id #'struct-id sel-id)
#,(opt/info-val opt/info))])
#,this-code)
s-flat-code)])
(if dep-vars s-lifts (append this-lifts s-lifts))
(if dep-vars s-super-lifts (append this-super-lifts s-super-lifts))
(if dep-vars s-partially-applied (append this-partially-applied s-partially-applied))
(and this-can-be-optimized? can-be-optimized?)
(append this-stronger-ribs stronger-ribs)
(and this-chaperone? chaperone?))))
(with-syntax ([(stronger-prop-desc stronger-prop-pred? stronger-prop-get)
(syntax-local-lift-values-expression
3
#'(make-impersonator-property 'struct/dc-stronger-prop))]
[(free-var ...) (opt/info-free-vars opt/info)]
[(index ...) (build-list (length (opt/info-free-vars opt/info)) values)]
[pred? (list-ref info 2)])
(values #`(if (and (stronger-prop-pred? #,(opt/info-val opt/info))
(let ([v (stronger-prop-get #,(opt/info-val opt/info))])
(and (eq? (vector-ref v index) free-var) ...)))
#,(opt/info-val opt/info)
(if (pred? #,(opt/info-val opt/info))
(begin
#,@(reverse s-flat-code) ;; built the last backwards, so reverse it here
(chaperone-struct
#,(opt/info-val opt/info)
#,@(reverse s-chap-code) ;; built the last backwards, so reverse it here
stronger-prop-desc
(vector free-var ...)))
(struct/dc-error blame #,(opt/info-val opt/info) 'struct-name)))
s-lifts
s-super-lifts
s-partially-applied
#f ;; flat sexp
can-be-optimized?
stronger-ribs
#t ;;chaperone?
))]))]))
(define (struct/dc-error blame obj what)
(raise-blame-error blame obj
"expected a struct of type ~a"
what))

View File

@ -0,0 +1,59 @@
#lang racket/base
(provide top-sort)
;; top-sort : (listof α) (α -> (listof α)) -> (listof α) or #f
;; returns #f if there is a cycle in the graph
;; (α needs hashing)
(define (top-sort elements neighbors)
(define parents (make-hash))
(define children (make-hash))
(define ids (make-hash))
(for ([element (in-list elements)]
[n (in-naturals)])
(hash-set! ids element n))
(define (add-link table from to)
(hash-set! (hash-ref table from) to #t))
;; initialize the tables telling me about parents and children
(for ([ele (in-list elements)])
(hash-set! parents ele (make-hash))
(hash-set! children ele (make-hash)))
(for ([parent (in-list elements)])
(for ([child (in-list (neighbors parent))])
(add-link parents parent child)
(add-link children child parent)))
;; contains elements that have no (unscheduled) dependencies
(define pending (make-hash))
(for ([(k v) (in-hash parents)])
(when (zero? (hash-count v))
(hash-set! pending k #t)))
(define sorted
(let loop ()
(cond
[(zero? (hash-count pending))
'()]
[else
(define best #f)
(for ([(ele _) (in-hash pending)])
(cond
[best
(when (< (hash-ref ids ele) (hash-ref ids best))
(set! best ele))]
[else
(set! best ele)]))
(hash-remove! pending best)
(for ([(child _) (in-hash (hash-ref children best))])
(define childs-parents (hash-ref parents child))
(hash-remove! childs-parents best)
(when (zero? (hash-count childs-parents))
(hash-set! pending child #t)))
(cons best (loop))])))
(cond
[(zero? (hash-count pending)) sorted]
[else #f]))

View File

@ -390,20 +390,40 @@ produced. Otherwise, an impersonator contract is produced.
@defform/subs[(struct/dc struct-id field-spec ...)
([field-spec [field-id contract-expr]
[field-id #:lazy contract-expr]
[field-id (dep-field-id ...) contract-expr]
[field-id (dep-field-id ...) #:lazy contract-expr]])]{
([field-spec [field-id maybe-lazy contract-expr]
[field-id (dep-field-id ...)
maybe-lazy
maybe-impersonator
maybe-flat
maybe-dep-state
contract-expr]]
[maybe-lazy (code:line) #:lazy]
[maybe-impersonator (code:line) #:impersonator]
[maybe-flat (code:line) #:flat]
[maybe-dep-state (code:line) #:depends-on-state])]{
Produces a contract that recognizes instances of the structure
type named by @racket[struct-id], and whose field values match the
contracts produced by the @racket[field-spec]s.
Each @racket[field-spec] can specify if the field is check lazily
(only when a selector is applied) or not via the @racket[#:lazy]
keyword. If the @racket[field-spec] lists the names of other fields,
keyword.
If the @racket[field-spec] lists the names of other fields,
then the contract depends on values in those fields, and the @racket[contract-expr]
expression is evaluated each time a selector is applied, building a new contract
for the fields based on the values of the @racket[dep-field-id] fields.
If the field is a dependent field, then it is assumed that the contract is
always a chaperone contract. If this is not the case, and the contract is
always flat, or sometimes not a chaperone, then the field must be annotated with
the @racket[#:flat] or @racket[#:impersonator].
If a dependent contract depends on some mutable state, then use the
@racket[#:depends-on-state] keyword argument (if a field's dependent contract
depends on a mutable field, this keyword is automatically inferred).
The presence of this keyword means that the contract expression is evaluated
each time the corresponding field is accessed (or mutated, if it is a mutable
field).
Contracts for immutable fields must be either flat or chaperone contracts.
Contracts for mutable fields may be impersonator contracts.
@ -412,7 +432,7 @@ to flat contracts, a flat contract is produced. If all the
@racket[contract-expr]s are chaperone contracts, a chaperone contract is
produced. Otherwise, an impersonator contract is produced.
For example, the function @racket[bst/c] below
As an example, the function @racket[bst/c] below
returns a contract for binary search trees whose values
are all between @racket[lo] and @racket[hi].

View File

@ -3072,8 +3072,11 @@
#'(contract symbol? "not a symbol" 'pos 'neg 'not-a-symbol #'here)
(lambda (x)
(and (exn:fail:contract:blame? x)
(regexp-match? #px"<collects>"
(exn-message x)))))
(let ([msg (exn-message x)])
(define ans (regexp-match? #px"<collects>" msg))
(unless ans
(printf "msg: ~s\n" msg))
ans))))
;; make sure that ->i checks its arguments
(contract-error-test
@ -3724,6 +3727,13 @@
(contract (-> funny/c any) void 'pos 'neg)))
(test/spec-passed
'or/c-opt-unknown-flat
(let ()
(define arr (-> number? number?))
((contract (opt/c (or/c not arr)) (λ (x) x) 'pos 'neg) 1)))
;
;
@ -9196,7 +9206,7 @@
(define-opt/c (f z)
(struct/dc s
[a (>=/c z)]
[b #:lazy (a) (f a)]))
[b (a) #:lazy (f a)]))
(s-a (contract (f 11)
(s 12 (s 13 #f))
@ -9211,7 +9221,7 @@
(define-opt/c (f z)
(struct/dc s
[a (>=/c z)]
[b #:lazy (a) (f a)]))
[b (a) #:lazy (f a)]))
(s-a (s-b (contract (f 11)
(s 12 (s 13 #f))
@ -9227,7 +9237,7 @@
(define-opt/c (f z)
(struct/dc s
[a (>=/c z)]
[b #:lazy (a) (f a)]))
[b (a) #:lazy (f a)]))
(s-b (s-b (contract (f 11)
(s 12 (s 13 #f))
'pos
@ -9242,7 +9252,7 @@
(define-opt/c (g z)
(struct/dc s
[a (>=/c z)]
[b #:lazy (a) (>=/c (+ a 1))]))
[b (a) #:lazy (>=/c (+ a 1))]))
(s-a (contract (g 10)
(s 12 (s 14 #f))
@ -9258,7 +9268,7 @@
(define-opt/c (g z)
(struct/dc s
[a (>=/c z)]
[b #:lazy (a) (>=/c (+ a 1))]))
[b (a) #:lazy (>=/c (+ a 1))]))
(s-b (contract (g 10)
(s 12 14)
@ -9275,7 +9285,7 @@
(define-opt/c (g z)
(struct/dc s
[a (>=/c z)]
[b #:lazy (a) (>=/c (+ a 1))]))
[b (a) #:lazy (>=/c (+ a 1))]))
(s-b (contract (g 11)
(s 12 10)
@ -9291,7 +9301,7 @@
(or/c not
(struct/dc kons
[hd (unknown-function a)]
[tl #:lazy () (or/c #f (f b a))])))
[tl () #:lazy (or/c #f (f b a))])))
(kons-hd (kons-tl (contract (f 1 2)
(kons 1 (kons 2 #f))
'pos
@ -9344,7 +9354,7 @@
(struct s (q a))
(contract (struct/dc s
[q integer?]
[a #:lazy (q) (<=/c a)])
[a (q) #:lazy (<=/c q)])
(s 1 #f)
'pos
'neg)))
@ -9367,6 +9377,44 @@
(struct/dc s [a (new-∃/c 'α)]))
exn:fail?)
(test/pos-blame
'struct/dc-new1
'(let ()
(struct s (a))
(contract (struct/dc s [a integer?]) (s #f) 'pos 'neg)))
(test/spec-passed
'struct/dc-new2
'(let ()
(struct s (a))
(contract (struct/dc s [a #:lazy integer?]) (s #f) 'pos 'neg)))
(test/pos-blame
'struct/dc-new3
'(let ()
(struct s (a))
(contract (s-a (struct/dc s [a #:lazy integer?])) (s #f) 'pos 'neg)))
(test/spec-passed
'struct/dc-new4
'(let ()
(struct s ([a #:mutable]))
(contract (struct/dc s [a integer?]) (s #f) 'pos 'neg)))
(test/pos-blame
'struct/dc-new5
'(let ()
(struct s ([a #:mutable]))
(contract (s-a (struct/dc s [a integer?])) (s #f) 'pos 'neg)))
(test/neg-blame
'struct/dc-new6
'(let ()
(struct s ([a #:mutable]))
(set-s-a! (contract (struct/dc s [a integer?]) (s 1) 'pos 'neg)
#f)))
;
;
;
@ -10552,18 +10600,17 @@ so that propagation occurs.
(ctest #t chaperone-contract? (let ()
(struct s (a b))
(struct/dc s [a integer?] [b integer?])))
(ctest #f flat-contract? (let ()
(ctest #t flat-contract? (let ()
(struct s (a b))
(struct/dc s [a integer?] [b integer?])))
(ctest #f flat-contract? (let ()
(ctest #t flat-contract? (let ()
(struct s (a b))
(struct/dc s [a integer?] [b (a) (>=/c a)])))
(ctest #t chaperone-contract? (let ()
(struct s (a b))
(struct/dc s [a integer?] [b (a) (>=/c a)])))
(test-flat-contract '(and/c number? integer?) 1 3/2)
(test-flat-contract '(not/c integer?) #t 1)
(test-flat-contract '(=/c 2) 2 3)
(test-flat-contract '(>/c 5) 10 5)
@ -12062,6 +12109,24 @@ so that propagation occurs.
(letrec ([f (λ (x) 'not-f)])
((contract ctc f 'pos 'neg) 1)))))
(ctest '("the a field of")
extract-context-lines
(λ ()
(struct s (a b))
(contract (struct/dc s [a (b) (<=/c b)] [b integer?])
(s 2 1)
'pos
'neg)))
(ctest '("the a field of")
extract-context-lines
(λ ()
(struct s (a b))
(contract (struct/dc s [a (<=/c 1)] [b integer?])
(s 2 1)
'pos
'neg)))
#;
(ctest '("an element of" "the 3rd element of")
extract-context-lines