Add support for collapsible contracts (#2367)

* Add support for space-efficient vector and arrow contracts.

When an eleventh contract would be applied to a function or vector,
switch representation for the wrapper and try eliding redundant
checks. The resulting value keeps a constant number of
chaperone/impersonator wrappers regardless of the number of contracts
applied to it, and won't run any (provably) redundant checks.

This avoids a pathological case where, e.g., a function crosses a
boundary inside a loop, and gets wrapped N times (or worse, 2^N).

The optimization for function contracts currently only applies for
fixed-arity functions and contracts, and only for functions with known
result-arity of 1. These limitations are not fundamental.

Checking specific checks is not as optimized as for regular arrow
contracts yet. (Specifically: arity-specific wrappers and
tail-marks-match support is missing.) Again, not a fundamental
limitation.

Further described in the OOPSLA 2018 Paper: "Collapsible Contracts: Fixing a Pathology of Gradual Typing"

In collaboration with Ben Greenman, Christophe Scholliers, Robby Findler, and Vincent St-Amour.
This commit is contained in:
Dan Feltey 2018-12-13 14:58:56 -06:00 committed by GitHub
parent 15d0ccc2c0
commit a0fdee59b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
40 changed files with 5081 additions and 479 deletions

View File

@ -31,6 +31,7 @@ constraints.
racket/contract/private/guts
racket/contract/private/prop
racket/contract/private/blame
racket/contract/collapsible
racket/contract/private/ds
racket/contract/private/opt
racket/contract/private/basic-opters
@ -2012,14 +2013,15 @@ The @racket[define-struct/contract] form only allows a subset of the
pos-blame-party
source-loc
name-for-blame
no-context)
context-limit)
#:grammar ([pos-blame-party (code:line)
(code:line #:pos-source pos-source-expr)]
[source-loc (code:line)
(code:line #:srcloc srcloc-expr)]
[name-for-blame (code:line)
(code:line #:name-for-blame blame-id)]
[name-for-blame (code:line)
[name-for-blame
(code:line)
#:name-for-blame blame-id]
[context-limit (code:line)
(code:line #:context-limit limit-expr)])]{
Defines @racket[id] to be @racket[orig-id], but with the contract
@racket[contract-expr].
@ -2133,6 +2135,10 @@ accepted by the third argument to @racket[datum->syntax].
late-neg-proj
(or/c #f (-> blame? (-> any/c any/c any/c)))
#f]
[#:collapsible-late-neg-projection
collapsible-late-neg-proj
(or/c #f (-> blame? (values (-> any/c any/c any/c) collapsible-contract?)))
#f]
[#:val-first-projection
val-first-proj
(or/c #f (-> blame? (-> any/c (-> any/c any/c))))
@ -2161,6 +2167,10 @@ accepted by the third argument to @racket[datum->syntax].
late-neg-proj
(or/c #f (-> blame? (-> any/c any/c any/c)))
#f]
[#:collapsible-late-neg-projection
collapsible-late-neg-proj
(or/c #f (-> blame? (values (-> any/c any/c any/c) collapsible-contract?)))
#f]
[#:val-first-projection
val-first-proj
(or/c #f (-> blame? (-> any/c (-> any/c any/c))))
@ -2189,6 +2199,10 @@ accepted by the third argument to @racket[datum->syntax].
late-neg-proj
(or/c #f (-> blame? (-> any/c any/c any/c)))
#f]
[#:collapsible-late-neg-projection
collapsible-late-neg-proj
(or/c #f (-> blame? (values (-> any/c any/c any/c) collapsible-contract?)))
#f]
[#:val-first-projection
val-first-proj
(or/c #f (-> blame? (-> any/c (-> any/c any/c))))
@ -2213,7 +2227,7 @@ accepted by the third argument to @racket[datum->syntax].
)]{
These functions build simple higher-order contracts, @tech{chaperone contracts},
and @tech{flat contracts}, respectively. They both take the same set of three
and @tech{flat contracts}, respectively. They all take the same set of three
optional arguments: a name, a first-order predicate, and a blame-tracking projection.
For @racket[make-flat-contract], see also @racket[flat-contract-with-explanation].
@ -2242,6 +2256,14 @@ The @racket[late-neg-proj] argument defines the behavior of applying
contract), or signal a contract violation using @racket[raise-blame-error].
The default is @racket[#f].
The @racket[collapsible-late-neg-proj] argument takes the place of the
@racket[late-neg-proj] argument for contracts that support collapsing.
If it is supplied, this argument accepts a @tech{blame object} that is
missing one party. It must return two values. The first value must be
a function that accepts both the value that is getting the contract and
the name of the missing blame party, in that order. The second value should
be a collapsible representation of the contract.
The projection @racket[proj] and @racket[val-first-proj] are older mechanisms for
defining the behavior of applying the contract. The @racket[proj] argument
is a curried function of two arguments: the first application accepts a blame
@ -2322,7 +2344,8 @@ to determine if this is a contract that accepts only @racket[list?] values.
]
@history[#:changed "6.0.1.13" @list{Added the @racket[#:list-contract?] argument.}
#:changed "6.90.0.30" @list{Added the @racket[#:equivalent] argument.}]
#:changed "6.90.0.30" @list{Added the @racket[#:equivalent] argument.}
#:changed "7.1.0.10" @list{Added the @racket[#:collapsible-late-neg-projection] argument.}]
}
@defproc[(build-compound-type-name [c/s any/c] ...) any]{
@ -2562,7 +2585,6 @@ when @racket[context] is @racket[#f].
an error message, if @racket[blame] is passed to @racket[raise-blame-error].
}
@deftogether[(
@defproc[(blame-positive [b blame?]) any/c]
@defproc[(blame-negative [b blame?]) any/c]
@ -2741,6 +2763,10 @@ returns @racket[#f] but @racket[value-blame] returns @racket[#f].
late-neg-proj
(or/c #f (-> contract? (-> blame? (-> any/c any/c any/c))))
#f]
[#:collapsible-late-neg-projection
collapsible-late-neg-proj
(or/c #f (-> contract? (-> blame? (values (-> any/c any/c any/c) collapsible-contract?))))
#f]
[#:val-first-projection
val-first-proj
(or/c #f (-> contract? blame? (-> any/c (-> any/c any/c))))
@ -2787,6 +2813,10 @@ returns @racket[#f] but @racket[value-blame] returns @racket[#f].
late-neg-proj
(or/c #f (-> contract? (-> blame? (-> any/c any/c any/c))))
#f]
[#:collapsible-late-neg-projection
collapsible-late-neg-proj
(or/c #f (-> contract? (-> blame? (values (-> any/c any/c any/c) collapsible-contract?))))
#f]
[#:val-first-projection
val-first-proj
(or/c #f (-> contract? blame? (-> any/c (-> any/c any/c))))
@ -2843,6 +2873,10 @@ returns @racket[#f] but @racket[value-blame] returns @racket[#f].
late-neg-proj
(or/c #f (-> contract? (-> blame? (-> any/c any/c any/c))))
#f]
[#:collapsible-late-neg-projection
collapsible-late-neg-proj
(or/c #f (-> contract? (-> blame? (values (-> any/c any/c any/c) collapsible-contract?))))
#f]
[#:val-first-projection
val-first-proj
(or/c #f (-> contract? blame? (-> any/c (-> any/c any/c))))
@ -2901,6 +2935,10 @@ a contract. It is specified in terms of seven properties:
defining the behavior of the contract (The @racket[get-projection]
and @racket[val-first-proj] arguments also specify the projection,
but using a different signature. They are here for backwards compatibility.);}
@item{@racket[collapsible-late-neg-proj], similar to @racket[late-neg-proj]
which produces a blame-tracking projection defining the behavior of the
contract, this function additionally specifies the collapsible behavior
of the contract;}
@item{@racket[stronger], a predicate that determines whether this
contract (passed in the first argument) is stronger than some other
contract (passed in the second argument) and whose default always
@ -2922,8 +2960,9 @@ a contract. It is specified in terms of seven properties:
to determine if this contract accepts only @racket[list?]s.}
]
At least one of the @racket[late-neg-proj], @racket[get-projection],
@racket[val-first-proj], or @racket[get-first-order] must be non-@racket[#f].
At least one of the @racket[late-neg-proj], @racket[collapsible-late-neg-proj],
@racket[get-projection], @racket[val-first-proj], or @racket[get-first-order]
must be non-@racket[#f].
These accessors are passed as (optional) keyword arguments to
@racket[build-contract-property], and are applied to instances of the
@ -2952,7 +2991,8 @@ arguments as @racket[build-contract-property]. The differences are:
#:changed "6.1.1.4"
@list{Allow @racket[generate] to return @racket[contract-random-generate-fail].}
#:changed "6.90.0.30"
@list{Added the @racket[#:equivalent] argument.}]
@list{Added the @racket[#:equivalent] argument.}
#:changed "7.1.0.10" @list{Added the @racket[#:collapsible-late-neg-projection] argument.}]
}
@deftogether[(
@ -3356,6 +3396,8 @@ and fix at some point, but have no concrete plans currently.
Key used by continuation marks that are present during contract checking.
The value of these marks are the @|blame-objects| that correspond to the contract
currently being checked.
@history[#:added "6.4.0.4"]
}
@defproc[(contract-custom-write-property-proc [c contract?]
@ -3462,6 +3504,160 @@ add contracts to libraries that @racketmodname[racket/contract]
uses to implement some of the more sophisticated
parts of the contract system.
@; ------------------------------------------------------------------------
@section[#:tag "collapsible"]{Collapsible Contracts}
@defmodule*/no-declare[(racket/contract/collapsible)]
@declare-exporting-ctc[racket/contract/collapsible]
@history[#:added "7.1.0.10"]
@deftech{Collapsible contracts} are an optimization in the contract system designed
to avoid a particular pathological build up of contract wrappers on higher-order
values. The @racket[vectorof], @racket[vector/c], and @racket[->] contract
combinators support collapsing for vector contracts and function contracts for
functions returning a single value.
@bold{Warning}: the features described in this section are experimental
and may not be sufficient to implement new collapsible contracts. Implementing
new collapsible contracts requires the use of unsafe chaperones and impersonators
which are only supported for vector and procedure values. This documentation exists
primarily to allow future maintenance of the @racket[racket/contract/collapsible] library/
@defproc[(get/build-collapsible-late-neg-projection [c contract?])
(-> blame? (values (-> any/c any/c any/c) collapsible-contract?))]{
Returns the @racket[_collapsible-late-neg] projection for @racket[c].
If @racket[c] does not have a @racket[_collapsible-late-neg] projection,
then this function uses the original projection for it and constructs a leaf
as its collapsible representation.
}
@defthing[collapsible-contract-continuation-mark-key continuation-mark-key?]{
Key used by continuation marks that are present during collapsible contract checking.
The value of these marks are @racket[#t] if the current contract is collapsible.
}
@defform[(with-collapsible-contract-continuation-mark body ...)]{
Inserts a continuation mark that informs the contract profiler that the current contract
is collapsible.
}
@defthing[prop:collapsible-contract struct-type-property?]{
Structures implementing this property are usable as collapsible contracts. The value
associated with this property should be constructed by calling
@racket[build-collapsible-contract-property].
}
@defproc[(collapsible-contract? [v any/c]) boolean?]{
A predicate recognizing structures with the @racket[prop:collapsible-contract] property.}
@defproc[(merge [new-cc collapsible-contract?]
[new-neg any/c]
[old-cc collapsible-contract?]
[old-neg any/c])
collapsible-contract?]{
Combine two collapsible contracts into a single collapsible contract.
The @racket[new-neg] and @racket[old-neg] arguments are expected to be
blame parties similar to those passed to a @tech{late neg projection}.
}
@defproc[(collapsible-guard [cc collapsible-contract?]
[val any/c]
[neg-party any/c])
any/c]{
Similar to a @tech{late neg projection}, this function guards the value @racket[val]
with the collapsible contract @racket[cc].
}
@defproc[(collapsible-contract-property? [v any/c]) boolean?]{
This predicate indicates that a value can be used as the property for
@racket[prop:collapsible-contract].
}
@defproc[(build-collapsible-contract-property
[#:try-merge try-merge
(or/c #f
(-> collapsible-contract?
any/c
collapsible-contract?
any/c
(or/c #f collapsible-contract?)))
#f]
[#:collapsible-guard collapsible-guard
(-> collapsible-contract? any/c any/c any/c)
(λ (cc v neg)
(error
"internal error: contract does not support `collapsible-guard`" ctc))])
collapsible-contract-property?]{
Constructs a @deftech{collapsible contract property} from a merging function and a guard.
The @racket[try-merge] argument is similar to @racket[merge], but may return @racket[#f] instead
of a collapsible contract and may be specialized to a particular collapsible contract.
The @racket[collapsible-guard] argument should be specialized to the particular collapsible
contract being implemented.
}
@defstruct*[collapsible-ho/c
([latest-blame blame?]
[missing-party any/c]
[latest-ctc contract?])]{
A common parent structure for collapsible contracts for higher-order values.
The @racket[latest-blame] field holds the blame object for the most recent
contract attached. Similarly, the @racket[missing-party] filed holds the latest
missing party passed to the contract. The @racket[latest-contract] field stores
the most recent contract attached to the value.
}
@defstruct*[collapsible-leaf/c
([proj-list (listof (-> any/c any/c any/c))]
[contract-list (listof contract?)]
[blame-list (listof blame?)]
[missing-party-list (listof any/c)])]{
A structure representing the leaf nodes of a collapsible contract. The @racket[proj-list]
field holds a list of partially applied @tech{late neg projections}. The @racket[contract-list],
@racket[blame-list], and @racket[missing-party-list] fields hold a list of contracts,
blame objects, and blame missing parties respectively.
}
@deftogether[(@defthing[impersonator-prop:collapsible impersonator-property?]
@defproc[(has-impersonator-prop:collapsible? [v any/c]) boolean?]
@defproc[(get-impersonator-prop:collapsible [v any/c]) collapsible-property?])]{
An impersonator property (and its accessors) that should be attached to chaperoned or impersonated
values that are guarded with a collapsible contract.
}
@defstruct*[collapsible-property ([c-c collapsible-contract?]
[neg-party any/c]
[ref (or/c #f impersonator?)])]{
The parent struct of properties that should be attached to chaperones or impersonators
of values protected with a collapsible contract. The @racket[c-c] field stores the collapsible
contract that is or will in the future be attached to the the value. The @racket[neg-party] field
stores the latest missing blame party passed to the contract on the value. The @racket[ref] field
is mutable and stores a reference to the chaperone or impersonator to which this property is
attached. This is necessary to determine whether an unknown chaperone has been attached to a value
after it has been protected by a collapsible contract.
}
@defstruct*[(collapsible-count-property collapsible-property)
([count natural-number/c]
[prev (or/c collapsible-count-property? any/c)])]{
This property is associated with the @racket[impersonator-prop:collapsible] property before
the value completely enters the collapsible mode. These properties keep track of the number of
contracts on a value in the @racket[_count] field, and hold a reference to the previous
@deftech{count property} in the @racket[prev] field or the original value without a contract. This
allows the contract system to traverse the chain of attached contracts and merge them into a single
collapsible contract to protect the original value.
}
@defstruct*[(collapsible-wrapper-property collapsible-property)
([checking-wrapper impersonator?])]{
This property is used when a value is guarded by a collapsible contract. The
@racket[checking-wrapper] field holds a chaperone or impersonator that dispatches to the
collapsible contract stored in this property to perform any necessary contract checks. When
the value receives another contract and merging happens, the checking wrapper will remain the
same even though the specific collapsible contract attached to the value may change.
}
@; ------------------------------------------------------------------------
@section{Legacy Contracts}
@defproc[(make-proj-contract [name any/c]

View File

@ -5,14 +5,18 @@
scribble/examples
scribble/decode
racket/contract
racket/contract/collapsible
"../icons.rkt")
(provide (all-from-out scribble/manual)
(all-from-out scribble/examples)
(all-from-out racket/contract))
(all-from-out racket/contract)
(all-from-out racket/contract/collapsible))
(require (for-label racket))
(provide (for-label (all-from-out racket)))
(require (for-label racket/contract/collapsible))
(provide (for-label (all-from-out racket/contract/collapsible)))
(provide mz-examples)
(define mz-eval (make-base-eval))

View File

@ -800,7 +800,21 @@
'neg))
x)
'(body ctc)
'(body ctc ctc)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'->d-underscore2-double-wrap
'(let ([x '()])
((contract (->d () () [_ (begin (set! x (cons 'ctc x)) any/c)])
(contract
(->d () () [_ (begin (set! x (cons 'ctc x)) any/c)])
(λ () (set! x (cons 'body x)))
'pos 'neg)
'pos
'neg))
x)
'(body ctc ctc)
do-not-double-wrap)
(test/spec-passed/result
'->d-underscore3
@ -811,7 +825,21 @@
'neg))
x)
'(ctc body)
'(ctc ctc body)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'->d-underscore3-double-wrap
'(let ([x '()])
((contract (->d () () [res (begin (set! x (cons 'ctc x)) any/c)])
(contract
(->d () () [res (begin (set! x (cons 'ctc x)) any/c)])
(λ () (set! x (cons 'body x)))
'pos 'neg)
'pos
'neg))
x)
'(ctc ctc body)
do-not-double-wrap)
(test/spec-passed/result
'->d-underscore4

View File

@ -750,18 +750,48 @@
b)
(unbox b))
'(5 4 3 2 1)
'(5 4 5 4 3 2 1 2 1)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'->i44
'((contract (->i ([x () any/c])
[y any/c]
#:post (x) x)
(lambda (x) x)
'pos
'neg)
#t)
'#t)
'->i43-double-wrap
'(let ([b (box '())])
((contract (->i ([i (box/c (listof integer?))])
(values [_ (i)
(begin
(set-box! i (cons 1 (unbox i)))
(λ (x)
(set-box! i (cons 4 (unbox i)))
#t))]
[_ (i)
(begin
(set-box! i (cons 2 (unbox i)))
(λ (x)
(set-box! i (cons 5 (unbox i)))
#t))]))
(contract
(->i ([i (box/c (listof integer?))])
(values [_ (i)
(begin
(set-box! i (cons 1 (unbox i)))
(λ (x)
(set-box! i (cons 4 (unbox i)))
#t))]
[_ (i)
(begin
(set-box! i (cons 2 (unbox i)))
(λ (x)
(set-box! i (cons 5 (unbox i)))
#t))]))
(λ (i)
(set-box! i (cons 3 (unbox i)))
(values 2 2))
'pos 'neg)
(quote pos)
(quote neg))
b)
(unbox b))
'(5 4 5 4 3 2 1 2 1)
do-not-double-wrap)
(test/pos-blame
'->i45
@ -875,7 +905,31 @@
1)
x)
'(res-check res-eval body arg-eval)
'(res-check res-eval res-check res-eval body arg-eval arg-eval)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'->i48-double-wrap
'(let ([x '()])
((contract (->i ([arg (begin (set! x (cons 'arg-eval x)) integer?)])
[res () (begin
(set! x (cons 'res-eval x))
(λ (res)
(set! x (cons 'res-check x))))])
(contract
(->i ([arg (begin (set! x (cons 'arg-eval x)) integer?)])
[res () (begin
(set! x (cons 'res-eval x))
(λ (res)
(set! x (cons 'res-check x))))])
(λ (arg)
(set! x (cons 'body x)))
'pos 'neg)
'pos
'neg)
1)
x)
'(res-check res-eval res-check res-eval body arg-eval arg-eval)
do-not-double-wrap)
(test/spec-passed/result
'->i49
@ -892,8 +946,32 @@
1)
x)
'(res-check body res-eval arg-eval)
'(res-check res-check body res-eval res-eval arg-eval arg-eval)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'->i49-double-wrap
'(let ([x '()])
((contract (->i ([arg (begin (set! x (cons 'arg-eval x)) integer?)])
[_ () (begin
(set! x (cons 'res-eval x))
(λ (res)
(set! x (cons 'res-check x))))])
(contract
(->i ([arg (begin (set! x (cons 'arg-eval x)) integer?)])
[_ () (begin
(set! x (cons 'res-eval x))
(λ (res)
(set! x (cons 'res-check x))))])
(λ (arg)
(set! x (cons 'body x)))
'pos 'neg)
'pos
'neg)
1)
x)
'(res-check res-check body res-eval res-eval arg-eval arg-eval)
do-not-double-wrap)
(test/spec-passed/result
'->i50
'(let ([x '()])
@ -909,7 +987,31 @@
1)
x)
'(res-check body res-eval arg-eval)
'(res-check res-check body res-eval arg-eval res-eval arg-eval)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'->i50-double-wrap
'(let ([x '()])
((contract (->i ([arg (begin (set! x (cons 'arg-eval x)) integer?)])
[res (begin
(set! x (cons 'res-eval x))
(λ (res)
(set! x (cons 'res-check x))))])
(contract
(->i ([arg (begin (set! x (cons 'arg-eval x)) integer?)])
[res (begin
(set! x (cons 'res-eval x))
(λ (res)
(set! x (cons 'res-check x))))])
(λ (arg)
(set! x (cons 'body x)))
'pos 'neg)
'pos
'neg)
1)
x)
'(res-check res-check body res-eval arg-eval res-eval arg-eval)
do-not-double-wrap)
(test/spec-passed/result
'->i51
@ -926,7 +1028,31 @@
1)
x)
'(res-check body res-eval arg-eval)
'(res-check res-check body res-eval arg-eval res-eval arg-eval)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'->i51-double-wrap
'(let ([x '()])
((contract (->i ([arg (begin (set! x (cons 'arg-eval x)) integer?)])
[_ (begin
(set! x (cons 'res-eval x))
(λ (res)
(set! x (cons 'res-check x))))])
(contract
(->i ([arg (begin (set! x (cons 'arg-eval x)) integer?)])
[_ (begin
(set! x (cons 'res-eval x))
(λ (res)
(set! x (cons 'res-check x))))])
(λ (arg)
(set! x (cons 'body x)))
'pos 'neg)
'pos
'neg)
1)
x)
'(res-check res-check body res-eval arg-eval res-eval arg-eval)
do-not-double-wrap)
(test/spec-passed/result
'->i52
@ -965,9 +1091,25 @@
1 2)
b)
'(3 2 1)
do-not-double-wrap)
;; this is probably right (but not what we really really want, of course)
'(3 2 1 2 1))
(test/spec-passed/result
'->i55-double-wrap
'(let ([b '()])
((contract (->i ([y () (begin (set! b (cons 1 b)) any/c)]
[z (y) (begin (set! b (cons 2 b)) any/c)])
any)
(contract
(->i ([y () (begin (set! b (cons 1 b)) any/c)]
[z (y) (begin (set! b (cons 2 b)) any/c)])
any)
(λ args (set! b (cons 3 b)) 0)
'pos 'neg)
'pos 'neg)
1 2)
b)
'(3 2 1 2 1)
do-not-double-wrap)
(test/spec-passed/result
'->i56
@ -982,9 +1124,29 @@
1 2)
b)
'(5 4 3 2 1)
do-not-double-wrap)
;; this is probably right (but not what we really really want, of course)
'(5 4 5 4 3 2 1 2 1))
(test/spec-passed/result
'->i56-double-wrap
'(let ([b '()])
((contract (->i ([y () (begin (set! b (cons 1 b)) any/c)]
[z (y) (begin (set! b (cons 2 b)) any/c)])
(values
[a () (begin (set! b (cons 4 b)) any/c)]
[b (a) (begin (set! b (cons 5 b)) any/c)]))
(contract
(->i ([y () (begin (set! b (cons 1 b)) any/c)]
[z (y) (begin (set! b (cons 2 b)) any/c)])
(values
[a () (begin (set! b (cons 4 b)) any/c)]
[b (a) (begin (set! b (cons 5 b)) any/c)]))
(λ args (set! b (cons 3 b)) (values 0 0))
'pos 'neg)
'pos 'neg)
1 2)
b)
'(5 4 5 4 3 2 1 2 1)
do-not-double-wrap)
(test/spec-passed/result
'->i57
@ -1003,9 +1165,37 @@
1 2)
b)
'(9 8 7 6 5 4 3 2 1)
do-not-double-wrap)
;; this is probably right (but not what we really really want, of course)
'(9 8 7 6 9 8 7 6 5 4 3 2 1 4 3 2 1))
(test/spec-passed/result
'->i57-double-wrap
'(let ([b '()])
((contract (->i ([y () (begin (set! b (cons 1 b))
(λ (y) (set! b (cons 2 b)) #t))]
[z (y) (begin (set! b (cons 3 b))
(λ (y) (set! b (cons 4 b)) #t))])
(values
[a () (begin (set! b (cons 6 b))
(λ (a) (set! b (cons 7 b)) #t))]
[b (a) (begin (set! b (cons 8 b))
(λ (a) (set! b (cons 9 b)) #t))]))
(contract
(->i ([y () (begin (set! b (cons 1 b))
(λ (y) (set! b (cons 2 b)) #t))]
[z (y) (begin (set! b (cons 3 b))
(λ (y) (set! b (cons 4 b)) #t))])
(values
[a () (begin (set! b (cons 6 b))
(λ (a) (set! b (cons 7 b)) #t))]
[b (a) (begin (set! b (cons 8 b))
(λ (a) (set! b (cons 9 b)) #t))]))
(λ args (set! b (cons 5 b)) (values 0 0))
'pos 'neg)
'pos 'neg)
1 2)
b)
'(9 8 7 6 9 8 7 6 5 4 3 2 1 4 3 2 1)
do-not-double-wrap)
(test/spec-passed/result
'->i58
@ -1028,7 +1218,26 @@
'pos 'neg))
b)
'(3 2 1)
'(3 2 1 2 1))
do-not-double-wrap)
(test/spec-passed/result
'->i59-double-wrap
'(let ([b '()])
((contract (->i ()
([x (begin (set! b (cons 1 b)) integer?)]
[y (x) (begin (set! b (cons 'nope b)) (>=/c x))])
[result (begin (set! b (cons 2 b)) any/c)])
(contract
(->i ()
([x (begin (set! b (cons 1 b)) integer?)]
[y (x) (begin (set! b (cons 'nope b)) (>=/c x))])
[result (begin (set! b (cons 2 b)) any/c)])
(λ ([x #f] [y #f]) (set! b (cons 3 b)) 0)
'pos 'neg)
'pos 'neg))
b)
'(3 2 1 2 1)
do-not-double-wrap)
(test/pos-blame
'->i-arity1
@ -1448,7 +1657,21 @@
'neg))
x)
'(body ctc)
'(body ctc ctc)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'->i-underscore2-double-wrap
'(let ([x '()])
((contract (->i () () [_ (begin (set! x (cons 'ctc x)) any/c)])
(contract
(->i () () [_ (begin (set! x (cons 'ctc x)) any/c)])
(λ () (set! x (cons 'body x)))
'pos 'neg)
'pos
'neg))
x)
'(body ctc ctc)
do-not-double-wrap)
(test/spec-passed/result
'->i-underscore3
@ -1459,7 +1682,21 @@
'neg))
x)
'(body ctc)
'(body ctc ctc)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'->i-underscore3-double-wrap
'(let ([x '()])
((contract (->i () () [res (begin (set! x (cons 'ctc x)) any/c)])
(contract
(->i () () [res (begin (set! x (cons 'ctc x)) any/c)])
(λ () (set! x (cons 'body x)))
'pos 'neg)
'pos
'neg))
x)
'(body ctc ctc)
do-not-double-wrap)
(test/spec-passed/result
'->i-underscore4
@ -1487,7 +1724,22 @@
11)
x)
'(body ctc)
'(body ctc ctc)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'->i-underscore6-double-wrap
'(let ([x '()])
((contract (->i ([a integer?]) () [_ (a) (begin (set! x (cons 'ctc x)) any/c)])
(contract
(->i ([a integer?]) () [_ (a) (begin (set! x (cons 'ctc x)) any/c)])
(λ (a) (set! x (cons 'body x)))
'pos 'neg)
'pos
'neg)
11)
x)
'(body ctc ctc)
do-not-double-wrap)
(test/pos-blame
'->i-bad-number-of-result-values1

View File

@ -3,7 +3,8 @@
(parameterize ([current-contract-namespace
(make-basic-contract-namespace 'racket/contract
'racket/contract/private/blame)])
'racket/contract/private/blame
'syntax/srcloc)])
(test/spec-passed/result
'blame-selector.1
@ -371,6 +372,16 @@
'(has-complete-blame? (contract (vectorof integer?) (vector 1 2 3) 'pos 'neg))
#t)
(test/spec-passed/result
'complete-prop-blame-vector/c
'(let* ([ctc (vector/c (-> integer? integer?))]
[v (contract
ctc
(contract ctc (vector add1) 'inner-pos 'inner-neg)
'pos 'neg)])
(has-complete-blame? (vector-ref v 0)))
#t)
(test/spec-passed/result
'blame-selectors
'(let ()
@ -394,10 +405,12 @@
(λ (val np)
val)))
'whatevs
'pos 'neg)
'pos 'neg
'there-is-no-name
(build-source-location #f))
(list source pos neg ctc val orig? swapped?))
(list (srcloc #f #f #f #f #f)
'pos #f 'blame-selector-helper #f #t #f))
'pos #f 'blame-selector-helper 'there-is-no-name #t #f))
(test/spec-passed/result
'swapped-blame-selectors
@ -425,10 +438,12 @@
any))
(contract the-ctc
(λ (x) 'whatevs)
'pos 'neg)
'pos 'neg
'there-is-no-name
(build-source-location #f))
(list source pos neg ctc val orig? swapped?))
(list (srcloc #f #f #f #f #f)
#f 'pos '(-> blame-selector-helper any) #f #f #t))
#f 'pos '(-> blame-selector-helper any) 'there-is-no-name #f #t))
(test/spec-passed/result
'blame-equality

View File

@ -0,0 +1,929 @@
#lang racket/base
(require "test-util.rkt")
(parameterize ([current-contract-namespace
(make-basic-contract-namespace
'racket/contract)])
(contract-eval
'(define (add-many-contracts n ctc val [pos 'pos] [neg 'neg])
(for/fold ([val val])
([i (in-range n)])
(contract ctc val pos neg))))
(contract-eval '(define ctc (-> (-> integer? integer?) (-> integer? integer?))))
(contract-eval '(define (wrap x) (add-many-contracts 11 ctc x 'pos 'neg)))
(contract-eval '(define id (wrap (wrap (lambda (x) x)))))
(test/spec-passed
'collapsible1
'(id add1))
(test/spec-passed
'collapsible2
'((id add1) 1))
(test/spec-failed
'collapsible3
'((id add1) 'a)
'neg)
(test/spec-passed
'collapsible4
'(((wrap id) add1) 1))
(test/spec-failed
'collapsible5
'(((wrap id) add1) 'a)
'neg)
(test/spec-passed
'collapsible6
'(((wrap (wrap (wrap (wrap (wrap (wrap (wrap (wrap (wrap id))))))))) add1) 1))
(test/spec-failed
'collapsible7
'(wrap 3)
'pos)
;; works with non-flat contracts at the leaves
(test/spec-passed
'collapsible8
'(let ([ctc (-> (vector/c integer?) (vector/c integer?))])
(vector-ref ((contract ctc
(add-many-contracts 11 ctc
(lambda (x) x)
'inner-pos 'inner-neg)
'pos 'neg)
(vector 1))
0)))
(test/spec-failed
'collapsible9
'(vector-ref ((contract ctc
(add-many-contracts 11 ctc
(lambda (x) x)
'inner-pos 'inner-neg)
'pos 'neg)
(vector 'a))
0)
'neg)
(test/spec-failed
'collapsible10
'(let ([ctc (-> any/c (-> integer? integer?))])
(vector-ref ((contract ctc
(add-many-contracts 11 ctc
(lambda (x) x)
'inner-pos 'inner-neg)
'pos 'neg)
(vector 'a))
0))
"inner-pos")
(contract-eval '(require (submod racket/contract/private/arrow-collapsible for-testing)))
(contract-eval '(require (submod racket/contract/private/collapsible-common for-testing)))
(contract-eval
'(define (has-num-contracts? f dom rng)
(unless (has-impersonator-prop:collapsible? f)
(error "has-num-contracts?: no collapsible contract"))
(define collapsible/c (collapsible-property-c-c (get-impersonator-prop:collapsible f)))
(define domain/c (car (collapsible->-doms collapsible/c)))
(define range/c (collapsible->-rng collapsible/c))
(unless (= (length (collapsible-leaf/c-proj-list domain/c)) dom)
(error "has-num-contracts?: wrong number of domain projections"))
(unless (= (length (collapsible-leaf/c-proj-list range/c)) rng)
(error "has-num-contracts?: wrong number of range projections"))
(unless (= (length (collapsible-leaf/c-contract-list domain/c)) dom)
(error "has-num-contracts?: wrong num of domain contracts"))
(unless (= (length (collapsible-leaf/c-contract-list range/c)) rng)
(error "has-num-contracts?: wrong num of range contracts"))))
(contract-eval '(define (collapsible? val)
(and (has-impersonator-prop:collapsible? val)
(let ([prop (get-impersonator-prop:collapsible val #f)])
(and (collapsible-wrapper-property? prop)
(eq? val (collapsible-property-ref prop)))))))
(contract-eval '(define pos (flat-named-contract
'pos
(lambda (x) (and (integer? x) (>= x 0))))))
(contract-eval '(define pos->pos (-> pos pos)))
(contract-eval '(define pos->pos->pos (-> pos->pos pos)))
(contract-eval
'(define guarded
(add-many-contracts 11 pos->pos (lambda (x) (* x -2)) 'positive 'negative)))
(contract-eval
'(define f1 (add-many-contracts 11 pos->pos->pos
(flat-named-contract
'c
(lambda (f)
(unless (has-contract? f)
(error "f1 should already be contracted"))
;; Check that the already contracted function only
;; has one contract
(has-num-contracts? f 1 1)
(f 1)))
'pos 'neg)))
(contract-eval
'(define f2 (add-many-contracts 11 pos->pos->pos
(lambda (f)
(unless (has-contract? f)
(error "f2 should already be contracted"))
;; Check that the already contracted function only
;; has one contract
(has-num-contracts? f 1 1)
(f -1))
'pos 'neg)))
(test/spec-failed
'collapsible11
'(f1 guarded)
"positive")
(test/spec-failed
'collapsible12
'(f2 guarded)
'pos)
;; check whether it has a contract (but not a collapsible wrapper)
(test-false
'collapsible12.5
'(collapsible->? (value-contract guarded)))
;; checking normal blame
(test/spec-failed
'collapsible13
'(guarded -34)
"negative")
(test/spec-failed
'collapsible14
'(guarded 34)
"positive")
(contract-eval
'(define guarded-twice (add-many-contracts 11 pos->pos guarded 'positive2 'negative2)))
;; Reapplying the same contract over the already contracted function
(test-true
'collapsible14.5
'(has-contract? guarded-twice))
;; Outer wrapper should be applied first for the domain
(test/spec-failed
'collapsible15
'(guarded-twice -34)
"negative2")
;; Inner wrapper should be applied first for the range
(test/spec-failed
'collapsible16
'(guarded-twice 34)
"positive")
;; Get the domain and range contract from the twice contracted function
(test/spec-passed
'collapsible16.1
'(has-num-contracts? guarded-twice 1 1))
(test-true
'collapsible16.5
'(collapsible? guarded-twice))
(contract-eval
'(define (contract-times f c n)
(if (= n 0)
f
(contract-times (contract c f 'positive 'negative) c (- n 1)))))
(test/spec-passed
'arrow-false-contracts
'(let* ([f (lambda (x) x)]
[ctc (-> #f #f)] ;; defeat opt/c rewriting
[cf1 (add-many-contracts 11 ctc f 'pos 'neg)]
[cf2 (contract ctc cf1 'pos 'neg)]
[cf3 (contract ctc cf2 'pos 'neg)]
[cf4 (contract ctc cf3 'pos 'neg)])
(has-num-contracts? cf4 1 1)))
(test/spec-passed
'arrow-many-false-contracts
'(let ([ctc (-> #f #f)])
(has-num-contracts? (contract-times (lambda (x) x) ctc 1000) 1 1)))
;; Apply the contract 1000 times
(contract-eval
'(define insanely-contracted (contract-times guarded-twice pos->pos 1000)))
(test/spec-passed
'collapsible-wrap0
'(has-num-contracts? insanely-contracted 1 1))
;; not actually doubly-wrapped
(contract-eval
'(define (double-wrapped? x)
(define prop (get-impersonator-prop:collapsible x #f))
(and
(collapsible-wrapper-property? prop)
(and (has-impersonator-prop:collapsible?
(collapsible-wrapper-property-checking-wrapper prop))
;; this is annoying because of how unsafe-chaperones ...
;; work in relation to impersonator-properties
(collapsible-wrapper-property?
(get-impersonator-prop:collapsible
(collapsible-wrapper-property-checking-wrapper prop)
#f))))))
(test-false
'collapsible-wrap1
'(double-wrapped? insanely-contracted))
(test-true
'collapsible-wrap2
'(collapsible? insanely-contracted))
(test-false
'collapsible-wrap3
'(double-wrapped? (id add1)))
(test-true
'collapsible-wrap4
'(collapsible? (id add1)))
(test-false
'collapsible-wrap5
'(double-wrapped? (id (id add1))))
(test-true
'collapsible-wrap6
'(collapsible? (id (id add1))))
(test-false
'collapsible-wrap7
'(double-wrapped? (id (id (id add1)))))
(test-true
'collapsible-wrap7
'(collapsible? (id (id (id add1)))))
;; test relying on contract-stronger?
(contract-eval '(define r-i (contract (-> integer? any/c)
(add-many-contracts 11 (-> integer? integer?)
add1
'inner-pos 'inner-neg)
'pos 'neg)))
(contract-eval '(define r-i2 (contract (-> integer? any/c)
(add-many-contracts 11 (-> integer? integer?)
(lambda (x) 'a)
'inner-pos 'inner-neg)
'pos 'neg)))
(test/spec-passed
'collapsible-stronger-num1
'(has-num-contracts? r-i 1 1))
(test-true
'collapsible-stronger1
'(collapsible? r-i))
(test/spec-passed
'collapsible-stronger-num2
'(has-num-contracts? r-i2 1 1))
(test-true
'collapsible-stronger2
'(collapsible? r-i2))
(test/spec-passed
'collapsible17
'(r-i 1))
(test/spec-failed
'collapsible18
'(r-i 'a)
'neg)
(test/spec-failed
'collapsible19
'(r-i2 1)
"inner-pos")
(contract-eval '(define i-r (contract (-> integer? integer?)
(add-many-contracts 11 (-> integer? any/c)
add1
'inner-pos 'inner-neg)
'pos 'neg)))
(contract-eval '(define i-r2 (contract (-> integer? integer?)
(add-many-contracts 11 (-> integer? any/c)
(lambda (x) 'a)
'inner-pos 'inner-neg)
'pos 'neg)))
;; can't collapse those. any/c must still be checked before integer? on the
;; way out, otherwise may blame wrong
(test/spec-passed
'collapsible-stronger-num3
'(has-num-contracts? i-r 1 2))
(test/spec-passed
'collapsible-stronger-num4
'(has-num-contracts? i-r2 1 2))
(test-true
'collapsible-stronger3
'(collapsible? i-r))
(test-true
'collapsible-stronger4
'(collapsible? i-r2))
(test/spec-passed
'collapsible20
'(i-r 1))
(test/spec-failed
'collapsible21
'(i-r 'a)
'neg)
(test/spec-failed
'collapsible22
'(i-r2 1)
'pos)
;; test mixing chaperone and impersonator contracts
(contract-eval
'(define c1 ; this is an impersonator contract
(make-contract
#:name 'c1
#:val-first-projection
(lambda (blame)
(lambda (x)
(lambda (neg-party)
(unless (integer? x)
(raise-blame-error (blame-add-missing-party blame neg-party) x "eh"))
(add1 x))))))) ; does not respect the chaperone property
(contract-eval
'(define c2 ; this is an chaperone contract
(make-chaperone-contract
#:name 'c2
#:val-first-projection
(lambda (blame)
(lambda (x)
(lambda (neg-party)
(unless (integer? x)
(raise-blame-error (blame-add-missing-party blame neg-party) x "eh"))
x))))))
(contract-eval
'(define (can-combine? val ctc)
(define cv (contract ctc val 'p 'n))
(and (collapsible? val)
(collapsible? cv))))
(contract-eval '(define ic
(contract (-> (-> c1 c1) (-> c1 c1))
(add-many-contracts 11 (-> (-> c2 c2) (-> c2 c2))
(lambda (x) x)
'inner-pos 'inner-neg)
'pos 'neg)))
(contract-eval '(define iic
(add-many-contracts 11 (-> (-> c1 c1) (-> c1 c1))
ic
'outer-pos 'outer-neg)))
(contract-eval
'(define imp-add1
(impersonate-procedure add1 (lambda (x) x))))
(contract-eval
'(define chap-add1
(chaperone-procedure add1 (lambda (x) x))))
(test-true
'collapsible-imps-on-underlying-chap
'(collapsible? (add-many-contracts 11 (-> c1 c1) chap-add1)))
(test-true
'collapsible-chaps-on-underlying-chap
'(collapsible? (add-many-contracts 11 (-> c2 c2) chap-add1)))
(test-true
'collapsible-imps-on-underlying-imp
'(collapsible? (add-many-contracts 11 (-> c1 c1) imp-add1)))
(test-true
'collapsible-chaps-on-underlying-imp
'(collapsible? (add-many-contracts 11 (-> c2 c2) imp-add1)))
(test-false
'collapsible-chap+imp1
'(can-combine? ic (-> c1 c1))) ; can collapse impersonators, the inner chaperone is not chaperone*
(test-false
'collapsible-chap+imp2
'(can-combine? ic (-> c2 c2)))
(test-false
'collapsible-chap+imp3
'(can-combine? iic (-> c1 c1))) ; see above
(test-false
'collapsible-chap+imp4
'(can-combine? iic (-> c2 c2)))
(test/spec-passed
'collapsible23
'((iic add1) 1))
(contract-eval '(define cc
(contract (-> (-> c2 c2) (-> c2 c2))
(add-many-contracts 11 (-> (-> c2 c2) (-> c2 c2))
(lambda (x) x)
'inner-pos 'inner-neg)
'pos 'neg)))
(contract-eval '(define icc
(add-many-contracts 11 (-> (-> c1 c1) (-> c1 c1))
cc
'outer-pos 'outer-neg)))
(test-true
'collapsible-chap+imp5
'(collapsible? cc))
(test-false
'collapsible-chap+imp6
'(can-combine? cc (-> c1 c1)))
(test-true
'collapsible-chap+imp7
'(can-combine? cc (-> c2 c2)))
(test-false
'collapsible-chap+imp8
'(can-combine? icc (-> c1 c1)))
(test-false
'collapsible-chap+imp9
'(can-combine? icc (-> c2 c2)))
(test/spec-passed
'collapsible24
'((icc add1) 1))
(contract-eval '(define ci (contract (-> (-> c2 c2) (-> c2 c2))
(add-many-contracts 11 (-> (-> c1 c1) (-> c1 c1))
(lambda (x) x)
'inner-pos 'inner-neg)
'pos 'neg)))
(contract-eval '(define ici (add-many-contracts 11 (-> (-> c1 c1) (-> c1 c1))
ci
'outer-pos 'outer-neg)))
(test-false
'collapsible-chap+imp10
'(can-combine? ci (-> c1 c1)))
(test-false
'collapsible-chap+imp11
'(can-combine? ci (-> c2 c2))) ; it's impersonated before the `cc`, but not impersonator*, sook
(test-false
'collapsible-chap+imp12
'(can-combine? ici (-> c1 c1))) ; ditto
(test-false
'collapsible-chap+imp13
'(can-combine? ici (-> c2 c2)))
(test/spec-passed
'collapsible25
'((ici add1) 1))
(test/spec-passed
'collapsible25.5
;; using `contract` explicitly, to trigger double-wrapping rewrite
;; (that changed something! (but it shouldn't, so it's a bug!))
'(((contract (-> (-> c1 c1) (-> c1 c1))
(contract (-> (-> c2 c2) (-> c2 c2))
(add-many-contracts 11 (-> (-> c1 c1) (-> c1 c1))
(lambda (x) x)
'inner-pos 'inner-neg)
'pos 'neg)
'outer-pos 'outer-neg)
add1)
1))
(contract-eval '(define cic
(add-many-contracts 11 (-> (-> c2 c2) (-> c2 c2))
ci
'outer-pos 'outer-neg)))
(test-false
'collapsible-chap+imp14
'(can-combine? cic (-> c1 c1)))
(test-false
'collapsible-chap+imp15
'(can-combine? cic (-> c2 c2)))
(test/spec-passed
'collapsible26
'((cic add1) 1))
;; can we get collapsible wrappers for impersonator contracts?
(contract-eval
'(define imp-imp (contract (-> c1 c1)
(add-many-contracts 11 (-> c1 c1) (lambda (x) x) 'pos 'neg)
'pos 'neg)))
(test-true
'collapsible-imp1
'(collapsible? imp-imp))
(test/spec-passed
'collapsible27
'(imp-imp 1))
(test/spec-failed
'collapsible27f
'(imp-imp 'a)
'neg)
;; should be an impersonator contract
(test-false
'collapsible-imp2
'(chaperone-contract? (value-contract imp-imp)))
(contract-eval '(define mix1 (contract (-> any/c any/c)
(add-many-contracts 11 (-> (-> integer? integer?)
(-> integer? integer?))
(lambda (x) x)
'pos 'neg)
'pos 'neg)))
(contract-eval '(define mix2 (contract (-> (-> integer? integer?)
(-> integer? integer?))
(add-many-contracts 11 (-> any/c any/c)
(lambda (x) x)
'pos 'neg)
'pos 'neg)))
(test-true
'collapsible-flat-h/o-mix1
'(collapsible? mix1))
(test/spec-passed
'collapsible-flat-h/o-mix2
'((mix1 add1) 2))
(test-true
'collapsible-flat-h/o-mix3
'(collapsible? mix2))
(test/spec-passed
'collapsible-flat-h/o-mix5
'((mix2 add1) 2))
(test/neg-blame
'collapsible-flat-h/o-mix6
'((mix1 add1) 'a))
(test/neg-blame
'collapsible-flat-h/o-mix7
'((mix1 number->string) 2))
(test/neg-blame
'collapsible-flat-h/o-mix8
'((mix2 add1) 'a))
(test/neg-blame
'collapsible-flat-h/o-mix9
'((mix2 number->string) 2))
;; only the outer contract matters for these tests, as the inner one is fully
;; checked before we enter collapsible mode
(test/pos-blame
'collapsible-first-order-checks1
'(contract (-> any/c)
(add-many-contracts 11 (-> any/c any/c) add1 'inner-pos 'inner-neg)
'pos 'neg))
(test/pos-blame
'collapsible-first-order-checks2
'((contract (-> (-> any/c))
(add-many-contracts 11 (-> (-> any/c any/c))
(lambda () add1)
'inner-pos 'inner-neg)
'pos 'neg)))
(test/pos-blame
'collapsible-first-order-checks3
'((contract (-> (-> any/c any/c))
(add-many-contracts 11 (-> (-> any/c))
(lambda () add1)
'pos 'neg)
'outer-pos 'outer-neg)))
(test/pos-blame
'collapsible-first-order-checks4
'((contract (-> (-> any/c any/c))
(contract (-> (-> any/c))
(add-many-contracts 11 (-> (-> any/c any/c any/c))
(lambda () add1)
'pos 'neg)
'mid-pos 'mid-neg)
'outer-pos 'outer-neg)))
(test/pos-blame
'collapsible-first-order-checks5
'((contract (-> (-> any/c any/c))
(contract (-> (-> any/c))
(add-many-contracts 11 (-> (-> any/c any/c any/c))
(contract (-> (-> any/c any/c)) ; to have next one be collapsible
(lambda () add1)
'inner-pos 'inner-neg)
'pos 'neg)
'mid-pos 'mid-neg)
'outer-pos 'outer-neg)))
(test/pos-blame
'collapsible-first-order-checks6
'((contract (-> (-> any/c any/c))
(contract (-> (-> any/c))
(add-many-contracts 11 (-> (-> any/c any/c))
(contract (-> (-> any/c any/c)) ; to have next one be collapsible
(lambda () add1)
'innermost-pos 'innermost-neg)
'inner-pos 'inner-neg)
'pos 'neg)
'outer-pos 'outer-neg)))
(test/neg-blame
'collapsible-first-order-checks7
;; both should fail, but want to make sure we drop the right redundant check
'((contract (-> (-> any/c any/c any/c) any)
(add-many-contracts 11 (-> (-> any/c any/c any/c) any)
(lambda (x) x)
'inner-pos 'inner-neg)
'pos 'neg)
add1))
;; scenario: double-wrap (enter collapsible mode), unrelated chaperone, another contract
;; want to make sure no check gets lost
(test/pos-blame
'collapsible-chaperone-in-middle
'(let ([x 0])
(define f (contract (-> any/c string?)
(add-many-contracts 11 (-> any/c string?)
(lambda (x) x)
'pos 'neg)
'mid-pos 'mid-neg))
(define f2 (chaperone-procedure f (lambda (y) y)))
((add-many-contracts 11 (-> any/c integer?)
f2
'outer-pos 'outer-neg)
4)))
(test-true
'collapsible-bail-on-subcontract1
;; Contracts lifted to defeat the opt/c rewriting
'(let ([ctc1 (-> (-> any/c (values any/c any/c)) any/c)]
[ctc2 (-> (-> any/c (values any/c any/c)) any/c)])
(collapsible?
(contract ctc1
(add-many-contracts 11 ctc2
(lambda (x) x)
'pos 'neg)
'pos 'neg))))
(test-true
'collapsible-bail-on-subcontract2
;; Contracts lifted to defeat the opt/c rewriting
'(let ([ctc1 (-> any/c (-> any/c (values any/c any/c)) any/c)]
[ctc2 (-> any/c (-> any/c (values any/c any/c)) any/c)])
(collapsible?
(contract ctc1
(add-many-contracts 11 ctc2
(lambda (x y) x)
'pos 'neg)
'pos 'neg))))
(test-true
'collapsible-bail-on-subcontract3
;; Contracts lifted to defeat the opt/c rewriting
'(let ([ctc1 (-> any/c (-> any/c (values any/c any/c)))]
[ctc2 (-> any/c (-> any/c (values any/c any/c)))])
(collapsible?
(contract ctc1
(add-many-contracts 11 ctc2
(lambda (x) x)
'pos 'neg)
'pos 'neg))))
(test/neg-blame
'collapsible-merge-subcontract1
'(let ()
(define id (contract (-> (-> string? string?) (-> string? string?))
(add-many-contracts 11 (-> (-> string? string?) (-> string? string?))
(lambda (x) x)
'p1 'n1)
'p2 'n2))
(define a1 (add-many-contracts 11 (-> integer? integer?)
add1
'pos 'neg))
((id a1) "a")))
(test/neg-blame
'collapsible-merge-subcontract2
'(let ()
(define id (contract (-> (-> string? string?) (-> string? string?))
(add-many-contracts 11 (-> (-> string? string?) (-> string? string?))
(lambda (x) x)
'p1 'n2)
'pos 'neg))
(define a1 (add-many-contracts 11 (-> integer? integer?)
(lambda (x) x)
'p3 'n3))
((id a1) 1)))
(test/spec-passed
'collapsible-merge-subcontract3
'(let ()
;; lift definitions to defeat the opt/c rewriting
;; (otherwise that bypasses the whole collapsible machinery)
(define ctc1 (-> (-> string? string?) (-> string? string?)))
(define ctc2 (-> string? string?))
(define id (contract ctc1
(add-many-contracts 11 ctc1
(lambda (x) x)
'pos 'neg)
'p2 'n2))
(define a1 (add-many-contracts 11 ctc2
(lambda (x) x)
'p3 'n3))
(has-num-contracts? (id a1) 1 1)))
(test/neg-blame
'collapsible-merge-subcontract4
'(let ()
(define id (contract (-> (-> string? string?) (-> string? string?))
(add-many-contracts 11 (-> (-> string? string?) (-> string? string?))
(lambda (x) x)
'p1 'n1)
'p2 'n2))
(define a1 (contract (-> integer? integer?)
(add-many-contracts 11 (-> integer? integer?)
add1
'p3 'n3)
'pos 'neg))
((id a1) "a")))
(test/neg-blame
'collapsible-merge-subcontract5
'(let ()
(define id (contract (-> (-> string? string?) (-> string? string?))
(add-many-contracts 11 (-> (-> string? string?) (-> string? string?))
(lambda (x) x)
'p1 'n1)
'pos 'neg))
(define a1 (contract (-> integer? integer?)
(add-many-contracts 11 (-> integer? integer?)
(lambda (x) x)
'p3 'n3)
'p4 'n4))
((id a1) 1)))
(test/spec-passed
'collapsible-merge-subcontract6
'(let ()
;; lift definitions to defeat the opt/c rewriting
;; (otherwise that bypasses the whole collapsible machinery)
(define ctc1 (-> (-> string? string?) (-> string? string?)))
(define ctc2 (-> string? string?))
(define id (contract ctc1
(add-many-contracts 11 ctc1
(lambda (x) x)
'pos 'neg)
'p2 'n2))
(define a1 (contract ctc2
(add-many-contracts 11 ctc2
(lambda (x) x)
'p3 'n3)
'p4 'n4))
(has-num-contracts? (id a1) 1 1)))
(test/spec-passed
'collapsible-multi-args1
'((contract (-> number? string? number?)
(add-many-contracts 11 (-> number? string? number?)
(lambda (x y) x)
'pos 'neg)
'outer-pos 'outer-neg)
1 "a"))
(test/pos-blame
'collapsible-multi-args2
'((contract (-> number? string? number?)
(add-many-contracts 11 (-> number? string? number?)
(lambda (x) x)
'pos 'neg)
'outer-pos 'outer-neg)
1 "a"))
(test/neg-blame
'collapsible-multi-args3
'((contract (-> number? string? number?)
(add-many-contracts 11 (-> number? string? number?)
(lambda (x y) x)
'inner-pos 'inner-neg)
'pos 'neg)
"a" "a"))
(test/neg-blame
'collapsible-multi-args4
'((contract (-> number? string? number?)
(add-many-contracts 11 (-> number? string? number?)
(lambda (x y) x)
'inner-pos 'inner-neg)
'pos 'neg)
1 1))
(test/spec-passed/result
'collapsible-multi-args5
'(with-handlers ([exn:fail:contract:arity? (lambda (e) 'ok)])
((contract (-> number? string? number?)
(add-many-contracts 11 (-> number? string? number?)
(lambda (x y) x)
'inner-pos 'inner-neg)
'pos 'neg)
1 1 "a"))
'ok)
(test/spec-passed/result
'collapsible-multi-args6
'(with-handlers ([exn:fail:contract:arity? (lambda (e) 'ok)])
((contract (-> number? string? number?)
(add-many-contracts 11 (-> number? string? number?)
(lambda (x y) x)
'inner-pos 'inner-neg)
'pos 'neg)
1))
'ok)
(test/spec-passed
'collapsible-multi-args7
'(collapsible? (contract (-> number? string? number?)
(add-many-contracts 11 (-> number? string? number?)
(lambda (x y) x)
'inner-pos 'inner-neg)
'pos 'neg)))
(contract-eval '(require racket/class))
(test/spec-passed
'object/c-->-pass/no-bail
'(let* ([grid/c (-> (-> (object/c)))]
[o (new object%)]
[v (lambda () o)]
[grid (contract
grid/c
(add-many-contracts 11
grid/c
(lambda () v)
'inner-pos 'inner-neg)
'pos 'neg)])
((grid))))
(test/spec-failed
'object/c-->-fail/should-bail
'(let* ([v (add-many-contracts 11 (-> integer?) (lambda () 1) 'p 'n)]
[grid (contract
(-> (-> (object/c)))
(add-many-contracts 11
(-> (-> (object/c)))
(lambda () v)
'inner-pos 'inner-neg)
'pos 'neg)])
((grid)))
"inner-pos")
;; arrow and vector contracts
(test/spec-failed
'arrow+vector
'(let* ([ctc (-> (vectorof integer?))]
[f (contract ctc
(add-many-contracts 11 ctc (lambda () (vector 1)) 'inner-pos 'inner-neg)
'pos 'neg)])
(vector-set! (f) 0 1.5))
"neg")
;; arrow and box
(test/spec-failed
'arrow+box
'(let* ([ctc (-> (box/c integer?))]
[f (contract ctc (add-many-contracts 11 ctc (lambda () (box 1)) 'inner-pos 'inner-neg) 'pos 'neg)])
(set-box! (f) 1.5))
"neg")
(test/spec-failed
'arrow-symbol-multi-pos1
'(let* ([ctc1 (-> integer? (-> symbol? symbol?))]
[ctc2 (-> integer? symbol?)]
[f (lambda (x) (lambda (y) y))]
[cf (contract ctc2 (add-many-contracts 11 ctc1 f 'inner-pos 'inner-neg) 'pos 'neg)])
(cf 0))
"pos")
(test/spec-failed
'arrow-symbol-multi-pos2
'(let* ([ctc1 (-> integer? (-> symbol? symbol?))]
[ctc2 (-> integer? symbol?)]
[f (lambda (x) 'foo)]
[cf (contract ctc2 (add-many-contracts 11 ctc1 f 'inner-pos 'inner-neg) 'pos 'neg)])
(cf 0))
"inner-pos")
(test/spec-failed
'arrow-symbol-multi-neg1
'(let* ([ctc1 (-> symbol? integer?)]
[ctc2 (-> (-> symbol? symbol?) integer?)]
[f (lambda (x) 0)]
[cf (contract ctc2 (add-many-contracts 11 ctc1 f 'inner-pos 'inner-neg) 'pos 'neg)])
(cf (lambda (x) x)))
"inner-neg")
(test/spec-failed
'arrow-symbol-multi-neg2
'(let* ([ctc1 (-> symbol? integer?)]
[ctc2 (-> (-> symbol? symbol?) integer?)]
[f (lambda (x) 0)]
[cf (contract ctc2 (add-many-contracts 11 ctc1 f 'inner-pos 'inner-neg) 'pos 'neg)])
(cf 'foo))
"neg")
(test/spec-passed/result
'calculate-drops-1
'(let* ([ctc1 (coerce-contract/f integer?)]
[ctc2 (coerce-contract/f string?)]
[ctcs (list ctc1 ctc2 ctc1 ctc2 ctc1)])
(calculate-drops ctcs))
'(2))
(test/spec-passed/result
'calculate-drops-2
'(let* ([ctc1 (coerce-contract/f integer?)]
[ctcs (list ctc1 ctc1 ctc1 ctc1 ctc1)])
(calculate-drops ctcs))
'(3 2 1))
(test/spec-passed/result
'calculate-drops-2
'(let* ([ctc1 (coerce-contract/f (object/c))]
[ctcs (list ctc1 ctc1 ctc1 ctc1 ctc1)])
(calculate-drops ctcs))
'())
(test/spec-passed/result
'calculate-drops-3
'(let* ([ctc1 (coerce-contract/f integer?)]
[ctc2 (coerce-contract/f string?)]
[ctcs (list ctc1 ctc2 ctc1 ctc2 ctc1)])
(calculate-drops ctcs))
'(2))
(test/spec-passed/result
'calculate-drops-4
'(let* ([c1 (coerce-contract/f integer?)]
[c2 (coerce-contract/f (vectorof integer?))]
[c3 (coerce-contract/f (-> integer? integer?))]
[ctcs (list c1 c2 c3 c2 c3 c1 c3 c2 c1)])
(calculate-drops ctcs))
'(5 3 4))
(test/spec-passed/result
'calculate-drops-5
'(let* ([c1 (coerce-contract/f integer?)]
[c2 (coerce-contract/f (vectorof integer?))]
[c3 (coerce-contract/f (-> integer? integer?))]
[c4 (coerce-contract/f (object/c))]
[ctcs (list c1 c2 c3 c4 c4 c2 c3 c1 c3 c2 c4 c1 c4)])
(calculate-drops ctcs))
'(7 5 6))
(test/spec-passed/result
'calculate-drops-6
'(let* ([c1 (coerce-contract/f integer?)]
[ctcs (list c1 c1 c1 c1 c1 c1 c1 c1 c1)])
(calculate-drops ctcs))
'(7 6 5 4 3 2 1))
)

File diff suppressed because it is too large Load Diff

View File

@ -72,6 +72,16 @@
'pos
'neg)
0 1))
(context-test '("the 1st argument of")
'((contract (-> boolean? integer? integer?)
(contract (-> boolean? integer? integer?)
(λ (x y) x)
'pos
'neg)
'pos
'neg)
0 1))
(context-test '("the cdr of" "the 1st argument of")
'((contract (-> (cons/c integer? boolean?) integer? integer?)
@ -344,6 +354,59 @@
'neg)
2)
0))
(context-test '("an element of" "the 2nd element of")
'(vector-ref
(vector-ref
(let ([ctc (vector/c (vectorof real?)
(vectorof number?)
(vectorof boolean?))])
(contract
ctc
(contract
ctc
(vector (vector 1) (vector 1) (vector 1))
'pos
'neg)
'pos
'neg))
2)
0))
(context-test
'("the 1st argument of" "an element of" "the range of" "the 2nd element of")
'(let* ([ctc (vector/c any/c any/c (-> any/c (vectorof (-> string? any/c))))]
[v (vector 'any1
'any2
(λ (_) (vector (λ (s) s))))]
[cv (contract ctc (contract ctc v 'pos 'neg) 'pos 'neg)])
((vector-ref ((vector-ref cv 2) 'any3) 0) 'not-a-string)))
(context-test
'("the range of" "an element of" "the range of" "the 2nd element of")
'(let* ([ctc (vector/c any/c any/c (-> any/c (vectorof (-> string? string?))))]
[v (vector 'any1
'any2
(λ (_) (vector (λ (s) s))))]
[cv (contract ctc (contract ctc v 'pos 'neg) 'pos 'neg)])
(vector-set! cv 2 (λ (_) (vector (λ (_) 'not-a-string))))
((vector-ref ((vector-ref cv 2) 'any3) 0) "a string")))
(context-test '("the 1st element of" "an element of")
'(vector-ref
(vector-ref
(let ([ctc (vectorof (vector/c integer? boolean?))])
(contract
ctc
(contract
ctc
(vector (vector 1 2) (vector 2 3))
'pos
'neg)
'pos
'neg))
0)
1))
(context-test '("the 0th element of")
'(vector-ref (contract (vector/c integer?)
@ -508,6 +571,50 @@
'(contract (and/c integer? positive?)
5.9
'pos 'neg))
(context-test
'("the x argument of" "an element of")
'(let ()
(define (contract* n c v pos neg)
(for/fold ([cv v])
([_ (in-range n)])
(contract c cv pos neg)))
(define c1 (vectorof (->i ([x integer?]) [_ integer?])))
(define c2 (vectorof (->i ([y (not/c string?)]) [_ any/c])))
(define vec
(contract
c1
(contract
c2
(contract* 11 c1 (vector (λ (x) x)) 'p 'n)
'p
'n)
'p
'n))
(define f (vector-ref vec 0))
(f "bad")))
(context-test
'("the x argument of" "an element of")
'(let ()
(define (contract* n c v pos neg)
(for/fold ([cv v])
([_ (in-range n)])
(contract c cv pos neg)))
(define c1 (vectorof (->i ([x integer?]) [_ integer?])))
(define c2 (vectorof (->i ([y (not/c string?)]) [_ any/c])))
(define vec
(contract
c1
(contract
c2
(contract c1 (vector (λ (x) x)) 'p 'n)
'p
'n)
'p
'n))
(define f (vector-ref vec 0))
(f "bad")))
(let* ([blame-pos (contract-eval '(make-blame (srcloc #f #f #f #f #f)
#f
@ -520,4 +627,4 @@
(ctest "promised: ~s\n produced: ~e" blame-fmt->-string ,blame-pos '(expected: "~s" given: "~e"))
(ctest "expected: ~s\n given: ~e" blame-fmt->-string ,blame-neg '(expected: "~s" given: "~e"))
(ctest "promised ~s produced ~e" blame-fmt->-string ,blame-pos '(expected "~s" given "~e"))
(ctest "expected ~s given ~e" blame-fmt->-string ,blame-neg '(expected "~s" given "~e"))))
(ctest "expected ~s given ~e" blame-fmt->-string ,blame-neg '(expected "~s" given "~e"))))

View File

@ -1664,8 +1664,7 @@
(and (exn:fail:contract:blame? x)
;; ensure there is context information
(regexp-match? #rx"in: the 1st argument of" (exn-message x))
(regexp-match? #rx"blaming: [^\n]*contract7-n" (exn-message x)))))
(regexp-match? #rx"blaming: [^\n]*contract7-n" (exn-message x)))))
(contract-error-test
're-providing

View File

@ -246,6 +246,48 @@
(list-contract? (odd-length-list-of-integers)))
#t)
(test/pos-blame
'build-chaperone-contract-property-s-e1
'(let ()
(struct s-e-late-neg-none ()
#:property prop:chaperone-contract
(build-chaperone-contract-property
#:collapsible-late-neg-projection
(λ (ctc)
(λ (blame)
(values
(λ (val neg-party)
(raise-blame-error blame val "bad"))
#f)))
#:name (λ (x) 'the-name)
#:first-order (λ (c) (λ (x) #t))
#:stronger (λ (x y) #f)))
(((contract-projection (s-e-late-neg-none))
(make-blame (srcloc #f #f #f #f #f) 5 (λ () 'the-name) 'pos 'neg #t))
5)))
(test/pos-blame
'build-chaperone-contract-property-s-e2
'(let ()
(struct s-e-late-neg-none ()
#:property prop:chaperone-contract
(build-chaperone-contract-property
#:collapsible-late-neg-projection
(λ (ctc)
(λ (blame)
(values
(λ (val neg-party)
(raise-blame-error blame val "bad"))
#f)))
#:name (λ (x) 'the-name)
#:first-order (λ (c) (λ (x) #t))
#:stronger (λ (x y) #f)))
(((contract-projection (listof (s-e-late-neg-none)))
(make-blame (srcloc #f #f #f #f #f) 5 (λ () 'the-name) 'pos 'neg #t))
(list 1 2 3))))
(contract-eval
'(define prop:late-neg-proj:bad-prime-box-list/c
(let* ([prime? (λ (n)

View File

@ -1464,19 +1464,20 @@
(let ([c% (parameterize ([current-inspector (make-inspector)])
(contract-eval '(class object% (super-new))))])
(test (list c% #f)
(test #:test-case-name 'object-info
(list c% #f)
'object-info
(contract-eval
`(call-with-values
(lambda () (object-info (contract (object-contract) (new ,c%) 'pos 'neg)))
list))))
;; object->vector tests
(let* ([obj
(parameterize ([current-inspector (make-inspector)])
(contract-eval '(new (class object% (field [x 1] [y 2]) (super-new)))))]
[vec (contract-eval `(object->vector ,obj))])
(test vec
(test #:test-case-name 'object->vector
vec
(contract-eval 'object->vector)
(contract-eval
`(contract (object-contract (field x integer?) (field y integer?))

View File

@ -422,7 +422,8 @@
'pos 'neg))
log)
'(c b a) '(c c b b a a))
'(c b a)
'(c c c c c c c c c c c b b b b b b b b b b b a a a a a a a a a a a))
;; this tests the situation where the double-wrapping avoidance
;; kicks in. The second part of the result, '(a b b a a), indicates

View File

@ -313,4 +313,24 @@ so that propagation occurs.
(ctest #t couple? (make-couple 1 2))
(ctest #t couple? (contract (couple/dc [hd any/c] [tl (hd) any/c]) (make-couple 1 2) 'pos 'neg))
(ctest #f couple? 1)
(ctest #f couple? #f))
(ctest #f couple? #f)
(test/spec-passed/result
"chaperone-contracts-stay-chaperones"
'(let ([ctc (-> integer?)]
[opt-ctc (opt/c (-> integer?))])
(and (chaperone-contract? ctc)
(not (impersonator-contract? ctc))
(chaperone-contract? opt-ctc)
(not (impersonator-contract? ctc))))
#t)
(test/spec-passed/result
"impersonators-stay-impersonators"
'(let ([ctc (->i () [_ any/c])]
[opt-ctc (opt/c (->i () [_ any/c]))])
(and (impersonator-contract? ctc)
(not (chaperone-contract? ctc))
(impersonator-contract? opt-ctc)
(not (chaperone-contract? opt-ctc))))
#t))

View File

@ -103,7 +103,21 @@
'neg)
x)
'(1 2)
'(1 2 1 2)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'or/c-ordering-double-wrap
'(let ([x '()])
(contract (or/c (lambda (y) (set! x (cons 2 x)) #f) (lambda (y) (set! x (cons 1 x)) #t))
(contract
(or/c (lambda (y) (set! x (cons 2 x)) #f) (lambda (y) (set! x (cons 1 x)) #t))
'anything
'pos 'neg)
'pos
'neg)
x)
'(1 2 1 2)
do-not-double-wrap)
(test/spec-passed/result
'or/c-ordering2
@ -114,7 +128,21 @@
'neg)
x)
'(2)
'(2 2)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'or/c-ordering2-double-wrap
'(let ([x '()])
(contract (or/c (lambda (y) (set! x (cons 2 x)) #t) (lambda (y) (set! x (cons 1 x)) #t))
(contract
(or/c (lambda (y) (set! x (cons 2 x)) #t) (lambda (y) (set! x (cons 1 x)) #t))
'anything
'pos 'neg)
'pos
'neg)
x)
'(2 2)
do-not-double-wrap)
(test/spec-passed
'or/c-hmm
@ -188,7 +216,21 @@
'neg)
x)
'(1 2)
'(1 2 1 2)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'and/c-ordering-double-wrap
'(let ([x '()])
(contract (and/c (lambda (y) (set! x (cons 2 x)) #t) (lambda (y) (set! x (cons 1 x)) #t))
(contract
(and/c (lambda (y) (set! x (cons 2 x)) #t) (lambda (y) (set! x (cons 1 x)) #t))
'anything
'pos 'neg)
'pos
'neg)
x)
'(1 2 1 2)
do-not-double-wrap)
(test/spec-passed/result
'ho-and/c-ordering
@ -203,7 +245,28 @@
1)
(reverse x))
'(3 1 2 4)
'(3 1 3 1 2 4 2 4)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'ho-and/c-ordering-double-wrap
'(let ([x '()])
((contract (and/c (-> (lambda (y) (set! x (cons 1 x)) #t)
(lambda (y) (set! x (cons 2 x)) #t))
(-> (lambda (y) (set! x (cons 3 x)) #t)
(lambda (y) (set! x (cons 4 x)) #t)))
(contract
(and/c (-> (lambda (y) (set! x (cons 1 x)) #t)
(lambda (y) (set! x (cons 2 x)) #t))
(-> (lambda (y) (set! x (cons 3 x)) #t)
(lambda (y) (set! x (cons 4 x)) #t)))
(λ (x) x)
'pos 'neg)
'pos
'neg)
1)
(reverse x))
'(3 1 3 1 2 4 2 4)
do-not-double-wrap)
(test/spec-passed/result
'and/c-isnt
@ -344,7 +407,22 @@
'neg)
x)
'(1 2)
'(1 2 1 2)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'first-or/c-ordering-double-wrap
'(let ([x '()])
(contract (first-or/c (lambda (y) (set! x (cons 2 x)) #f) (lambda (y) (set! x (cons 1 x)) #t))
(contract
(first-or/c (lambda (y) (set! x (cons 2 x)) #f) (lambda (y) (set! x (cons 1 x)) #t))
'anything
'pos
'neg)
'pos
'neg)
x)
'(1 2 1 2)
do-not-double-wrap)
(test/spec-passed/result
'first-or/c-ordering2
@ -355,7 +433,21 @@
'neg)
x)
'(2)
'(2 2)) ; result if contract is applied twice
do-not-double-wrap)
(test/spec-passed/result
'first-or/c-ordering2-double-wrap
'(let ([x '()])
(contract (first-or/c (lambda (y) (set! x (cons 2 x)) #t) (lambda (y) (set! x (cons 1 x)) #t))
(contract
(first-or/c (lambda (y) (set! x (cons 2 x)) #t) (lambda (y) (set! x (cons 1 x)) #t))
'anything
'pos 'neg)
'pos
'neg)
x)
'(2 2)
do-not-double-wrap)
(test/spec-passed
'first-or/c-hmm

View File

@ -396,7 +396,7 @@
'(contract (vector/c pos-blame? #:flat? #t) #(1) 'pos 'neg))
(test/spec-passed
'contract-marks42
'contract-marks42b
'((vector-ref (contract (vector/c (-> pos-blame? neg-blame?)) (vector values)
'pos 'neg)
0)

View File

@ -138,4 +138,5 @@
;; with the old implementation it is more like 20 seconds
;; on my laptop and about .3 seconds with the new one
(< (- cpu gc) 5000))
#t))
#t
do-not-double-wrap))

View File

@ -213,7 +213,9 @@
(struct gds dx ())
(define gd (contract (struct/c gds (vectorof any/c)) (gds (vector 1)) 'pos 'neg))
(vector-ref (d-vec gd) 0)))
1)
1
;; wrapping 11 times is too many for this test case, so skip the multi-wrap test
do-not-double-wrap)
(test/spec-passed
'struct/c-simple-contract-accessor
@ -1005,7 +1007,7 @@
do-not-double-wrap)
(test/spec-passed/result
'struct/dc-new42
'struct/dc-new42-1
'(let ()
(struct s (a [b #:mutable]))
(define α (new-∀/c 'α))
@ -1013,10 +1015,11 @@
(λ (x) (s 11 x))
'pos
'neg) 1)))
1)
1
do-not-double-wrap)
(test/spec-passed
'struct/dc-new42
'struct/dc-new42-2
'(let ()
(struct s (a [b #:mutable]))
(contract (struct/dc s [a (-> integer? integer?)] [b (new-∀/c 'α)])

View File

@ -9,6 +9,7 @@
test/neg-blame
test/well-formed
test ctest ctest/rewrite
test-true test-false
current-contract-namespace
make-basic-contract-namespace
@ -23,14 +24,17 @@
contract-expand
rewrite-to-add-opt/c
rewrite-to-double-wrap
rewrite-to-multi-wrap
do-not-double-wrap
contract-rewrite-tests-to-skip
test-cases failures)
(define test-cases 0)
(define failures 0)
(define contract-rewrite-tests-to-skip (make-parameter '()))
(provide new-test-case new-failure)
(define (new-test-case name)
;(printf "running test ~a\n" name)
@ -101,6 +105,7 @@
(parameterize ([current-namespace n])
(namespace-require 'racket/contract/base)
(namespace-require '(only racket/contract/private/blame exn:fail:contract:blame?))
(namespace-require '(only racket/contract/private/collapsible-common COLLAPSIBLE-LIMIT))
(for ([addon (in-list addons)])
(namespace-require addon)))
n)
@ -159,7 +164,9 @@
(test #t
name
(contract-eval #:test-case-name name
`(with-handlers ((exn:fail? (λ (x) (and (,exn-ok? x) #t)))) ,exp))))
`(with-handlers ((exn:fail? (λ (x) (and (,exn-ok? x) #t))))
,exp
"NO EXN RAISED"))))
(define (contract-syntax-error-test name exp [reg #rx""])
(test #t
@ -207,7 +214,7 @@
,(wrapper expression k)
'no-exn-raised)))))
(rewrite-test rewrite-to-add-opt/c "rewrite-to-add-opt/c")
(rewrite-test rewrite-to-double-wrap "rewrite-to-double-wrap"))
(rewrite-test rewrite-to-multi-wrap "rewrite-to-double-wrap"))
(define (test/spec-passed/result name expression result [double-wrapped-result result])
(parameterize ([compile-enforce-module-constants #f])
@ -224,7 +231,7 @@
',(wrapper expression k)))))
(rewrite-test rewrite-to-add-opt/c "rewrite-to-add-opt/c")
(unless (eq? double-wrapped-result do-not-double-wrap)
(rewrite-test rewrite-to-double-wrap "rewrite-to-double-wrap" double-wrapped-result))
(rewrite-test rewrite-to-multi-wrap "rewrite-to-double-wrap" double-wrapped-result))
(let ([new-expression (rewrite-out expression)])
(when new-expression
@ -237,6 +244,10 @@
eval
',new-expression))))))
;; convenient shortcuts
(define (test-true name expression) (test/spec-passed/result name expression #t))
(define (test-false name expression) (test/spec-passed/result name expression #f))
;; rewrites `provide/contract' to use `contract-out'
(define (rewrite-out orig-exp)
(define rewrote? #f)
@ -327,16 +338,15 @@
(rewrite (lambda (ctc val parties loop)
`(contract (opt/c ,(loop ctc)) ,(loop val) ,@(map loop parties)))))
;; rewrites `contract` to double-wrap. To test space-efficient wrappers.
(define rewrite-to-double-wrap
;; rewrites `contract` to double-wrap. To test collapsible wrappers.
(define rewrite-to-multi-wrap
(rewrite (lambda (ctc val parties loop)
(define new-ctc (loop ctc))
(define new-parties (map loop parties))
`(contract ,new-ctc
(contract ,(loop ctc)
,(loop val)
,@new-parties)
,@new-parties))))
`(let ([the-ctc ,new-ctc])
(for/fold ([the-val ,(loop val)])
([i (in-range (add1 COLLAPSIBLE-LIMIT))])
(contract the-ctc the-val ,@new-parties))))))
(define do-not-double-wrap (gensym)) ; recognized by some test forms
;; blame : (or/c 'pos 'neg string?)
@ -360,19 +370,20 @@
(and (exn:fail:contract:blame? exn)
(,has-proper-blame? (exn-message exn))))))
(define (rewrite-test wrapper wrapper-name short-wrapper-name)
(let/ec k
(let ([rewritten (wrapper expression k)])
(contract-eval
#:test-case-name (format "~a ~a" name wrapper-name)
`(,test-an-error
',(string->symbol (format "~a+~a" name short-wrapper-name))
(lambda () ,rewritten)
',rewritten
(lambda (exn)
(and (exn:fail:contract:blame? exn)
(,has-proper-blame? (exn-message exn)))))))))
(rewrite-test rewrite-to-add-opt/c "rewrite-to-add-opt/c" "opt/c")
(rewrite-test rewrite-to-double-wrap "rewrite-to-double-wrap" "double"))
(unless (member short-wrapper-name (contract-rewrite-tests-to-skip))
(let/ec k
(let ([rewritten (wrapper expression k)])
(contract-eval
#:test-case-name (format "~a ~a" name wrapper-name)
`(,test-an-error
',(string->symbol (format "~a+~a" name short-wrapper-name))
(lambda () ,rewritten)
',rewritten
(lambda (exn)
(and (exn:fail:contract:blame? exn)
(,has-proper-blame? (exn-message exn))))))))))
(rewrite-test rewrite-to-add-opt/c "rewrite-to-add-opt/c" "opt/c")
(rewrite-test rewrite-to-multi-wrap "rewrite-to-double-wrap" "double"))
(define (test/pos-blame name expression) (test/spec-failed name expression 'pos))
(define (test/neg-blame name expression) (test/spec-failed name expression 'neg))
@ -392,7 +403,7 @@
',name*
,(wrapper 'expression k)))))
(rewrite-test rewrite-to-add-opt/c 'opt-name)
(rewrite-test rewrite-to-double-wrap 'double-name)))]))
(rewrite-test rewrite-to-multi-wrap 'double-name)))]))
(define (test/well-formed stx)
(contract-eval
@ -427,4 +438,4 @@
eval
'(begin ,rewritten (void)))))))
(rewrite-test rewrite-to-add-opt/c "opt/c")
(rewrite-test rewrite-to-double-wrap "double"))
(rewrite-test rewrite-to-multi-wrap "double"))

View File

@ -1,5 +1,6 @@
#lang racket/base
(require "test-util.rkt")
(require (only-in racket/contract/private/collapsible-common COLLAPSIBLE-LIMIT))
(parameterize ([current-contract-namespace
(make-basic-contract-namespace
'racket/contract/combinator)])
@ -87,41 +88,43 @@
(vector-immutable 11)
'pos 'neg))
(test/pos-blame
'vectorof13
'(let ()
(define N 40)
(define cv
(contract (for/fold ([c (-> integer? integer?)])
([i (in-range N)])
(vectorof c))
(for/fold ([v 'not-a-procedure])
([i (in-range N)])
(vector v))
'pos 'neg))
(let loop ([cv cv])
(loop (vector-ref cv 0)))))
(parameterize ([contract-rewrite-tests-to-skip '("double")])
(test/pos-blame
'vectorof13
'(let ()
(define N 40)
(define cv
(contract (for/fold ([c (-> integer? integer?)])
([i (in-range N)])
(vectorof c))
(for/fold ([v 'not-a-procedure])
([i (in-range N)])
(vector v))
'pos 'neg))
(let loop ([cv cv])
(loop (vector-ref cv 0))))))
(test/neg-blame
'vectorof14
'(let ()
(define N 40)
(define cv
(contract (for/fold ([c (-> integer? integer?)])
([i (in-range N)])
(vectorof c))
(for/fold ([v add1])
([i (in-range N)])
(vector v))
'pos 'neg))
(let loop ([cv cv]
[i N])
(cond
[(= i 1)
(vector-set! cv 0 'not-a-procedure)]
[else
(loop (vector-ref cv 0)
(- i 1))]))))
(parameterize ([contract-rewrite-tests-to-skip '("double")])
(test/neg-blame
'vectorof14
'(let ()
(define N 40)
(define cv
(contract (for/fold ([c (-> integer? integer?)])
([i (in-range N)])
(vectorof c))
(for/fold ([v add1])
([i (in-range N)])
(vector v))
'pos 'neg))
(let loop ([cv cv]
[i N])
(cond
[(= i 1)
(vector-set! cv 0 'not-a-procedure)]
[else
(loop (vector-ref cv 0)
(- i 1))])))))
(test/spec-passed
'vector/c1
@ -163,56 +166,59 @@
(vector-set! (contract (vector/c integer?) v 'pos 'neg)
0 #f)))
(test/pos-blame
'vector/c7
'(let ()
(define N 40)
(define cv
(contract (for/fold ([c (-> integer? integer?)])
([i (in-range N)])
(vector/c c))
(for/fold ([v 'not-a-procedure])
([i (in-range N)])
(vector v))
'pos 'neg))
(let loop ([cv cv])
(loop (vector-ref cv 0)))))
(parameterize ([contract-rewrite-tests-to-skip '("double")])
(test/pos-blame
'vector/c7
'(let ()
(define N 40)
(define cv
(contract (for/fold ([c (-> integer? integer?)])
([i (in-range N)])
(vector/c c))
(for/fold ([v 'not-a-procedure])
([i (in-range N)])
(vector v))
'pos 'neg))
(let loop ([cv cv])
(loop (vector-ref cv 0))))))
(test/pos-blame
'vector/c8
'(let ()
(define N 40)
(define cv
(contract (for/fold ([c (-> integer? integer?)])
([i (in-range N)])
(vector/c c))
(for/fold ([v 'not-a-procedure])
([i (in-range N)])
(vector-immutable v))
'pos 'neg))
(let loop ([cv cv])
(loop (vector-ref cv 0)))))
(parameterize ([contract-rewrite-tests-to-skip '("double")])
(test/pos-blame
'vector/c8
'(let ()
(define N 40)
(define cv
(contract (for/fold ([c (-> integer? integer?)])
([i (in-range N)])
(vector/c c))
(for/fold ([v 'not-a-procedure])
([i (in-range N)])
(vector-immutable v))
'pos 'neg))
(let loop ([cv cv])
(loop (vector-ref cv 0))))))
(test/neg-blame
'vector/c9
'(let ()
(define N 40)
(define cv
(contract (for/fold ([c (-> integer? integer?)])
([i (in-range N)])
(vector/c c))
(for/fold ([v add1])
([i (in-range N)])
(vector v))
'pos 'neg))
(let loop ([cv cv]
[i N])
(cond
[(= i 1)
(vector-set! cv 0 'not-a-procedure)]
[else
(loop (vector-ref cv 0)
(- i 1))]))))
(parameterize ([contract-rewrite-tests-to-skip '("double")])
(test/neg-blame
'vector/c9
'(let ()
(define N 40)
(define cv
(contract (for/fold ([c (-> integer? integer?)])
([i (in-range N)])
(vector/c c))
(for/fold ([v add1])
([i (in-range N)])
(vector v))
'pos 'neg))
(let loop ([cv cv]
[i N])
(cond
[(= i 1)
(vector-set! cv 0 'not-a-procedure)]
[else
(loop (vector-ref cv 0)
(- i 1))])))))
(test/pos-blame
'vector/c7
@ -234,7 +240,7 @@
'pos 'neg)
0)
1
2)
(add1 COLLAPSIBLE-LIMIT))
(test/spec-passed/result
'vectorof-eager

View File

@ -123,7 +123,7 @@
value-blame
contract-continuation-mark-key
list-contract?
;; from private/case-arrow.rkt
case->

View File

@ -0,0 +1,28 @@
#lang racket/base
(require "private/collapsible-common.rkt"
(submod "private/collapsible-common.rkt" properties)
(only-in "private/guts.rkt"
get/build-collapsible-late-neg-projection
collapsible-contract-continuation-mark-key
with-collapsible-contract-continuation-mark))
(provide
;; collapsible functions and structures
(struct-out collapsible-ho/c)
(struct-out collapsible-leaf/c)
(struct-out collapsible-property)
(struct-out collapsible-count-property)
(struct-out collapsible-wrapper-property)
build-collapsible-leaf
prop:collapsible-contract
collapsible-contract-property?
build-collapsible-contract-property
collapsible-contract?
merge
collapsible-guard
impersonator-prop:collapsible
has-impersonator-prop:collapsible?
get-impersonator-prop:collapsible
get/build-collapsible-late-neg-projection
collapsible-contract-continuation-mark-key
with-collapsible-contract-continuation-mark)

View File

@ -53,7 +53,7 @@
contract-stronger?
contract-equivalent?
list-contract?
contract-first-order
contract-first-order-passes?
@ -74,7 +74,7 @@
contract-continuation-mark-key
with-contract-continuation-mark
(struct-out wrapped-extra-arg-arrow)
contract-custom-write-property-proc
(rename-out [contract-custom-write-property-proc custom-write-property-proc])
@ -123,6 +123,7 @@
(λ (#:name [name 'anonymous-chaperone-contract]
#:first-order [first-order (λ (x) #t)]
#:late-neg-projection [late-neg-projection #f]
#:collapsible-late-neg-projection [collapsible-late-neg-projection #f]
#:val-first-projection [val-first-projection #f]
#:projection [projection #f]
#:stronger [stronger #f]
@ -133,6 +134,9 @@
#:first-order first-order
#:late-neg-projection
(maybe-add-wrapper add-late-neg-chaperone-check late-neg-projection)
#:collapsible-late-neg-projection
(maybe-add-wrapper add-collapsible-late-neg-chaperone-check
collapsible-late-neg-projection)
#:val-first-projection
(maybe-add-wrapper add-val-first-chaperone-check val-first-projection)
#:projection
@ -149,6 +153,7 @@
#:first-order [get-first-order (λ (c) (λ (x) #t))]
#:val-first-projection [val-first-proj #f]
#:late-neg-projection [late-neg-proj #f]
#:collapsible-late-neg-projection [collapsible-late-neg-proj #f]
#:projection [get-projection #f]
#:stronger [stronger #f]
#:equivalent [equivalent #f]
@ -162,6 +167,8 @@
(maybe-add-wrapper add-prop-val-first-chaperone-check val-first-proj)
#:late-neg-projection
(maybe-add-wrapper add-prop-late-neg-chaperone-check late-neg-proj)
#:collapsible-late-neg-projection
(maybe-add-wrapper add-prop-collapsible-late-neg-chaperone-check collapsible-late-neg-proj)
#:projection
(maybe-add-wrapper add-prop-chaperone-check get-projection)
#:stronger stronger
@ -171,6 +178,20 @@
#:list-contract? is-list-contract?))
build-chaperone-contract-property))
(define (add-prop-collapsible-late-neg-chaperone-check get-collapsible-late-neg)
(λ (c)
(add-collapsible-late-neg-chaperone-check (get-collapsible-late-neg c))))
(define (add-collapsible-late-neg-chaperone-check accepts-blame)
(λ (b)
(define-values (accepts-val-and-np collapsible-ctc) (accepts-blame b))
(values
(λ (x neg-party)
(check-and-signal x
(accepts-val-and-np x neg-party)
'make-chaperone-contract::collapsible-late-neg-projection))
collapsible-ctc)))
(define (add-prop-late-neg-chaperone-check get-late-neg)
(λ (c)
(add-late-neg-chaperone-check (get-late-neg c))))
@ -221,6 +242,7 @@
(λ (#:name [name 'anonymous-chaperone-contract]
#:first-order [first-order (λ (x) #t)]
#:late-neg-projection [late-neg-projection #f]
#:collapsible-late-neg-projection [collapsible-late-neg-projection #f]
#:val-first-projection [val-first-projection #f]
#:projection [projection #f]
#:stronger [stronger #f]
@ -230,6 +252,8 @@
#:name name
#:first-order first-order
#:late-neg-projection (force-late-neg-eq late-neg-projection)
#:collapsible-late-neg-projection
(force-collapsible-late-neg-eq collapsible-late-neg-projection)
#:val-first-projection (force-val-first-eq val-first-projection)
#:projection (force-projection-eq projection)
#:stronger stronger
@ -242,6 +266,7 @@
(λ (#:name [name (λ (c) 'anonymous-chaperone-contract)]
#:first-order [first-order (λ (c) (λ (x) #t))]
#:late-neg-projection [late-neg-projection #f]
#:collapsible-late-neg-projection [collapsible-late-neg-projection #f]
#:val-first-projection [val-first-projection #f]
#:projection [projection #f]
#:stronger [stronger #f]
@ -253,6 +278,9 @@
#:first-order first-order
#:late-neg-projection
(and late-neg-projection (λ (c) (force-late-neg-eq (late-neg-projection c))))
#:collapsible-late-neg-projection
(and collapsible-late-neg-projection
(λ (c) (force-collapsible-late-neg-eq (collapsible-late-neg-projection c))))
#:val-first-projection
(and val-first-projection (λ (c) (force-val-first-eq (val-first-projection c))))
#:projection
@ -271,6 +299,15 @@
(accepts-val-and-np x neg-party)
x))))
(define (force-collapsible-late-neg-eq accepts-blame)
(and accepts-blame
(λ (b)
(define-values (accepts-val-and-np collapsible-ctc) (accepts-blame b))
(values
(λ (x neg-party)
(accepts-val-and-np x neg-party))
collapsible-ctc))))
(define (force-val-first-eq vfp)
(and vfp
(λ (b)

View File

@ -152,8 +152,8 @@
#:name and-name
#:first-order and-first-order
#:stronger and-stronger?
#:equivalent and-equivalent?
#:generate and/c-generate?))
#:generate and/c-generate?
#:equivalent and-equivalent?))
(define-struct (chaperone-and/c base-and/c) ()
#:property prop:custom-write custom-write-property-proc
#:property prop:chaperone-contract
@ -162,8 +162,8 @@
#:name and-name
#:first-order and-first-order
#:stronger and-stronger?
#:equivalent and-equivalent?
#:generate and/c-generate?))
#:generate and/c-generate?
#:equivalent and-equivalent?))
(define-struct (impersonator-and/c base-and/c) ()
#:property prop:custom-write custom-write-property-proc
#:property prop:contract
@ -172,8 +172,8 @@
#:name and-name
#:first-order and-first-order
#:stronger and-stronger?
#:equivalent and-equivalent?
#:generate and/c-generate?))
#:generate and/c-generate?
#:equivalent and-equivalent?))
(define-syntax (and/c stx)
(syntax-case stx (pair? listof)
@ -359,8 +359,8 @@
#:name integer-in-name
#:first-order integer-in-first-order
#:stronger integer-in-stronger
#:equivalent integer-in-equivalent
#:generate integer-in-generate))
#:generate integer-in-generate
#:equivalent integer-in-equivalent))
(struct renamed-integer-in integer-in-ctc (name)
#:property prop:flat-contract
@ -389,4 +389,4 @@
(set-some-basic-integer-in-contracts! renamed-integer-in
(integer-in #f #f)
(integer-in 0 #f)
(integer-in 1 #f))
(integer-in 1 #f))

View File

@ -19,7 +19,7 @@ code does the parsing and validation of the syntax.
|#
;; istx-is-chaperone-contract? : boolean?
;; is-chaperone-contract? : boolean?
;; args : (listof arg?)
;; rst : (or/c #f arg/res?)
;; pre : (listof pre/post?)
@ -589,7 +589,7 @@ code does the parsing and validation of the syntax.
(format "expected a sequence of variables and an expression to follow ~a"
(syntax-e #'kwd))
stx #'a))]
[(#:post/name (id ...) str post-cond . leftover)
[(#:post/name (id ...) str post-cond . pre-leftover)
(begin
(for-each (λ (x) (check-id stx x)) (syntax->list #'(id ...)))
(syntax-case range (any)
@ -604,7 +604,7 @@ code does the parsing and validation of the syntax.
" declaration to be a string")
stx
#'str))
(loop #'leftover
(loop #'pre-leftover
(cons (pre/post (syntax->list #'(id ...)) (syntax-e #'str) #'post-cond
(compute-quoted-src-expression #'post-cond))
post-conds)))]

View File

@ -674,7 +674,8 @@ evaluted left-to-right.)
;; (listof identifier) -- indy-arg/res-vars, bound to wrapped values with indy blame,
;; sorted like the second input
;; (listof identifier) (listof arg/var) (listof identifier) (listof arg/var)
;; the last four inputs are used only to call arg/res-to-indy-var.
;; the last four inputs are used only to call arg/res-to-indy-var.
;; boolean?
;; adds nested lets that bind the wrapper-args and the indy-arg/res-vars to projected values,
;; with 'body' in the body of the let also handles adding code to check to see if unsupplied
;; args are present (skipping the contract check, if so)
@ -1119,6 +1120,7 @@ evaluted left-to-right.)
(coerce-chaperone-contract '->i orig-ctc)
(coerce-contract '->i orig-ctc)))
(((get/build-late-neg-projection ctc) blame) obj neg-party)]))
(define (un-dep/chaperone orig-ctc obj blame neg-party indy-blame?)
(un-dep/maybe-chaperone orig-ctc obj blame neg-party #t indy-blame?))

View File

@ -0,0 +1,371 @@
#lang racket/base
;; collapsible arrow contracts
;; supports a subset of full arrow contracts
;; based on a prototype by Christophe Scholliers
(require racket/unsafe/ops
"collapsible-common.rkt" "merge-cache.rkt"
(submod "collapsible-common.rkt" properties)
"prop.rkt" "guts.rkt" "misc.rkt" "blame.rkt" "arrow-common.rkt"
"arity-checking.rkt"
(for-syntax racket/base))
(provide arrow-enter-collapsible-mode/continue
arrow-enter-collapsible-mode/collapse
val-has-arrow-collapsible-support?
->-contract-has-collapsible-support?
build-collapsible-arrow)
(module+ for-testing
(provide collapsible->? collapsible->-doms collapsible->-rng))
;; General Strategy
;; Each function contracted with a collapsible contract has two or three
;; chaperone wrappers.
;; - Functions that are wrapped in a "top-level" arrow contract (i.e., not a
;; subcontract of an arrow contract) are first contracted using a regular
;; function contract wrapper (before reaching this code). Upon being
;; contracted a second time, they reach this code, and get three chaperone
;; wrappers:
;; - first, an unsafe-chaperone wrapper, which chaperones the current
;; contracted value (to pretend it's it), but actually just calls the
;; original, uncontracted function (i.e. skips the original contract)
;; - second, a chaperone* wrapper, which gets passed the outermost wrapper,
;; and looks at a property on it to figure out what to check, then does
;; the actual contract checking
;; - third, a property-only chaperone wrapper, which has a collapsible contract
;; on a property, to keep track of which contracts to check.
;; When additional contracts are applied, this third chaperone is swapped out
;; for a new one, which keeps track of the new, merged contract to check.
;; Because it's a property-only chaperone, replacing it with a new one doesn't
;; affect chaperone-of-ness.
;; - Functions that are wrapped in an "internal node" arrow contract (i.e.,
;; their arrow contract is a subcontract of another arrow contract) may be
;; wrapped with collapsible wrappers from the start (i.e., before getting
;; any other contract).
;; Note: This could be changed. Just avoid recursively converting contracts in
;; `ho/c->collapsible->`, and instead have doms and rngs be `ho-leaf/c` always.
;; Because of this, they don't need the first, unsafe chaperone wrapper above.
;; They only have the last two wrappers, otherwise the above strategy applies.
;; Alternatively, we may try to attach an (internal node) collapsible
;; contract to a value that doesn't support collapsible contracts (e.g.,
;; a function that takes keyword arguments). In this case, we must fall back to
;; regular contract wrapping, and convert the collapsible contract to a
;; regular checking wrapper, as used elsewhere in the contract system (c.f.
;; `bail-to-regular-wrapper`).
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Data structures
;; we store the most recent blame only. when contracts fail, they assign
;; blame based on closed-over blame info, so `latest-blame` is only used
;; for things like prop:blame, contract profiling, and tail marks, in which
;; case we lose information, but it's ok to be conservative in these places
;; (and this behavior is consistent with what would happen in the absence
;; of collapsible contracts anyway)
;; ditto for `latest-ctc` and prop:contracted
(struct collapsible-> collapsible-ho/c (doms rng first-order-checks))
;; contains all the information necessary to both (1) perform first order checks
;; for an arrow contract, and (2) determine which such checks are redundant and
;; can be eliminated
(struct arrow-first-order-check (n-doms blame missing-party method?))
;; stronger really means "the same" here
(define (arrow-first-order-check-stronger? x y)
(= (arrow-first-order-check-n-doms x) (arrow-first-order-check-n-doms y)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Applicability checks
(define (->-contract-has-collapsible-support? ctc)
(define-syntax-rule (bail reason)
(begin
(log-collapsible-contract-bailout-info (format "arrow: ~a" reason))
#f))
(cond [(collapsible->? ctc) ; already one
#t]
[(base->? ctc) ; only applies to regular arrow contracts (for now)
(define doms (base->-doms ctc))
(define rngs (base->-rngs ctc))
(and
;; TODO: we can probably handle more of these cases for an -> contract
(or doms
(bail "no doms"))
(or (= (length doms) (base->-min-arity ctc)) ; no optional args
(bail "has optional args"))
(or (null? (base->-kwd-infos ctc)) ; no keyword args
(bail "has keyword args"))
(or (not (base->-rest ctc)) ; no rest arg
(bail "has rest arg"))
(or (not (base->-pre? ctc)) ; no pre-condition
(bail "has pre-condition"))
(or (not (base->-post? ctc)) ; no post-condition
(bail "has post-condition"))
(or rngs
(bail "no rngs"))
(or (= (length rngs) 1)
(bail "multiple return values")))]
[else
(bail "not base arrow")
#f]))
(define (val-has-arrow-collapsible-support? val)
(define-syntax-rule (bail reason)
(begin
(log-collapsible-value-bailout-info (format "arrow: ~a" reason))
#f))
(and
(or (not (procedure-impersonator*? val))
(bail "procedure-impersonator*?"))
;; the interposition wrapper has to support a superset of the arity
;; of the function it's wrapping, and ours can't support optional
;; args, keywords, etc. so just bail out in these cases
;; TODO: I think we can actually support optional arguments without any additional work
;; here ... so maybe this check can be removed
(or (integer? (procedure-arity val))
(bail "has optional args"))
(or (let-values ([(man opt) (procedure-keywords val)]) ; no keyword arguments
(and (null? man) (null? opt)))
(bail "has keyword args"))
;; TODO: we can maybe support non single return value functions
(or (equal? (procedure-result-arity val) 1)
(bail "can't prove single-return-value"))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Wrapper management and contract checking
(define (arrow-collapsible-guard c-c val neg-party)
(do-arrow-first-order-checks c-c val neg-party)
(define chap-not-imp? (chaperone-collapsible->? c-c))
(define prop (get-impersonator-prop:collapsible val #f))
(define safe-for-c-c?
(if prop
(and (collapsible-property? prop)
(eq? (collapsible-property-ref prop) val))
(val-has-arrow-collapsible-support? val)))
(cond
[(not safe-for-c-c?) (bail-to-regular-wrapper c-c val neg-party)]
[(collapsible-wrapper-property? prop)
(arrow-enter-collapsible-mode/continue
c-c
val
neg-party
(collapsible-property-c-c prop)
(collapsible-property-neg-party prop)
(collapsible-wrapper-property-checking-wrapper prop)
chap-not-imp?)]
[(collapsible-count-property? prop)
(arrow-enter-collapsible-mode/collapse
c-c
val
neg-party
prop
chap-not-imp?)]
;; else enter directly
[else
(arrow-enter-collapsible-mode/direct c-c val neg-party chap-not-imp?)]))
(define (add-collapsible-arrow-chaperone merged c-c neg-party checking-wrapper chap-not-imp?)
(define chap/imp (if chap-not-imp? chaperone-procedure impersonate-procedure))
(define c-c-prop
(collapsible-wrapper-property merged neg-party #f checking-wrapper))
(define wrapped
(chap/imp
checking-wrapper
#f
impersonator-prop:collapsible c-c-prop))
(set-collapsible-property-ref! c-c-prop wrapped)
wrapped)
(define (make-checking-wrapper unwrapped chap-not-imp?)
(if chap-not-imp?
(chaperone-procedure* unwrapped arrow-wrapper)
(impersonate-procedure* unwrapped arrow-wrapper)))
(define (make-unsafe-checking-wrapper val unwrapped chap-not-imp?)
(if chap-not-imp?
(chaperone-procedure*
(unsafe-chaperone-procedure val unwrapped)
arrow-wrapper)
(impersonate-procedure*
(unsafe-impersonate-procedure val unwrapped)
arrow-wrapper)))
;; If requested, we can log the arities of the contracts that end up being
;; collapsible. That can inform whether we should have arity-specific
;; wrappers, and if so, for which arities.
(define-logger collapsible-contract-arrow-wrapper-arity)
;; Create the 2nd chaperone wrapper procedure (see comment at the top),
;; as well as "deoptimization" wrappers (see below).
;; Checking wrappers come in different varieties, along two axes:
;; - chaperone vs impersonator (to know how to wrap for subcontracts)
;; - where to find the checks (on an impersonator property, for actual
;; collapsible contracts, vs closed over, for cases where we need
;; a regular contract wrapper (i.e., a subcontract has to "bail out,
;; and can't use the collapsible machinery (but since subcontracts
;; always start-out as collapsible, they can't bail out via the
;; checks in arrow-higher-order, so we need to handle them here)))
(define-syntax (make-interposition-procedure stx)
(syntax-case stx ()
[(_ maybe-closed-over-m/c maybe-closed-over-neg)
;; Note: it would be more efficient to have arity-specific wrappers here,
;; as opposed to using a rest arg.
#`(λ (outermost-chaperone . args)
(define-values (m/c neg-party)
#,(if (syntax-e #'maybe-closed-over-m/c)
#'(values maybe-closed-over-m/c maybe-closed-over-neg)
#'(let ()
(define prop (get-impersonator-prop:collapsible outermost-chaperone))
(values (collapsible-property-c-c prop)
(collapsible-property-neg-party prop)))))
(define neg (or (collapsible-ho/c-missing-party m/c) neg-party))
(define doms (collapsible->-doms m/c))
(define rng (collapsible->-rng m/c))
(define blame (collapsible-ho/c-latest-blame m/c))
(define blame+neg-party (cons blame neg))
(define n-args (length args))
(define n-doms (length doms))
(log-collapsible-contract-arrow-wrapper-arity-info
(number->string n-doms))
(unless (= n-args n-doms)
(raise-wrong-number-of-args-error blame #:missing-party neg outermost-chaperone
n-args n-doms n-doms #f))
;; Note: to support (i.e., not bail on) functions that can't be proven
;; to return a single value, have a `case-lambda` wrapper here. (With
;; the possibility of using return-arity-specific wrappers if return
;; arity happens to be known.)
;; Note: should add tail-marks-match support here.
(define rng-checker
(lambda (result)
(with-collapsible-contract-continuation-mark
(with-contract-continuation-mark
blame+neg-party
(collapsible-guard rng result neg)))))
(apply values
rng-checker
(for/list ([dom (in-list doms)]
[arg (in-list args)])
(with-collapsible-contract-continuation-mark
(with-contract-continuation-mark
blame+neg-party
(collapsible-guard dom arg neg))))))]))
(define arrow-wrapper (make-interposition-procedure #f #f))
;; create a regular checking wrapper from a collapsible wrapper for a value
;; that can't use collapsible wrapping
(define (bail-to-regular-wrapper m/c val neg-party)
(define chap-not-imp? (chaperone-collapsible->? m/c))
(define neg (or (collapsible-ho/c-missing-party m/c) neg-party))
((if chap-not-imp? chaperone-procedure* impersonate-procedure*)
val
(make-interposition-procedure m/c neg)
impersonator-prop:contracted (collapsible-ho/c-latest-ctc m/c)
impersonator-prop:blame (cons
(collapsible-ho/c-latest-blame m/c)
neg)))
(define (do-arrow-first-order-checks m/c val neg-party)
(define checks (collapsible->-first-order-checks m/c))
(for ([c (in-list checks)])
(define n-doms (arrow-first-order-check-n-doms c))
(define partial-blame (arrow-first-order-check-blame c))
(define neg (arrow-first-order-check-missing-party c))
(cond [(do-arity-checking
partial-blame
val
(for/list ([i (in-range n-doms)]) #f) ; has to have the right length
#f ; no rest arg
n-doms ; min-arity = max-arity
'() ; no keywords
(arrow-first-order-check-method? c))
=> (lambda (fail) (fail (or neg neg-party)))])))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; collapsible contract data structure management
(define (build-collapsible-arrow rng doms ctc blame chap? [maybe-focs #f] [maybe-neg-blame #f])
(define focs
(or maybe-focs (list (arrow-first-order-check (length doms) blame maybe-neg-blame (base->-method? ctc)))))
(if chap?
(chaperone-collapsible-> blame maybe-neg-blame ctc doms rng focs)
(impersonator-collapsible-> blame maybe-neg-blame ctc doms rng focs)))
;; merge two collapsible->
(define/merge-cache (arrow-try-merge new-collapsible new-neg old-collapsible old-neg)
(define constructor (get-constructor new-collapsible old-collapsible))
(and constructor
(constructor
(collapsible-ho/c-latest-blame new-collapsible)
(or (collapsible-ho/c-missing-party new-collapsible) new-neg)
(collapsible-ho/c-latest-ctc new-collapsible)
;; if old and new don't have the same arity, then one of them will *have*
;; to fail its first order checks, so we're fine.
;; (we don't support optional arguments)
(merge-list (collapsible->-doms old-collapsible) old-neg (collapsible->-doms new-collapsible) new-neg)
(merge (collapsible->-rng new-collapsible) new-neg (collapsible->-rng old-collapsible) old-neg)
(arrow-first-order-merge
(collapsible->-first-order-checks new-collapsible) new-neg
(collapsible->-first-order-checks old-collapsible) old-neg))))
(define (merge-list news new-neg olds old-neg)
(for/list ([new (in-list news)]
[old (in-list olds)])
(merge new new-neg old old-neg)))
(define (arrow-first-order-merge new new-neg old old-neg)
(first-order-check-join
(add-f-o-neg-party new new-neg)
(add-f-o-neg-party old old-neg)
arrow-first-order-check-stronger?))
(define arrow-enter-collapsible-mode/continue
(make-enter-collapsible-mode/continue
arrow-try-merge
add-collapsible-arrow-chaperone
bail-to-regular-wrapper))
(define arrow-enter-collapsible-mode/collapse
(make-enter-collapsible-mode/collapse
make-unsafe-checking-wrapper
add-collapsible-arrow-chaperone
arrow-try-merge
bail-to-regular-wrapper))
(define arrow-enter-collapsible-mode/direct
(make-enter-collapsible-mode/direct
make-checking-wrapper
add-collapsible-arrow-chaperone))
(define (add-f-o-neg-party focs neg-party)
(for/list ([foc (in-list focs)])
(define missing-party (arrow-first-order-check-missing-party foc))
(struct-copy
arrow-first-order-check
foc
[missing-party (or missing-party neg-party)])))
(define (get-constructor new old)
(or (and (chaperone-collapsible->? new)
(chaperone-collapsible->? old)
chaperone-collapsible->)
(and (impersonator-collapsible->? new)
(impersonator-collapsible->? old)
impersonator-collapsible->)))
(define (->-collapsible-contract-property chap?)
(build-collapsible-contract-property
#:try-merge arrow-try-merge
#:collapsible-guard arrow-collapsible-guard))
(struct chaperone-collapsible-> collapsible-> ()
#:property prop:collapsible-contract (->-collapsible-contract-property #t))
(struct impersonator-collapsible-> collapsible-> ()
#:property prop:collapsible-contract (->-collapsible-contract-property #f))

View File

@ -10,6 +10,9 @@
"guts.rkt"
"list.rkt"
(prefix-in arrow: "arrow-common.rkt")
"arrow-collapsible.rkt"
"collapsible-common.rkt"
(submod "collapsible-common.rkt" properties)
(only-in racket/unsafe/ops
unsafe-chaperone-procedure
unsafe-impersonate-procedure))
@ -541,6 +544,9 @@
min-arity doms kwd-infos rest pre? rngs post?
plus-one-arity-function chaperone-constructor method?
late-neg?)
(define has-c-c-support?
(->-contract-has-collapsible-support? ctc))
(define chaperone? (not is-impersonator?))
(define optionals-length (- (length doms) min-arity))
(define mtd? #f) ;; not yet supported for the new contracts
(define okay-to-do-only-arity-check?
@ -555,13 +561,18 @@
(define rng-blame (arrow:blame-add-range-context orig-blame))
(define swapped-domain (blame-add-context orig-blame "the domain of" #:swap? #t))
(define partial-doms
(for/list ([dom (in-list doms)]
[n (in-naturals 1)])
((get/build-late-neg-projection dom)
(blame-add-context orig-blame
(nth-argument-of (if method? (sub1 n) n))
#:swap? #t))))
;; if the ctc supports c-c mode, there are only positional args
(define-values (partial-doms c-c-doms)
(for/lists (projs ses)
([dom (in-list doms)]
[n (in-naturals 1)])
(define dom-blame
(blame-add-context orig-blame
(nth-argument-of (if method? (sub1 n) n))
#:swap? #t))
(define prepared (get/build-collapsible-late-neg-projection dom))
(prepared dom-blame)))
(define rest-blame
(if (ellipsis-rest-arg-ctc? rest)
(blame-swap orig-blame)
@ -570,11 +581,14 @@
(define partial-rest (and rest
((get/build-late-neg-projection rest)
rest-blame)))
(define partial-ranges
(if rngs
(for/list ([rng (in-list rngs)])
((get/build-late-neg-projection rng) rng-blame))
'()))
(define-values (partial-ranges maybe-c-c-ranges)
(cond
[rngs
(for/lists (proj c-c)
([rng (in-list rngs)])
(define prepared (get/build-collapsible-late-neg-projection rng))
(prepared rng-blame))]
[else (values '() #f)]))
(define partial-kwds
(for/list ([kwd-info (in-list kwd-infos)]
[kwd (in-list kwd-infos)])
@ -591,7 +605,9 @@
[kwd-info (in-list kwd-infos)]
#:unless (kwd-info-mandatory? kwd-info))
partial-kwd)))
(define c-c-mergable
(and has-c-c-support?
(build-collapsible-arrow (car maybe-c-c-ranges) c-c-doms ctc orig-blame chaperone?)))
(define the-args (append partial-doms
(if partial-rest (list partial-rest) '())
man-then-opt-partial-kwds
@ -603,6 +619,17 @@
(if partial-rest (list partial-rest) '())))
(define blame-party-info (arrow:get-blame-party-info orig-blame))
(define (successfully-got-the-right-kind-of-function val neg-party)
(define old-c-c-prop (get-impersonator-prop:collapsible val #f))
(define safe-for-c-c?
(and has-c-c-support?
(if old-c-c-prop
(and (collapsible-property? old-c-c-prop)
(eq? (collapsible-property-ref old-c-c-prop) val))
(val-has-arrow-collapsible-support? val))))
(define wrapper-count
(if (collapsible-count-property? old-c-c-prop)
(collapsible-count-property-count old-c-c-prop)
0))
(define-values (chap/imp-func use-unsafe-chaperone-procedure?)
(apply chaperone-constructor
orig-blame val
@ -613,21 +640,60 @@
(if is-impersonator? unsafe-impersonate-procedure unsafe-chaperone-procedure)
(if is-impersonator? impersonate-procedure chaperone-procedure)))
(cond
[chap/imp-func
[(not chap/imp-func)
val]
[(not safe-for-c-c?)
(if (or post? (not rngs))
(chaperone-or-impersonate-procedure
val
chap/imp-func
impersonator-prop:contracted ctc
impersonator-prop:blame (blame-add-missing-party orig-blame neg-party))
impersonator-prop:blame (cons orig-blame neg-party))
(chaperone-or-impersonate-procedure
val
chap/imp-func
impersonator-prop:contracted ctc
impersonator-prop:blame (blame-add-missing-party orig-blame neg-party)
impersonator-prop:blame (cons orig-blame neg-party)
impersonator-prop:application-mark
(cons arrow:tail-contract-key (list* neg-party blame-party-info rngs))))]
[else val]))
[(wrapper-count . >= . COLLAPSIBLE-LIMIT)
(arrow-enter-collapsible-mode/collapse
c-c-mergable
val
neg-party
old-c-c-prop
chaperone?)]
[(collapsible-wrapper-property? old-c-c-prop)
(arrow-enter-collapsible-mode/continue
c-c-mergable
val
neg-party
(collapsible-property-c-c old-c-c-prop)
(collapsible-property-neg-party old-c-c-prop)
(collapsible-wrapper-property-checking-wrapper old-c-c-prop)
chaperone?)]
[else
(define c-c-prop
(collapsible-count-property
c-c-mergable
neg-party
#f
(add1 wrapper-count)
(or old-c-c-prop val)))
(define wrapped
(if (or post? (not rngs))
(chaperone-or-impersonate-procedure
val
chap/imp-func
impersonator-prop:collapsible c-c-prop)
(chaperone-or-impersonate-procedure
val
chap/imp-func
impersonator-prop:collapsible c-c-prop
impersonator-prop:application-mark
(cons arrow:tail-contract-key (list* neg-party blame-party-info rngs)))))
(set-collapsible-property-ref! c-c-prop wrapped)
wrapped]))
(cond
[late-neg?
(define (arrow-higher-order:lnp val neg-party)
@ -638,12 +704,18 @@
(f neg-party))]
[else
(successfully-got-the-right-kind-of-function val neg-party)]))
(if okay-to-do-only-arity-check?
(λ (val neg-party)
(cond
[(arrow:procedure-arity-exactly/no-kwds val min-arity) val]
[else (arrow-higher-order:lnp val neg-party)]))
arrow-higher-order:lnp)]
(cond
[okay-to-do-only-arity-check?
(define lnp
(λ (val neg-party)
(cond
[(arrow:procedure-arity-exactly/no-kwds val min-arity) val]
[else (arrow-higher-order:lnp val neg-party)])))
(values lnp (or c-c-mergable (build-collapsible-leaf lnp ctc orig-blame)))]
[else
(values
arrow-higher-order:lnp
(or c-c-mergable (build-collapsible-leaf arrow-higher-order:lnp ctc orig-blame)))])]
[else
(define (arrow-higher-order:vfp val)
(define-values (normal-proc proc-with-no-result-checking expected-number-of-results)

View File

@ -10,6 +10,8 @@
"generate.rkt"
"arrow-common.rkt"
"arrow-higher-order.rkt"
"arrow-collapsible.rkt"
"collapsible-common.rkt"
"list.rkt"
racket/stxparam)
@ -1543,7 +1545,7 @@
(base->-chaperone-constructor ->stct)
(base->-method? ->stct)
#f)))
(define late-neg-proj
(define collapsible-late-neg-proj
(λ (->stct)
(->-proj is-impersonator? ->stct
(base->-min-arity ->stct)
@ -1572,7 +1574,7 @@
#:generate ->-generate
#:exercise ->-exercise
#:val-first-projection val-first-proj
#:late-neg-projection late-neg-proj))
#:collapsible-late-neg-projection collapsible-late-neg-proj))
(define (->-stronger this that)
(and (base->? that)

View File

@ -0,0 +1,335 @@
#lang racket/base
;; Common functionality used by all collapsible contracts
(require "prop.rkt" "merge-cache.rkt")
(provide (struct-out collapsible-ho/c)
(struct-out collapsible-leaf/c)
(struct-out collapsible-property)
(struct-out collapsible-count-property)
(struct-out collapsible-wrapper-property)
build-collapsible-leaf
prop:collapsible-contract
collapsible-contract-property?
build-collapsible-contract-property
collapsible-contract-property?
collapsible-contract?
merge
collapsible-guard
first-order-check-join
log-collapsible-value-bailout-info
log-collapsible-contract-bailout-info
log-collapsible-cache-fail-info
make-enter-collapsible-mode/continue
make-enter-collapsible-mode/collapse
make-enter-collapsible-mode/direct
COLLAPSIBLE-LIMIT)
(module+ for-testing
(provide collapsible-leaf/c?
collapsible-leaf/c-contract-list
collapsible-leaf/c-proj-list
collapsible-property-c-c
collapsible-property-ref
has-impersonator-prop:collapsible?
get-impersonator-prop:collapsible
collapsible-wrapper-property?
collapsible-wrapper-property-checking-wrapper
calculate-drops))
;; object contracts need to propagate properties across procedure->method
(module+ properties
(provide impersonator-prop:collapsible
has-impersonator-prop:collapsible?
get-impersonator-prop:collapsible))
(define-logger collapsible-value-bailout)
(define-logger collapsible-contract-bailout)
(define-logger collapsible-merging)
(define-logger collapsible-cache-fail)
(define COLLAPSIBLE-LIMIT 10)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Properties
(define-values (impersonator-prop:collapsible
has-impersonator-prop:collapsible?
get-impersonator-prop:collapsible)
(make-impersonator-property 'impersonator-prop:collapsible))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;
;; An interface for collapsible contract conversion and merging
;;
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(struct collapsible-contract-property
(try-merge
collapsible-guard)
#:omit-define-syntaxes)
(define (collapsible-contract-property-guard prop info)
(unless (collapsible-contract-property? prop)
(raise
(make-exn:fail:contract
(format "~a: expected a collapsible contract property; got: ~e"
prop)
(current-continuation-marks))))
prop)
(define-values (prop:collapsible-contract collapsible-contract? get-collapsible-contract-property)
(make-struct-type-property 'collapsible-contract collapsible-contract-property-guard))
(define (build-collapsible-contract-property
#:try-merge [try-merge #f]
#:collapsible-guard
[collapsible-guard
(lambda (ctc val neg)
(error "internal error: contract does not support `collapsible-guard`" ctc))])
(collapsible-contract-property
(or try-merge (lambda (_1 _2 _3 _4) #f))
collapsible-guard))
;; Parent structure for higher order collapsible contracts
;; which must keep track of the latest blame and missing party
;; and latest contract applied
(struct collapsible-ho/c (latest-blame missing-party latest-ctc))
(struct collapsible-leaf/c (proj-list contract-list blame-list missing-party-list)
#:property prop:collapsible-contract
(build-collapsible-contract-property
#:try-merge (lambda (new new-neg old old-neg)
(and (collapsible-leaf/c? old)
(collapsible-leaf/c? new)
(join-collapsible-leaf/c new new-neg old old-neg)))
#:collapsible-guard
(lambda (c-c val neg-party)
(apply-proj-list (collapsible-leaf/c-proj-list c-c)
(collapsible-leaf/c-missing-party-list c-c)
val
neg-party))))
(define (build-collapsible-leaf proj ctc blame)
(collapsible-leaf/c (list proj) (list ctc) (list blame) (list #f)))
;; Allow the bailout to be passed as an optional to avoid
;; an extra indirection through the property when possible
(define (collapsible->leaf c neg-party [bail #f])
(cond
[(collapsible-leaf/c? c) c]
[else
(define bailout (or bail (get-bail c)))
(collapsible-leaf/c
(list bailout)
(list #f) ;; Bail out of ctc comparison when we see #f
(list (collapsible-ho/c-latest-blame c))
(list neg-party))]))
;; Apply a list of projections over a value
(define (apply-proj-list proj-list missing-parties val neg-party)
(for/fold ([val* val])
([proj (in-list proj-list)]
[missing-party (in-list missing-parties)])
(proj val* (or missing-party neg-party))))
;; checks whether the contract c is already implied by one of the
;; contracts in contract-list
(define (implied-by-one? contract-list c #:implies implies)
(for/or ([e (in-list contract-list)])
(implies e c)))
(define (leaf-implied-by-one? contract-list new-ctc)
(and new-ctc
(for/or ([old-ctc (in-list contract-list)])
(and old-ctc
(flat-contract-struct? new-ctc)
(contract-struct-stronger? old-ctc new-ctc)))))
;; join two collapsible-leaf contracts
(define (join-collapsible-leaf/c new-collapsible new-neg old-collapsible old-neg)
(define new-proj-list (collapsible-leaf/c-proj-list new-collapsible))
(define new-flat-list (collapsible-leaf/c-contract-list new-collapsible))
(define new-blame-list (collapsible-leaf/c-blame-list new-collapsible))
(define new-missing-party-list (collapsible-leaf/c-missing-party-list new-collapsible))
(define old-proj-list (collapsible-leaf/c-proj-list old-collapsible))
(define old-flat-list (collapsible-leaf/c-contract-list old-collapsible))
(define old-blame-list (collapsible-leaf/c-blame-list old-collapsible))
;; We have to traverse the list to add the new neg party where it is missing
(define old-missing-party-list (add-missing-parties (collapsible-leaf/c-missing-party-list old-collapsible) old-neg))
(define-values (not-implied-projs not-implied-flats not-implied-blames not-implied-missing-parties)
(for/lists (_1 _2 _3 _4) ([new-proj (in-list new-proj-list)]
[new-flat (in-list new-flat-list)]
[new-blame (in-list new-blame-list)]
[new-missing-party (in-list new-missing-party-list)]
#:when (not (leaf-implied-by-one? old-flat-list new-flat)))
(values new-proj new-flat new-blame (or new-missing-party new-neg))))
(define res-flats (fast-append old-flat-list not-implied-flats))
(define res-blames (fast-append old-blame-list not-implied-blames))
(define res-missings (fast-append old-missing-party-list not-implied-missing-parties))
(define res-projs (fast-append old-proj-list not-implied-projs))
(define-values (pruned-projs pruned-flats pruned-blames pruned-missings)
(prune res-projs res-flats res-blames res-missings))
(collapsible-leaf/c pruned-projs pruned-flats pruned-blames pruned-missings))
(define (add-missing-parties missing-parties new-neg-party)
(for/list ([neg-party (in-list missing-parties)])
(or neg-party new-neg-party)))
(define (calculate-drops flats)
(define-values (to-drop _1 _2)
(for/fold ([indices '()]
[seen (hasheq)]
[maybe-drop (hasheq)])
([flat (in-list flats)]
[i (in-naturals)])
(cond
[(or (flat-contract-struct? flat) (chaperone-contract-struct? flat))
(cond
[(hash-ref seen flat #f)
(define maybe-index (hash-ref maybe-drop flat #f))
(cond
[maybe-index
(define new-maybe-drop (hash-set maybe-drop flat i))
(values (cons maybe-index indices) seen new-maybe-drop)]
[else
(define new-maybe-drop (hash-set maybe-drop flat i))
(values indices seen new-maybe-drop)])]
[else
(define new-seen (hash-set seen flat #t))
(values indices new-seen maybe-drop)])]
[else
(values indices seen maybe-drop)])))
to-drop)
(define (prune projs flats blames missings)
(cond
[((length flats) . <= . 10)
(define to-drop (calculate-drops flats))
(for/lists (_1 _2 _3 _4) ([proj (in-list projs)]
[flat (in-list flats)]
[blame (in-list blames)]
[missing (in-list missings)]
[i (in-naturals)]
#:when (not (memv i to-drop)))
(values proj flat blame missing))]
[else (values projs flats blames missings)]))
;; A specialized version of append that will immediately return if either
;; argument is empty
(define (fast-append l1 l2)
(cond
[(null? l2) l1]
[(null? l1) l2]
[else
(cons (car l1) (fast-append (cdr l1) l2))]))
;; Assuming that merging is symmetric, ie old-can-merge? iff new-can-merge?
;; This is true of the current c-c implementation, but if it ever changes
;; this function will neef to check both directions for merging
(define/merge-cache (merge new-c-c new-neg old-c-c old-neg)
(define-values (new-try-merge new-proj) (get-merge-components new-c-c))
(define-values (_ old-proj) (get-merge-components old-c-c))
(or (new-try-merge new-c-c new-neg old-c-c old-neg)
(join-collapsible-leaf/c (collapsible->leaf new-c-c new-neg new-proj)
new-neg
(collapsible->leaf old-c-c old-neg old-proj)
old-neg)))
(define (get-merge-components collapsible)
(define prop (get-collapsible-contract-property collapsible))
(define guard (collapsible-contract-property-collapsible-guard prop))
(values
(collapsible-contract-property-try-merge prop)
;; FIXME: don't really want to build a lambda here ...
(λ (val neg) (guard collapsible val neg))))
(define (collapsible-guard collapsible val neg-party)
(define prop (get-collapsible-contract-property collapsible))
(define guard (collapsible-contract-property-collapsible-guard prop))
(guard collapsible val neg-party))
(define (get-bail collapsible)
(define prop (collapsible-contract-property collapsible))
(define guard (collapsible-contract-property-collapsible-guard prop))
;; FIXME: don't really want to build this lambda ...
(λ (val neg) (guard collapsible val neg)))
(define (first-order-check-join new-checks old-checks stronger?)
(fast-append old-checks
(for/list ([new (in-list new-checks)]
#:when (not (implied-by-one?
old-checks new
#:implies stronger?)))
new)))
(struct collapsible-property (c-c neg-party [ref #:mutable]))
(struct collapsible-count-property collapsible-property (count prev))
(struct collapsible-wrapper-property collapsible-property (checking-wrapper))
;; A Collapsible-Property is one of
;; - (collapsible-count-property collapsible?
;; neg-party?
;; impersonator?
;; natural-number/c
;; (or/c collapsible-count-property?
;; (not/c collapsible-count-property?)))
;; a count of the contracts currently attached to the value along with other
;; necessary collapsible information
;; - (collapsible-wrapper-property collapsible? neg-party? impersonator? impersonator?)
;; indicates this value is in collapsible mode, holds the attached collapsible contract,
;; the most recent neg-party, a pointer to the
;; last known collapsible wrapper, and the checking wrapper that has
;; the collapsible interposition functions
(define (make-enter-collapsible-mode/direct
make-checking-wrapper
add-c-c-chaperone)
(λ (c-c val neg-party chap-not-imp?)
(define checking-wrapper (make-checking-wrapper val chap-not-imp?))
(add-c-c-chaperone c-c c-c neg-party checking-wrapper chap-not-imp?)))
(define (make-enter-collapsible-mode/continue
try-merge
add-c-c-chaperone
bail)
(λ (new-c-c val new-neg-party c-c neg-party checking-wrapper chap-not-imp?)
(define merged-c-c (try-merge new-c-c new-neg-party c-c neg-party))
(cond
[merged-c-c
;; Passing #f as the new-neg seems ugly, need to do more to fix this plumbing
(add-c-c-chaperone merged-c-c new-c-c #f checking-wrapper chap-not-imp?)]
[else (bail new-c-c val new-neg-party)])))
(define (make-enter-collapsible-mode/collapse
make-unsafe-checking-wrapper
add-c-c-chaperone
try-merge
bail)
(λ (c-c val neg-party c-c-prop chap-not-imp?)
(define-values (merged-c-c checking-wrapper)
(let loop ([left c-c]
[left-neg neg-party]
[prop c-c-prop])
(cond
[left
(define right (collapsible-property-c-c prop))
(define right-neg (collapsible-property-neg-party prop))
(define prev (collapsible-count-property-prev prop))
(define merged (try-merge left left-neg right right-neg))
(cond
;; there is another contract underneath this one
[(collapsible-count-property? prev)
(loop merged #f prev)]
;; we've reached the bottom of the contract stack
[else
(define checking-wrapper
(make-unsafe-checking-wrapper val prev chap-not-imp?))
(values merged checking-wrapper)])]
;; a merge failed, so we should return immediately
;; indicating the failure
[else (values #f #f)])))
(cond
[merged-c-c
(add-c-c-chaperone merged-c-c c-c neg-party checking-wrapper chap-not-imp?)]
[else (bail c-c val neg-party)])))

View File

@ -5,6 +5,8 @@
"prop.rkt"
"rand.rkt"
"generate-base.rkt"
"collapsible-common.rkt"
(submod "collapsible-common.rkt" properties)
"../../private/math-predicates.rkt"
racket/pretty
racket/list
@ -24,12 +26,14 @@
contract-stronger?
contract-equivalent?
list-contract?
contract-first-order
contract-first-order-passes?
prop:contracted prop:blame
impersonator-prop:contracted impersonator-prop:blame
impersonator-prop:contracted
impersonator-prop:blame
has-contract? value-contract
has-blame? value-blame
@ -57,6 +61,8 @@
contract-continuation-mark-key
with-contract-continuation-mark
collapsible-contract-continuation-mark-key
with-collapsible-contract-continuation-mark
(struct-out wrapped-extra-arg-arrow)
contract-custom-write-property-proc
@ -67,6 +73,7 @@
contract-late-neg-projection ;; might return #f (if none)
get/build-val-first-projection ;; builds one if necc., using contract-projection
get/build-late-neg-projection
get/build-collapsible-late-neg-projection
warn-about-val-first?
contract-name
@ -90,7 +97,8 @@
false/c-contract
true/c-contract
contract-pos/neg-doubling)
contract-pos/neg-doubling
contract-pos/neg-doubling.2)
(define (contract-custom-write-property-proc stct port mode)
(define (write-prefix)
@ -153,7 +161,9 @@
(define (has-contract? v)
(or (has-prop:contracted? v)
(has-impersonator-prop:contracted? v)))
(has-impersonator-prop:contracted? v)
;; TODO: I think this is the right check, but I'm not positive
(has-impersonator-prop:collapsible? v)))
(define (value-contract v)
(cond
@ -161,11 +171,17 @@
(get-prop:contracted v)]
[(has-impersonator-prop:contracted? v)
(get-impersonator-prop:contracted v)]
[(get-impersonator-prop:collapsible v #f)
=>
(λ (p)
(collapsible-ho/c-latest-ctc (collapsible-property-c-c p)))]
[else #f]))
(define (has-blame? v)
(or (has-prop:blame? v)
(has-impersonator-prop:blame? v)))
(has-impersonator-prop:blame? v)
;; TODO: I think this check is ok, but I'm not sure ...
(has-impersonator-prop:collapsible? v)))
(define (value-blame v)
(define bv
@ -174,6 +190,13 @@
(get-prop:blame v)]
[(has-impersonator-prop:blame? v)
(get-impersonator-prop:blame v)]
[(get-impersonator-prop:collapsible v #f)
=>
(λ (p)
(define c-c (collapsible-property-c-c p))
(cons
(collapsible-ho/c-latest-blame c-c)
(or (collapsible-ho/c-missing-party c-c) (collapsible-property-neg-party p))))]
[else #f]))
(cond
[(and (pair? bv) (blame? (car bv)))
@ -397,7 +420,8 @@
name)
x
#f
(memq x the-known-good-contracts))])]
(or (struct-predicate-procedure? x)
(memq x the-known-good-contracts)))])]
[(null? x)
(unless list/c-empty
(error 'coerce-contract/f::list/c-empty "too soon!"))
@ -792,12 +816,27 @@
(define-logger racket/contract)
(define (get/build-collapsible-late-neg-projection ctc)
(cond
[(contract-struct-collapsible-late-neg-projection ctc) => values]
[else
(define lnp (get/build-late-neg-projection ctc))
(λ (blame)
(define proj (lnp blame))
(values proj
(build-collapsible-leaf proj ctc blame)))]))
(define (get/build-late-neg-projection ctc)
(cond
[(contract-struct-late-neg-projection ctc) => values]
[else
(log-racket/contract-info "no late-neg-projection for ~s" ctc)
(cond
[(contract-struct-collapsible-late-neg-projection ctc) =>
(lambda (f)
(lambda (blame)
(define-values (proj _) (f blame))
proj))]
[(contract-struct-projection ctc)
=>
(λ (projection)
@ -809,7 +848,7 @@
[else
(first-order->late-neg-projection (contract-struct-first-order ctc)
(contract-struct-name ctc))])]))
(define (projection->late-neg-projection proj)
(λ (b)
(λ (x neg-party)
@ -914,6 +953,13 @@
(with-continuation-mark contract-continuation-mark-key payload
(let () code ...))))
(define collapsible-contract-continuation-mark-key
(make-continuation-mark-key 'collapsible-contract))
(define-syntax-rule (with-collapsible-contract-continuation-mark code ...)
(with-continuation-mark collapsible-contract-continuation-mark-key #t
(let () code ...)))
(define (n->th n)
(string-append
(number->string n)
@ -954,6 +1000,9 @@
(define-syntax-rule
(contract-pos/neg-doubling e1 e2)
(contract-pos/neg-doubling/proc (λ () e1) (λ () e2)))
(define-syntax-rule
(contract-pos/neg-doubling.2 e1 e2)
(contract-pos/neg-doubling.2/proc (λ () e1) (λ () e2)))
(define doubling-cm-key (gensym 'racket/contract-doubling-mark))
(define (contract-pos/neg-doubling/proc t1 t2)
(define depth
@ -965,4 +1014,18 @@
(values #f t1 t2)]
[else
(with-continuation-mark doubling-cm-key (+ depth 1)
(values #t (t1) (t2)))]))
(values #t (t1) (t2)))]))
(define (contract-pos/neg-doubling.2/proc t1 t2)
(define depth
(or (continuation-mark-set-first (current-continuation-marks)
doubling-cm-key)
0))
(cond
[(> depth 5)
(values #f t1 #f t2 #f)]
[else
(with-continuation-mark doubling-cm-key (+ depth 1)
(let ()
(define-values (t11 t12) (t1))
(define-values (t21 t22) (t2))
(values #t t11 t12 t21 t22)))]))

View File

@ -332,25 +332,25 @@
val
(λ (h k)
(values (with-contract-continuation-mark
blame+neg-party
(neg-dom-proj k neg-party))
blame+neg-party
(neg-dom-proj k neg-party))
(λ (h k v)
(with-contract-continuation-mark
blame+neg-party
((mk-pos-rng-proj k) v neg-party)))))
blame+neg-party
((mk-pos-rng-proj k) v neg-party)))))
(λ (h k v)
(with-contract-continuation-mark
blame+neg-party
(values (neg-dom-proj k neg-party)
((mk-neg-rng-proj k) v neg-party))))
blame+neg-party
(values (neg-dom-proj k neg-party)
((mk-neg-rng-proj k) v neg-party))))
(λ (h k)
(with-contract-continuation-mark
blame+neg-party
(neg-dom-proj k neg-party)))
blame+neg-party
(neg-dom-proj k neg-party)))
(λ (h k)
(with-contract-continuation-mark
blame+neg-party
(pos-dom-proj k neg-party)))
blame+neg-party
(pos-dom-proj k neg-party)))
impersonator-prop:contracted ctc
impersonator-prop:blame blame)))

View File

@ -1006,7 +1006,7 @@
blame
val
'(expected: "list?" given: "~e") val)]))))
;; prefix : contract
;; suffix : (listof contract)
(struct *list-ctc (prefix suffix)

View File

@ -0,0 +1,60 @@
#lang racket/base
(provide define/merge-cache)
(require (for-syntax racket/base))
;; weak hashtable never cleared
(define MERGE-CACHE (make-thread-cell (make-weak-hasheq)))
(require (for-syntax racket/base))
(define-syntax (define/merge-cache stx)
(syntax-case stx ()
[(_ (merge-name new-se new-neg old-se old-neg) body ...)
#'(define (merge-name new-se new-neg old-se old-neg)
(call-with-merge-cache new-se new-neg old-se old-neg
(let ([merge-name (λ () body ...)])
merge-name)))]))
(define (call-with-merge-cache new-se new-neg old-se old-neg body-thunk)
(define the-cache (thread-cell-ref MERGE-CACHE))
(define h1 (hash-ref the-cache new-se #f))
(cond
[(and h1 (ephemeron-value h1))
=>
(λ (h1)
(define h2 (hash-ref h1 new-neg #f))
(cond
[(and h2 (ephemeron-value h2))
=>
(λ (h2)
(define h3 (hash-ref h2 old-se #f))
(cond
[(and h3 (ephemeron-value h3))
=>
(λ (h3)
(define cached-result (hash-ref h3 old-neg #f))
(cond
[(ephemeron-value cached-result) => values]
[else
(define result (body-thunk))
(hash-set! h3 old-neg (make-ephemeron old-neg result))
result]))]
[else
(define result (body-thunk))
(define h3 (make-hasheq (list (cons old-neg (make-ephemeron old-neg result)))))
(hash-set! h2 old-se (make-ephemeron old-se h3))
result]))]
[else
(define result (body-thunk))
(define h3 (make-hasheq (list (cons old-neg (make-ephemeron old-neg result)))))
(define h2 (make-hasheq (list (cons old-se (make-ephemeron old-se h3)))))
(hash-set! h1 new-neg (make-ephemeron new-neg h2))
result]))]
[else
(define result (body-thunk))
(define h3 (make-hasheq (list (cons old-neg (make-ephemeron old-neg result)))))
(define h2 (make-hasheq (list (cons old-se (make-ephemeron old-se h3)))))
(define h1 (make-hasheq (list (cons new-neg (make-ephemeron new-neg h2)))))
(hash-set! the-cache new-se (make-ephemeron new-se h1))
result]))

View File

@ -477,6 +477,7 @@
#:stronger promise-ctc-stronger?
#:equivalent promise-ctc-equivalent?
#:first-order (λ (ctc) promise?)))
(struct promise-ctc promise-base-ctc ()
#:property prop:custom-write custom-write-property-proc
#:property prop:contract
@ -524,6 +525,7 @@
(with-contract-continuation-mark
blame+neg-party
(f x neg-party))))
;; TODO this ought to have the `contracted` property, but it's not a chaperone...
(make-derived-parameter
val
(add-profiling in-proj)

View File

@ -88,7 +88,7 @@
(barrier/c negative? var)))
(define protector
(apply (polymorphic-contract-body c) instances))
(((get/build-late-neg-projection protector) blame) p neg-party)))
(((get/build-late-neg-projection protector) blame) p neg-party)))
(lambda (p neg-party)
(unless (procedure? p)

View File

@ -11,6 +11,7 @@
contract-struct-projection
contract-struct-val-first-projection
contract-struct-late-neg-projection
contract-struct-collapsible-late-neg-projection
contract-struct-stronger?
contract-struct-equivalent?
contract-struct-generate
@ -68,6 +69,7 @@
exercise
val-first-projection
late-neg-projection
collapsible-late-neg-projection
list-contract? ]
#:omit-define-syntaxes)
@ -113,6 +115,12 @@
(and get-projection
(get-projection c)))
(define (contract-struct-collapsible-late-neg-projection c)
(define prop (contract-struct-property c))
(define get-collapsible-projection (contract-property-collapsible-late-neg-projection prop))
(and get-collapsible-projection
(get-collapsible-projection c)))
(define (contract-struct-stronger/equivalent?
a b
trail
@ -296,6 +304,7 @@
#:projection [get-projection #f]
#:val-first-projection [get-val-first-projection #f]
#:late-neg-projection [get-late-neg-projection #f]
#:collapsible-late-neg-projection [get-collapsible-late-neg-projection #f]
#:stronger [stronger #f]
#:equivalent [equivalent #f]
#:generate [generate (λ (ctc) (λ (fuel) #f))]
@ -312,12 +321,14 @@
" #:projection, #:val-first-projection, #:late-neg-projection, or #:first-order"
" argument to not be #f, but all four were #f")))
;; TODO: update for collapsible late-neg-projection
(unless get-late-neg-projection
(unless first-order?
(log-racket/contract-info
"no late-neg-projection passed to ~s~a"
proc-name
(build-context))))
(unless get-collapsible-late-neg-projection
(unless first-order?
(log-racket/contract-info
"no late-neg-projection passed to ~s~a"
proc-name
(build-context)))))
(unless (and (procedure? list-contract?)
(procedure-arity-includes? list-contract? 1))
@ -344,6 +355,7 @@
(λ (c) (late-neg-first-order-projection (get-name c) (get-first-order c)))]
[else #f])]
[else get-late-neg-projection])
get-collapsible-late-neg-projection
list-contract?))
(define (build-context)
@ -406,7 +418,8 @@
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(define-struct make-contract [ name first-order projection
val-first-projection late-neg-projection
val-first-projection late-neg-projection
collapsible-late-neg-projection
stronger equivalent generate exercise list-contract? ]
#:omit-define-syntaxes
#:property prop:custom-write
@ -421,6 +434,7 @@
#:projection (lambda (c) (make-contract-projection c))
#:val-first-projection (lambda (c) (make-contract-val-first-projection c))
#:late-neg-projection (lambda (c) (make-contract-late-neg-projection c))
#:collapsible-late-neg-projection (lambda (c) (make-contract-collapsible-late-neg-projection c))
#:stronger (lambda (a b) ((make-contract-stronger a) a b))
#:generate (lambda (c) (make-contract-generate c))
#:exercise (lambda (c) (make-contract-exercise c))
@ -428,6 +442,7 @@
(define-struct make-chaperone-contract [ name first-order projection
val-first-projection late-neg-projection
collapsible-late-neg-projection
stronger equivalent generate exercise list-contract? ]
#:omit-define-syntaxes
#:property prop:custom-write
@ -442,6 +457,7 @@
#:projection (lambda (c) (make-chaperone-contract-projection c))
#:val-first-projection (lambda (c) (make-chaperone-contract-val-first-projection c))
#:late-neg-projection (lambda (c) (make-chaperone-contract-late-neg-projection c))
#:collapsible-late-neg-projection (lambda (c) (make-chaperone-contract-collapsible-late-neg-projection c))
#:stronger (lambda (a b) ((make-chaperone-contract-stronger a) a b))
#:generate (lambda (c) (make-chaperone-contract-generate c))
#:exercise (lambda (c) (make-chaperone-contract-exercise c))
@ -449,6 +465,7 @@
(define-struct make-flat-contract [ name first-order projection
val-first-projection late-neg-projection
collapsible-late-neg-projection
stronger equivalent generate exercise list-contract? ]
#:omit-define-syntaxes
#:property prop:custom-write
@ -462,6 +479,7 @@
#:first-order (lambda (c) (make-flat-contract-first-order c))
#:val-first-projection (λ (c) (make-flat-contract-val-first-projection c))
#:late-neg-projection (λ (c) (make-flat-contract-late-neg-projection c))
#:collapsible-late-neg-projection (lambda (c) (make-flat-contract-collapsible-late-neg-projection c))
#:projection (lambda (c) (make-flat-contract-projection c))
#:stronger (lambda (a b) ((make-flat-contract-stronger a) a b))
#:generate (lambda (c) (make-flat-contract-generate c))
@ -474,6 +492,7 @@
#:projection [projection #f]
#:val-first-projection [val-first-projection #f]
#:late-neg-projection [late-neg-projection #f]
#:collapsible-late-neg-projection [collapsible-late-neg-projection #f]
#:stronger [stronger #f]
#:equivalent [equivalent #f]
#:generate [generate (λ (fuel) #f)]
@ -491,12 +510,14 @@
" #:projection, #:val-first-projection, #:late-neg-projection, or #:first-order"
" argument to not be #f, but all four were #f")))
;; TODO: handle the addition of the collapsible-late-neg-projection
(unless late-neg-projection
(unless first-order?
(log-racket/contract-info
"no late-neg-projection passed to ~s~a"
proc-name
(build-context))))
(unless collapsible-late-neg-projection
(unless first-order?
(log-racket/contract-info
"no late-neg-projection passed to ~s~a"
proc-name
(build-context)))))
(mk (or name default-name)
(or first-order any?)
@ -509,6 +530,7 @@
(late-neg-first-order-projection name first-order)]
[else #f])]
[else late-neg-projection])
collapsible-late-neg-projection
(or stronger weakest)
(or equivalent (if equivalent-equal? equal? weakest))
generate exercise

View File

@ -0,0 +1,261 @@
#lang racket/base
(require "prop.rkt" "guts.rkt" "blame.rkt" "vector-common.rkt"
"collapsible-common.rkt" "merge-cache.rkt"
(submod "collapsible-common.rkt" properties)
(only-in racket/unsafe/ops unsafe-chaperone-vector unsafe-impersonate-vector)
(for-syntax racket/base))
(provide build-collapsible-vector
build-doubling-collapsible-vector
vector-collapsible-guard
vector-enter-collapsible-mode/continue
vector-enter-collapsible-mode/collapse)
(module+ for-testing
(provide collapsible-vector? collapsible-vector-ref-ctcs collapsible-vector-set-ctcs))
(struct vector-first-order-check (immutable length blame missing-party))
;; mutable field are only to support impersonation in `build-doubling-c-c-vector`
(struct collapsible-vector collapsible-ho/c (first-order [ref-ctcs #:mutable] [set-ctcs #:mutable]) #:transparent)
(define (do-vector-first-order-checks m/c val neg-party)
(define checks (collapsible-vector-first-order m/c))
(for ([c (in-list checks)])
(define immutable (vector-first-order-check-immutable c))
(define length (vector-first-order-check-length c))
(define blame (vector-first-order-check-blame c))
(define neg (or (vector-first-order-check-missing-party c) neg-party))
(check-vector/c val blame immutable length neg)))
(define (vector-first-order-check-stronger? f1 f2)
(define f1-immutable (vector-first-order-check-immutable f1))
(define f1-length (vector-first-order-check-length f1))
(define f2-immutable (vector-first-order-check-immutable f2))
(define f2-length (vector-first-order-check-length f2))
(and (or (eq? f2-immutable 'dont-care)
(eq? f1-immutable f2-immutable))
(or (not f2-length)
(and f1-length (= f1-length f2-length)))))
(define (build-collapsible-vector c-c-pos c-c-neg ctc blame chap-not-imp?)
(define focs (list (build-vector-first-order-checks ctc blame)))
(if chap-not-imp?
(chaperone-collapsible-vector blame #f ctc focs c-c-pos c-c-neg)
(impersonator-collapsible-vector blame #f ctc focs c-c-pos c-c-neg)))
(define (build-doubling-collapsible-vector fetch-c-c-pos fetch-c-c-neg ctc blame chap-not-imp?)
(define focs (list (build-vector-first-order-checks ctc blame)))
(define dummy
(if chap-not-imp?
(chaperone-collapsible-vector blame #f ctc focs 'dummy-c-c-pos 'dummy-c-c-neg)
(impersonator-collapsible-vector blame #f ctc focs 'dummy-c-c-pos 'dummy-c-c-neg)))
(impersonate-struct dummy
collapsible-vector-ref-ctcs
(λ (self field-v) (fetch-c-c-pos))
collapsible-vector-set-ctcs
(λ (self field-v) (fetch-c-c-neg))))
(define (build-vector-first-order-checks ctc blame)
(cond
[(base-vectorof? ctc)
(vector-first-order-check
(base-vectorof-immutable ctc)
#f
blame
#f)]
[(base-vector/c? ctc)
(vector-first-order-check
(base-vector/c-immutable ctc)
(length (base-vector/c-elems ctc))
blame
#f)]))
(define (add-f-o-neg-party focs neg-party)
(for/list ([foc (in-list focs)])
(define missing-party (vector-first-order-check-missing-party foc))
(struct-copy
vector-first-order-check
foc
[missing-party (or missing-party neg-party)])))
(define (vector-first-order-merge new new-neg old old-neg)
(first-order-check-join
(add-f-o-neg-party new new-neg)
(add-f-o-neg-party old old-neg)
vector-first-order-check-stronger?))
(define/merge-cache (vector-try-merge new-collapsible new-neg old-collapsible old-neg)
(define constructor (get-constructor new-collapsible old-collapsible))
(and constructor
(constructor
(collapsible-ho/c-latest-blame new-collapsible)
(or (collapsible-ho/c-missing-party new-collapsible) new-neg)
(collapsible-ho/c-latest-ctc new-collapsible)
(vector-first-order-merge
(collapsible-vector-first-order new-collapsible) new-neg
(collapsible-vector-first-order old-collapsible) old-neg)
(merge* (collapsible-vector-ref-ctcs new-collapsible)
new-neg
(collapsible-vector-ref-ctcs old-collapsible)
old-neg)
(merge* (collapsible-vector-set-ctcs old-collapsible)
old-neg
(collapsible-vector-set-ctcs new-collapsible)
new-neg))))
(define (merge* new new-neg old old-neg)
(cond
[(and (vector? new) (vector? old))
(for/vector ([nc (in-vector new)]
[oc (in-vector old)])
(merge nc new-neg oc old-neg))]
[(vector? new)
(for/vector ([nc (in-vector new)])
(merge nc new-neg old old-neg))]
[(vector? old)
(for/vector ([oc (in-vector old)])
(merge new new-neg oc old-neg))]
[else
(merge new new-neg old old-neg)]))
(define (get-constructor new old)
(or (and (chaperone-collapsible-vector? new)
(chaperone-collapsible-vector? old)
chaperone-collapsible-vector)
(and (impersonator-collapsible-vector? new)
(impersonator-collapsible-vector? old)
impersonator-collapsible-vector)))
(define (vector-collapsible-guard c-c val neg-party)
(do-vector-first-order-checks c-c val neg-party)
(define chap-not-imp? (chaperone-collapsible-vector? c-c))
(define prop (get-impersonator-prop:collapsible val #f))
(define safe-for-c-c?
(if prop
(and (collapsible-property? prop)
(eq? (collapsible-property-ref prop) val))
(not (impersonator? val))))
(cond
;; not safe, bail out
[(not safe-for-c-c?)
(bail-to-regular-wrapper c-c val neg-party)]
;; already in c-c mode, so stay in
[(collapsible-wrapper-property? prop)
(vector-enter-collapsible-mode/continue
c-c
val
neg-party
(collapsible-property-c-c prop)
(collapsible-property-neg-party prop)
(collapsible-wrapper-property-checking-wrapper prop)
chap-not-imp?)]
;; need to collapse contracts ...
[(collapsible-count-property? prop)
(vector-enter-collapsible-mode/collapse
c-c
val
neg-party
prop
chap-not-imp?)]
;; else enter directly
[else
(vector-enter-collapsible-mode/direct c-c val neg-party chap-not-imp?)]))
(define (add-collapsible-vector-chaperone merged c-c neg-party checking-wrapper chap-not-imp?)
(define chap/imp (if chap-not-imp? chaperone-vector impersonate-vector))
(define c-c-prop
(collapsible-wrapper-property merged neg-party #f checking-wrapper))
(define wrapped
(chap/imp
checking-wrapper
#f
#f
impersonator-prop:collapsible c-c-prop))
(set-collapsible-property-ref! c-c-prop wrapped)
wrapped)
(define (make-checking-wrapper unwrapped chap-not-imp?)
(if chap-not-imp?
(chaperone-vector* unwrapped ref-wrapper set-wrapper)
(impersonate-vector* unwrapped ref-wrapper set-wrapper)))
(define (make-unsafe-checking-wrapper val unwrapped chap-not-imp?)
(if chap-not-imp?
(chaperone-vector*
(unsafe-chaperone-vector val unwrapped)
ref-wrapper
set-wrapper)
(impersonate-vector*
(unsafe-impersonate-vector val unwrapped)
ref-wrapper
set-wrapper)))
(define-syntax (make-vector-checking-wrapper stx)
(syntax-case stx ()
[(_ set? maybe-closed-over-m/c maybe-closed-over-neg)
#`(λ (outermost v i elt)
(define-values (m/c neg-party)
#,(if (syntax-e #'maybe-closed-over-m/c)
#'(values maybe-closed-over-m/c maybe-closed-over-neg)
#'(let ()
(define prop (get-impersonator-prop:collapsible outermost))
(values (collapsible-property-c-c prop)
(collapsible-property-neg-party prop)))))
(define neg (or (collapsible-ho/c-missing-party m/c) neg-party))
(define field
#,(if (syntax-e #'set?)
#'(collapsible-vector-set-ctcs m/c)
#'(collapsible-vector-ref-ctcs m/c)))
(define c-c
(if (vector? field) (vector-ref field i) field))
(define blame (cons (collapsible-ho/c-latest-blame m/c) neg))
(with-collapsible-contract-continuation-mark
(with-contract-continuation-mark
blame
(collapsible-guard c-c elt neg))))]))
(define ref-wrapper (make-vector-checking-wrapper #f #f #f))
(define set-wrapper (make-vector-checking-wrapper #t #f #f))
(define (bail-to-regular-wrapper m/c val neg-party)
(define chap-not-imp? (chaperone-collapsible-vector? m/c))
(define neg (or (collapsible-ho/c-missing-party m/c) neg-party))
(define blame (cons (collapsible-ho/c-latest-blame m/c) neg))
(define ctc (collapsible-ho/c-latest-ctc m/c))
(define merged+neg-party (cons m/c neg))
((if chap-not-imp? chaperone-vector* impersonate-vector*)
val
(make-vector-checking-wrapper #f m/c neg)
(make-vector-checking-wrapper #t m/c neg)
impersonator-prop:contracted ctc
impersonator-prop:blame blame))
(define vector-enter-collapsible-mode/continue
(make-enter-collapsible-mode/continue
vector-try-merge
add-collapsible-vector-chaperone
bail-to-regular-wrapper))
(define vector-enter-collapsible-mode/collapse
(make-enter-collapsible-mode/collapse
make-unsafe-checking-wrapper
add-collapsible-vector-chaperone
vector-try-merge
bail-to-regular-wrapper))
(define vector-enter-collapsible-mode/direct
(make-enter-collapsible-mode/direct
make-checking-wrapper
add-collapsible-vector-chaperone))
(define (vector-collapsible-contract-property chap-not-imp?)
(build-collapsible-contract-property
#:try-merge vector-try-merge
#:collapsible-guard vector-collapsible-guard))
(struct chaperone-collapsible-vector collapsible-vector ()
#:property prop:collapsible-contract (vector-collapsible-contract-property #t))
(struct impersonator-collapsible-vector collapsible-vector ()
#:property prop:collapsible-contract (vector-collapsible-contract-property #f))

View File

@ -0,0 +1,67 @@
#lang racket/base
(require "blame.rkt")
(provide (struct-out base-vectorof)
(struct-out base-vector/c)
do-check-vectorof
check-vector/c)
;; eager is one of:
;; - #t: always perform an eager check of the elements of an immutable vector
;; - #f: never perform an eager check of the elements of an immutable vector
;; - N (for N>=0): perform an eager check of immutable vectors size <= N
(define-struct base-vectorof (elem immutable eager))
(define-struct base-vector/c (elems immutable))
(define (do-check-vectorof val immutable blame neg-party raise-blame?)
(cond
[(vector? val)
(cond
[(eq? immutable #t)
(cond
[(immutable? val) #t]
[raise-blame?
(raise-blame-error
blame
#:missing-party neg-party
val
'(expected "an immutable vector" given: "~e")
val)]
[else #f])]
[(eq? immutable #f)
(cond
[(not (immutable? val)) #t]
[raise-blame?
(raise-blame-error
blame
#:missing-party neg-party
val
'(expected "an mutable vector" given: "~e")
val)]
[else #f])]
[else #t])]
[raise-blame?
(raise-blame-error
blame
#:missing-party neg-party
val
'(expected "an immutable vector" given: "~e")
val)]
[else #f]))
(define (check-vector/c val blame immutable length neg-party)
(define (raise-blame val . args)
(apply raise-blame-error blame #:missing-party neg-party val args))
(do-check-vectorof val immutable blame neg-party #t)
(unless (or (not length) (= (vector-length val) length))
(raise-blame-error
blame
#:missing-party neg-party
val
'(expected: "a vector of ~a element~a" given: "~e")
length
(if (= length 1) "" "s")
val)))

View File

@ -4,18 +4,16 @@
"guts.rkt"
"prop.rkt"
"blame.rkt"
"misc.rkt")
"misc.rkt"
"collapsible-common.rkt"
(submod "collapsible-common.rkt" properties)
"vector-common.rkt"
"vector-collapsible.rkt")
(provide (rename-out [wrap-vectorof vectorof]
[wrap-vector/c vector/c])
vector-immutable/c vector-immutableof)
;; eager is one of:
;; - #t: always perform an eager check of the elements of an immutable vector
;; - #f: never perform an eager check of the elements of an immutable vector
;; - N (for N>=0): perform an eager check of immutable vectors size <= N
(define-struct base-vectorof (elem immutable eager))
(define-for-syntax (convert-args args this-one)
(let loop ([args args]
[new-args null])
@ -50,32 +48,29 @@
(list '#:immutable immutable)
null)))))
(define (check-vectorof c)
(let ([elem-ctc (base-vectorof-elem c)]
[immutable (base-vectorof-immutable c)]
[flat? (flat-vectorof? c)])
(λ (val fail first-order?)
(unless (vector? val)
(fail val '(expected "a vector," given: "~e") val))
(cond
[(eq? immutable #t)
(unless (immutable? val)
(fail val '(expected "an immutable vector" given: "~e") val))]
[(eq? immutable #f)
(when (immutable? val)
(fail val '(expected "a mutable vector" given: "~e") val))]
[else (void)])
(when first-order?
(let loop ([n 0])
(cond
[(= n (vector-length val))
(void)]
[else
(define e (vector-ref val n))
(unless (contract-first-order-passes? elem-ctc e)
(fail val '(expected: "~s for element ~s" given: "~e") (contract-name elem-ctc) n e))
(contract-first-order-try-less-hard (loop (+ n 1)))])))
#t)))
(define (check-vectorof elem-ctc immutable val blame neg-party first-order? raise-blame?)
(and
(do-check-vectorof val immutable blame neg-party raise-blame?)
(if first-order?
(let loop ([n 0])
(cond
[(= n (vector-length val)) #t]
[else
(define e (vector-ref val n))
(cond
[(contract-first-order-passes? elem-ctc e)
(contract-first-order-try-less-hard (loop (+ n 1)))]
[raise-blame?
(raise-blame-error
blame
#:missing-party neg-party
val
'(expected: "~s for element ~s" given: "~e")
(contract-name elem-ctc)
n
e)]
[else #f])]))
#t)))
(define (check-late-neg-vectorof c)
(define immutable (base-vectorof-immutable c))
@ -103,10 +98,10 @@
val)])))
(define (vectorof-first-order ctc)
(let ([check (check-vectorof ctc)])
(let ([elem-ctc (base-vectorof-elem ctc)]
[immutable (base-vectorof-immutable ctc)])
(λ (val)
(let/ec return
(check val (λ _ (return #f)) #t)))))
(check-vectorof elem-ctc immutable val #f #f #t #f))))
(define (vectorof-stronger this that)
(define this-elem (base-vectorof-elem this))
@ -157,107 +152,161 @@
(define (blame-add-element-of-context blame #:swap? [swap? #f])
(blame-add-context blame "an element of" #:swap? swap?))
(define (vectorof-late-neg-ho-projection chaperone-or-impersonate-vector)
(define (vectorof-collapsible-late-neg-ho-projection chap-not-imp?)
(define chaperone-or-impersonate-vector
(if chap-not-imp? chaperone-vector impersonate-vector))
(λ (ctc)
(define elem-ctc (base-vectorof-elem ctc))
(define immutable (base-vectorof-immutable ctc))
(define flat-subcontract? (flat-contract-struct? elem-ctc))
(define eager (base-vectorof-eager ctc))
(define check (check-vectorof ctc))
(define immutable (base-vectorof-immutable ctc))
(define vfp (get/build-collapsible-late-neg-projection elem-ctc))
(λ (blame)
(define pos-blame (blame-add-element-of-context blame))
(define neg-blame (blame-add-element-of-context blame #:swap? #t))
(define vfp (get/build-late-neg-projection elem-ctc))
(define-values (filled? elem-pos-proj elem-neg-proj)
(contract-pos/neg-doubling (vfp pos-blame) (vfp neg-blame)))
(define-values (checked-ref checked-set)
(define-values (filled? maybe-elem-pos-proj maybe-c-c-pos maybe-elem-neg-proj maybe-c-c-neg)
(contract-pos/neg-doubling.2 (vfp pos-blame) (vfp neg-blame)))
(define-values (fetch-tc-pos fetch-tc-neg)
(cond
[filled? (values #f #f)]
[else
(define tc-pos (make-thread-cell #f))
(define tc-neg (make-thread-cell #f))
(define (fetch-from-tc tc maybe-elem-proj maybe-c-c)
(cond
[(thread-cell-ref tc) => values]
[else
(define-values (elem-proj c-c) (maybe-elem-proj))
(define pr (cons elem-proj c-c))
(thread-cell-set! tc pr)
pr]))
(values (λ () (fetch-from-tc tc-pos maybe-elem-pos-proj maybe-c-c-pos))
(λ () (fetch-from-tc tc-neg maybe-elem-neg-proj maybe-c-c-neg)))]))
(define c-c-vector
(cond
[filled? (build-collapsible-vector maybe-c-c-pos maybe-c-c-neg ctc blame chap-not-imp?)]
[else
(build-doubling-collapsible-vector (λ () (cdr (fetch-tc-pos)))
(λ () (cdr (fetch-tc-neg)))
ctc blame chap-not-imp?)]))
(define checked-ref
(cond
[filled?
(define checked-ref (λ (neg-party)
(define blame+neg-party (cons pos-blame neg-party))
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
(elem-pos-proj val neg-party)))))
(define checked-set (λ (neg-party)
(define blame+neg-party (cons neg-blame neg-party))
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
(elem-neg-proj val neg-party)))))
(values checked-ref checked-set)]
(λ (neg-party)
(define blame+neg-party (cons pos-blame neg-party))
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
(maybe-elem-pos-proj val neg-party))))]
[else
(define ref-tc (make-thread-cell #f))
(define set-tc (make-thread-cell #f))
(define checked-ref (λ (neg-party)
(define blame+neg-party (cons pos-blame neg-party))
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
(define real-elem-pos-proj
(cond
[(thread-cell-ref ref-tc) => values]
[else
(define real-elem-pos-proj (elem-pos-proj))
(thread-cell-set! ref-tc real-elem-pos-proj)
real-elem-pos-proj]))
(real-elem-pos-proj val neg-party)))))
(define checked-set (λ (neg-party)
(define blame+neg-party (cons neg-blame neg-party))
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
(define real-elem-neg-proj
(cond
[(thread-cell-ref set-tc) => values]
[else
(define real-elem-neg-proj (elem-neg-proj))
(thread-cell-set! set-tc real-elem-neg-proj)
real-elem-neg-proj]))
(real-elem-neg-proj val neg-party)))))
(values checked-ref checked-set)]))
(cond
[(flat-contract? elem-ctc)
(define p? (flat-contract-predicate elem-ctc))
(λ (val neg-party)
(define (raise-blame val . args)
(apply raise-blame-error blame #:missing-party neg-party val args))
(check val raise-blame #f)
;; avoid traversing large vectors
;; unless `eager` is specified
(cond
[(and (or (equal? eager #t)
(and eager (<= (vector-length val) eager)))
(immutable? val)
(not (chaperone? val)))
(for ([e (in-vector val)])
(unless (p? e)
(elem-pos-proj e neg-party)))
val]
[else
(chaperone-or-impersonate-vector
val
(checked-ref neg-party)
(checked-set neg-party)
impersonator-prop:contracted ctc
impersonator-prop:blame (blame-add-missing-party blame neg-party))]))]
[else
(λ (val neg-party)
(define (raise-blame val . args)
(apply raise-blame-error blame #:missing-party neg-party val args))
(check val raise-blame #f)
(cond
[(and (immutable? val) (not (chaperone? val)))
(vector->immutable-vector
(for/vector #:length (vector-length val) ([e (in-vector val)])
(elem-pos-proj e neg-party)))]
[else
(chaperone-or-impersonate-vector
val
(checked-ref neg-party)
(checked-set neg-party)
impersonator-prop:contracted ctc
impersonator-prop:blame (blame-add-missing-party blame neg-party))]))]))))
(λ (neg-party)
(define blame+neg-party (cons pos-blame neg-party))
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
(define elem-pos-proj (car (fetch-tc-pos)))
(elem-pos-proj val neg-party))))]))
(define checked-set
(cond
[filled?
(λ (neg-party)
(define blame+neg-party (cons neg-blame neg-party))
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
(maybe-elem-neg-proj val neg-party))))]
[else
(λ (neg-party)
(define blame+neg-party (cons neg-blame neg-party))
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
(define elem-neg-proj (car (fetch-tc-neg)))
(elem-neg-proj val neg-party))))]))
(define p? (and (flat-contract-struct? elem-ctc)
(flat-contract-predicate elem-ctc)))
(define late-neg-proj
(λ (val neg-party)
(check-vectorof elem-ctc immutable val blame neg-party #f #t)
(define immutable-non-chaperone?
(and (immutable? val) (not (chaperone? val))))
;; avoid traversing large vectors
;; unless `eager` is specified
(cond
[(and flat-subcontract?
immutable-non-chaperone?
(or (equal? eager #t)
(and eager (<= (vector-length val) eager))))
(define elem-pos-proj (if filled?
maybe-elem-pos-proj
(car (fetch-tc-pos))))
(for ([e (in-vector val)])
(unless (p? e)
(elem-pos-proj e neg-party)))
val]
[(and (not flat-subcontract?) immutable-non-chaperone?)
(define elem-pos-proj (if filled?
maybe-elem-pos-proj
(car (fetch-tc-pos))))
(vector->immutable-vector
(for/vector #:length (vector-length val) ([e (in-vector val)])
(elem-pos-proj e neg-party)))]
[else
(define old-c-c-prop (get-impersonator-prop:collapsible val #f))
(define safe-for-c-c?
(if old-c-c-prop
(and (collapsible-property? old-c-c-prop)
(eq? (collapsible-property-ref old-c-c-prop) val))
(not (impersonator? val))))
(define wrapper-count
(if (collapsible-count-property? old-c-c-prop)
(collapsible-count-property-count old-c-c-prop)
0))
(cond
[(not safe-for-c-c?)
(chaperone-or-impersonate-vector
val
(checked-ref neg-party)
(checked-set neg-party)
impersonator-prop:contracted ctc
impersonator-prop:blame (cons blame neg-party))]
[(wrapper-count . >= . COLLAPSIBLE-LIMIT)
(vector-enter-collapsible-mode/collapse
c-c-vector
val
neg-party
old-c-c-prop
chap-not-imp?)]
[(collapsible-wrapper-property? old-c-c-prop)
(vector-enter-collapsible-mode/continue
c-c-vector
val
neg-party
(collapsible-property-c-c old-c-c-prop)
(collapsible-property-neg-party old-c-c-prop)
(collapsible-wrapper-property-checking-wrapper old-c-c-prop)
chap-not-imp?)]
[else
(define c-c-prop
(collapsible-count-property
c-c-vector
neg-party
#f
(add1 wrapper-count)
(or old-c-c-prop val)))
(define wrapped
(chaperone-or-impersonate-vector
val
(checked-ref neg-party)
(checked-set neg-party)
impersonator-prop:collapsible c-c-prop))
(set-collapsible-property-ref! c-c-prop wrapped)
wrapped])])))
(values
late-neg-proj
c-c-vector))))
(define-values (prop:neg-blame-party prop:neg-blame-party? prop:neg-blame-party-get)
(make-impersonator-property 'prop:neg-blame-party))
@ -270,7 +319,7 @@
#:first-order vectorof-first-order
#:equivalent vectorof-equivalent
#:stronger vectorof-stronger
#:late-neg-projection (vectorof-late-neg-ho-projection chaperone-vector)))
#:collapsible-late-neg-projection (vectorof-collapsible-late-neg-ho-projection #t)))
(define-struct (impersonator-vectorof base-vectorof) ()
#:property prop:custom-write custom-write-property-proc
@ -280,7 +329,7 @@
#:first-order vectorof-first-order
#:equivalent vectorof-equivalent
#:stronger vectorof-stronger
#:late-neg-projection (vectorof-late-neg-ho-projection impersonate-vector)))
#:collapsible-late-neg-projection (vectorof-collapsible-late-neg-ho-projection #f)))
(define-syntax (wrap-vectorof stx)
(syntax-case stx ()
@ -331,8 +380,6 @@
(define/subexpression-pos-prop (vector-immutableof c)
(vectorof c #:immutable #t))
(define-struct base-vector/c (elems immutable))
(define (vector/c-name c)
(let ([immutable (base-vector/c-immutable c)])
(apply build-compound-type-name 'vector/c
@ -346,32 +393,6 @@
(list '#:immutable immutable)
null)))))
(define (check-vector/c ctc val blame neg-party)
(define elem-ctcs (base-vector/c-elems ctc))
(define immutable (base-vector/c-immutable ctc))
(unless (vector? val)
(raise-blame-error blame #:missing-party neg-party val
'(expected: "a vector" given: "~e") val))
(cond
[(eq? immutable #t)
(unless (immutable? val)
(raise-blame-error blame #:missing-party neg-party val
'(expected: "an immutable vector" given: "~e")
val))]
[(eq? immutable #f)
(when (immutable? val)
(raise-blame-error blame #:missing-party neg-party val
'(expected: "a mutable vector" given: "~e")
val))]
[else (void)])
(define elem-count (length elem-ctcs))
(unless (= (vector-length val) elem-count)
(raise-blame-error blame #:missing-party neg-party val
'(expected: "a vector of ~a element~a" given: "~e")
elem-count
(if (= elem-count 1) "" "s")
val)))
(define (vector/c-first-order ctc)
(define elem-ctcs (base-vector/c-elems ctc))
(define immutable (base-vector/c-immutable ctc))
@ -442,88 +463,184 @@
#:stronger vector/c-stronger
#:equivalent vector/c-equivalent
#:late-neg-projection
(λ (ctc)
(λ (ctc)
(define elems (base-vector/c-elems ctc))
(define immutable (base-vector/c-immutable ctc))
(λ (blame)
(define blame+ctxt (blame-add-element-of-context blame))
(define val+np-acceptors
(for/list ([c (in-list (base-vector/c-elems ctc))])
(for/list ([c (in-list elems)])
((get/build-late-neg-projection c) blame+ctxt)))
(λ (val neg-party)
(check-vector/c ctc val blame neg-party)
(check-vector/c val blame immutable (length elems) neg-party)
(for ([e (in-vector val)]
[p (in-list val+np-acceptors)])
(p e neg-party))
val)))))
(define (vector/c-ho-late-neg-projection vector-wrapper)
(define (vector/c-collapsible-late-neg-ho-projection chap-not-imp?)
(define vector-wrapper (if chap-not-imp? chaperone-vector impersonate-vector))
(λ (ctc)
(let ([elem-ctcs (base-vector/c-elems ctc)]
[immutable (base-vector/c-immutable ctc)])
(λ (blame)
(define-values (filled? maybe-elem-pos-projs maybe-elem-neg-projs)
(contract-pos/neg-doubling
(for/vector #:length (length elem-ctcs)
([c (in-list elem-ctcs)]
[i (in-naturals)])
((get/build-late-neg-projection c)
(blame-add-context blame (nth-element-of i))))
(for/vector #:length (length elem-ctcs)
([c (in-list elem-ctcs)]
[i (in-naturals)])
((get/build-late-neg-projection c)
(blame-add-context blame (nth-element-of i) #:swap? #t)))))
(cond
[filled?
(λ (val neg-party)
(check-vector/c ctc val blame neg-party)
(define blame+neg-party (cons blame neg-party))
(if (and (immutable? val) (not (chaperone? val)))
(apply vector-immutable
(for/list ([e (in-vector val)]
[i (in-naturals)])
((vector-ref maybe-elem-pos-projs i) e neg-party)))
(vector-wrapper
val
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
((vector-ref maybe-elem-pos-projs i) val neg-party)))
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
((vector-ref maybe-elem-neg-projs i) val neg-party)))
impersonator-prop:contracted ctc
impersonator-prop:blame blame)))]
[else
(define pos-tc (make-thread-cell #f))
(define neg-tc (make-thread-cell #f))
(define (get-projs tc get-ele-projs)
(cond
[(thread-cell-ref tc) => values]
[else
(define projs (get-ele-projs))
(thread-cell-set! tc projs)
projs]))
(λ (val neg-party)
(check-vector/c ctc val blame neg-party)
(define blame+neg-party (cons blame neg-party))
(if (and (immutable? val) (not (chaperone? val)))
(apply vector-immutable
(for/list ([e (in-vector val)]
[i (in-naturals)])
((vector-ref (get-projs pos-tc maybe-elem-pos-projs) i) e neg-party)))
(vector-wrapper
val
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
((vector-ref (get-projs pos-tc maybe-elem-pos-projs) i) val neg-party)))
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
((vector-ref (get-projs neg-tc maybe-elem-neg-projs) i) val neg-party)))
impersonator-prop:contracted ctc
impersonator-prop:blame blame)))])))))
(define elem-ctcs (base-vector/c-elems ctc))
(define immutable (base-vector/c-immutable ctc))
(define elems-length (length elem-ctcs))
(define selnps
(for/list ([elem-ctc (in-list elem-ctcs)])
(get/build-collapsible-late-neg-projection elem-ctc)))
(λ (blame)
(define-values (filled? maybe-elem-pos-projs maybe-c-c-poss maybe-elem-neg-projs maybe-c-c-negs)
(contract-pos/neg-doubling.2
(let ()
(define elem-pos-projs (make-vector elems-length #f))
(define elem-c-c-poss (make-vector elems-length #f))
(for ([selnp (in-list selnps)]
[i (in-naturals)])
(define pos-blame (blame-add-context blame (nth-element-of i)))
(define-values (elem-pos-proj elem-c-c-pos) (selnp pos-blame))
(vector-set! elem-pos-projs i elem-pos-proj)
(vector-set! elem-c-c-poss i elem-c-c-pos))
(values elem-pos-projs elem-c-c-poss))
(let ()
(define elem-neg-projs (make-vector elems-length #f))
(define elem-c-c-negs (make-vector elems-length #f))
(for ([selnp (in-list selnps)]
[i (in-naturals)])
(define neg-blame (blame-add-context blame (nth-element-of i)
#:swap? #t))
(define-values (elem-neg-proj elem-c-c-neg) (selnp neg-blame))
(vector-set! elem-neg-projs i elem-neg-proj)
(vector-set! elem-c-c-negs i elem-c-c-neg))
(values elem-neg-projs elem-c-c-negs))))
(define-values (fetch-tc-pos fetch-tc-neg)
(cond
[filled? (values (void) (void))]
[else
(define tc-pos (make-thread-cell #f))
(define tc-neg (make-thread-cell #f))
(values (λ ()
(cond
[(thread-cell-ref tc-pos) => values]
[else
(define-values (elem-pos-projs maybe-c-c-pos) (maybe-elem-pos-projs))
(define pr (cons elem-pos-projs maybe-c-c-pos))
(thread-cell-set! tc-pos pr)
pr]))
(λ ()
(cond
[(thread-cell-ref tc-neg) => values]
[else
(define-values (elem-neg-projs maybe-c-c-neg) (maybe-elem-neg-projs))
(define pr (cons elem-neg-projs maybe-c-c-neg))
(thread-cell-set! tc-neg pr)
pr])))]))
(define c-c-vector
(cond
[filled?
(build-collapsible-vector maybe-c-c-poss maybe-c-c-negs ctc blame chap-not-imp?)]
[else
(build-doubling-collapsible-vector (λ () (cdr (fetch-tc-pos)))
(λ () (cdr (fetch-tc-neg)))
ctc blame chap-not-imp?)]))
(define chaperone-get-proc
(cond
[filled?
(λ (neg-party blame+neg-party)
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
((vector-ref maybe-elem-pos-projs i) val neg-party))))]
[else
(λ (neg-party blame+neg-party)
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
(define elem-pos-projs (car (fetch-tc-pos)))
((vector-ref elem-pos-projs i) val neg-party))))]))
(define chaperone-set-proc
(cond
[filled?
(λ (neg-party blame+neg-party)
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
((vector-ref maybe-elem-neg-projs i) val neg-party))))]
[else
(λ (neg-party blame+neg-party)
(λ (vec i val)
(with-contract-continuation-mark
blame+neg-party
(define elem-neg-projs (car (fetch-tc-neg)))
((vector-ref elem-neg-projs i) val neg-party))))]))
(define late-neg-proj
(λ (val neg-party)
(define old-c-c-prop (get-impersonator-prop:collapsible val #f))
(define safe-for-c-c
(if old-c-c-prop
(and (collapsible-property? old-c-c-prop)
(eq? (collapsible-property-ref old-c-c-prop) val))
(not (impersonator? val))))
(define wrapper-count
(if (collapsible-count-property? old-c-c-prop)
(collapsible-count-property-count old-c-c-prop)
0))
(check-vector/c val blame immutable elems-length neg-party)
(define blame+neg-party (cons blame neg-party))
(cond
[(and (immutable? val) (not (chaperone? val)))
(define elem-pos-projs
(if filled?
maybe-elem-pos-projs
(car (fetch-tc-pos))))
(apply vector-immutable
(for/list ([i (in-naturals)]
[elem-val (in-vector val)])
((vector-ref elem-pos-projs i) elem-val neg-party)))]
[(not safe-for-c-c)
(vector-wrapper
val
(chaperone-get-proc neg-party blame+neg-party)
(chaperone-set-proc neg-party blame+neg-party)
;; TODO: should this be a collapsible property instead??
impersonator-prop:contracted ctc
impersonator-prop:blame blame+neg-party)]
[(wrapper-count . >= . COLLAPSIBLE-LIMIT)
(vector-enter-collapsible-mode/collapse
c-c-vector
val
neg-party
old-c-c-prop
chap-not-imp?)]
[(collapsible-wrapper-property? old-c-c-prop)
(vector-enter-collapsible-mode/continue
c-c-vector
val
neg-party
(collapsible-property-c-c old-c-c-prop)
(collapsible-property-neg-party old-c-c-prop)
(collapsible-wrapper-property-checking-wrapper old-c-c-prop)
chap-not-imp?)]
[else
(define c-c-prop
(collapsible-count-property
c-c-vector
neg-party
#f
(add1 wrapper-count)
(or old-c-c-prop val)))
(define wrapped
(vector-wrapper
val
(chaperone-get-proc neg-party blame+neg-party)
(chaperone-set-proc neg-party blame+neg-party)
impersonator-prop:collapsible c-c-prop))
(set-collapsible-property-ref! c-c-prop wrapped)
wrapped])))
(values
late-neg-proj
c-c-vector))))
(define-struct (chaperone-vector/c base-vector/c) ()
#:property prop:custom-write custom-write-property-proc
@ -532,8 +649,8 @@
#:name vector/c-name
#:first-order vector/c-first-order
#:stronger vector/c-stronger
#:equivalent vector/c-equivalent
#:late-neg-projection (vector/c-ho-late-neg-projection chaperone-vector)))
#:collapsible-late-neg-projection (vector/c-collapsible-late-neg-ho-projection #t)
#:equivalent vector/c-equivalent))
(define-struct (impersonator-vector/c base-vector/c) ()
#:property prop:custom-write custom-write-property-proc
@ -542,8 +659,8 @@
#:name vector/c-name
#:first-order vector/c-first-order
#:stronger vector/c-stronger
#:equivalent vector/c-equivalent
#:late-neg-projection (vector/c-ho-late-neg-projection impersonate-vector)))
#:collapsible-late-neg-projection (vector/c-collapsible-late-neg-ho-projection #f)
#:equivalent vector/c-equivalent))
(define-syntax (wrap-vector/c stx)
(syntax-case stx ()

View File

@ -10,7 +10,8 @@
"../contract/combinator.rkt"
(only-in "../contract/private/arrow-val-first.rkt" ->-internal ->*-internal)
(only-in "../contract/private/case-arrow.rkt" case->-internal)
(only-in "../contract/private/arr-d.rkt" ->d-internal))
(only-in "../contract/private/arr-d.rkt" ->d-internal)
(submod "../contract/private/collapsible-common.rkt" properties))
(provide make-class/c class/c-late-neg-proj
blame-add-method-context blame-add-field-context blame-add-init-context