diff --git a/collects/racket/set.rkt b/collects/racket/set.rkt index 9430c2febc..1e6dbd18c1 100644 --- a/collects/racket/set.rkt +++ b/collects/racket/set.rkt @@ -85,6 +85,28 @@ (lambda (set hc) (add1 (hc (set-ht set))))) #:property prop:sequence (lambda (v) (*in-set v))) +;; Not currently exporting this because I'm not sure whether this is the right semantics +;; for it yet, but it follows most closely the semantics of the old set/c implementation +;; (while still returning a chaperone). +(define (chaperone-set s elem-chaperone) + (when (or (set-eq? s) + (set-eqv? s)) + (raise-type-error 'chaperone-set "equal-based set" s)) + (chaperone-struct s + set-ht + (let ([cached-ht #f]) + (λ (st ht) + (if cached-ht cached-ht + (let ([new-ht (make-immutable-hash + (hash-map ht (λ (k v) + ;; should be a check of the return here, + ;; but until this is exported, it's only + ;; used by set/c, which is sure to pass + ;; a chaperone-respecting function. + (cons (elem-chaperone s k) v))))]) + (set! cached-ht new-ht) + new-ht)))))) + (define (set . elems) (make-set (make-immutable-hash (map (lambda (k) (cons k #t)) elems)))) (define (seteq . elems) @@ -328,9 +350,19 @@ (raise-type-error 'set/c "(or/c 'dont-care 'equal? 'eq? 'eqv)" cmp)) - (if (flat-contract? ctc) - (flat-set/c ctc cmp (flat-contract-predicate ctc)) - (make-set/c ctc cmp))) + (cond + [(flat-contract? ctc) + (flat-set/c ctc cmp (flat-contract-predicate ctc))] + [(chaperone-contract? ctc) + (if (memq cmp '(eq eqv)) + (raise-type-error 'set/c + "flat contract" + ctc) + (make-set/c ctc cmp))] + [else + (raise-type-error 'set/c + "chaperone contract" + ctc)])) set/c)) (define (set/c-name c) @@ -347,45 +379,51 @@ (contract-stronger? (set/c-ctc this) (set/c-ctc that)))) +(define (check-set/c ctc) + (let ([elem-ctc (set/c-ctc ctc)] + [pred (get-pred ctc)] + [name (get-name ctc)]) + (λ (val fail [first-order? #f]) + (unless (pred val) + (fail "expected a <~a>, got ~a" name val)) + (when first-order? + (for ([e (in-set val)]) + (unless (contract-first-order-passes? elem-ctc e) + (fail "expected: ~s, got ~v" (contract-name elem-ctc) e)))) + #t))) + +(define (set/c-first-order ctc) + (let ([check (check-set/c ctc)]) + (λ (val) + (let/ec return + (check val (λ _ (return #f)) #t))))) + (define (set/c-proj c) (let ([proj (contract-projection (set/c-ctc c))] - [pred (get-pred c)]) + [check (check-set/c c)]) (λ (blame) (let ([pb (proj blame)]) (λ (s) - (if (pred s) - (cond - [(set-equal? s) - (for/set ((e (in-set s))) - (pb e))] - [(set-eqv? s) - (for/seteqv ((e (in-set s))) - (pb e))] - [(set-eq? s) - (for/seteq ((e (in-set s))) - (pb e))]) - (raise-blame-error - blame - s - "expected a <~a>, got ~v" - (get-name c) - s))))))) + (check s (λ args (apply raise-blame-error blame s args))) + (chaperone-set s (λ (s v) (pb v)))))))) (define-struct set/c (ctc cmp) - #:property prop:contract - (build-contract-property + #:property prop:chaperone-contract + (build-chaperone-contract-property #:name set/c-name - #:first-order get-pred + #:first-order set/c-first-order #:stronger set/c-stronger #:projection set/c-proj)) -(define (flat-first-order c) - (let ([inner-pred (flat-set/c-pred c)] - [pred (get-pred c)]) - (λ (s) - (and (pred s) - (for/and ((e (in-set s))) - (inner-pred e)))))) +(define (flat-set/c-proj c) + (let ([proj (contract-projection (set/c-ctc c))] + [check (check-set/c c)]) + (λ (blame) + (let ([pb (proj blame)]) + (λ (val) + (check val (λ args (apply raise-blame-error blame val args))) + (for ([e (in-set val)]) (pb e)) + val))))) (define-values (flat-set/c flat-set/c-pred) (let () @@ -393,9 +431,9 @@ #:property prop:flat-contract (build-flat-contract-property #:name set/c-name - #:first-order flat-first-order + #:first-order set/c-first-order #:stronger set/c-stronger - #:projection set/c-proj)) + #:projection flat-set/c-proj)) (values make-flat-set/c flat-set/c-pred))) ;; ---- diff --git a/collects/scribblings/reference/sets.scrbl b/collects/scribblings/reference/sets.scrbl index 1f62220f0b..01e6e755fe 100644 --- a/collects/scribblings/reference/sets.scrbl +++ b/collects/scribblings/reference/sets.scrbl @@ -207,12 +207,16 @@ Returns @racket[#t] if @racket[st] compares elements with @racket[eqv?], Returns @racket[#t] if @racket[st] compares elements with @racket[eq?], @racket[#f] if it compares with @racket[equal?] or @racket[eqv?].} -@defproc[(set/c [contract contract?] [#:cmp cmp (or/c 'dont-care 'equal 'eqv 'eq) 'dont-care]) contract?]{ +@defproc[(set/c [contract chaperone-contract?] [#:cmp cmp (or/c 'dont-care 'equal 'eqv 'eq) 'dont-care]) contract?]{ Constructs a contract that recognizes sets whose elements match @racket[contract]. If @racket[cmp] is @racket['dont-care], then the equality notion of the set is not considered when checking the contract. Otherwise, the contract accepts only sets with the corresponding notion of equality. + + If @racket[cmp] is @racket['eq] or @racket['eqv], then @racket[contract] must be a flat contract. + If @racket[contract] is not a flat contract, then @racket[cmp] cannot be @racket['eq] or @racket['eqv] + and the resulting contract can only be applied to sets that use @racket[equal?] for equality. } @defproc[(in-set [st set?]) sequence?]{ diff --git a/collects/tests/racket/contract-test.rktl b/collects/tests/racket/contract-test.rktl index 18efa685c3..a1e44e51f7 100644 --- a/collects/tests/racket/contract-test.rktl +++ b/collects/tests/racket/contract-test.rktl @@ -9385,6 +9385,19 @@ so that propagation occurs. (struct/c s alpha))) (ctest #t flat-contract? (set/c integer?)) + (ctest #f flat-contract? (set/c (-> integer? integer?))) + (ctest #t chaperone-contract? (set/c (-> integer? integer?))) + + ;; Make sure that impersonators cannot be used as the element contract in set/c. + (contract-error-test + 'contract-error-test-set + '(let ([proxy-ctc + (make-contract + #:name 'proxy-ctc + #:first-order values + #:projection (λ (b) values))]) + (set/c proxy-ctc)) + exn:fail?) ;; Hash contracts with flat domain/range contracts (ctest #t contract? (hash/c any/c any/c #:immutable #f)) @@ -9941,7 +9954,7 @@ so that propagation occurs. (test-name '(set/c boolean? #:cmp 'equal) (set/c boolean? #:cmp 'equal)) (test-name '(set/c char? #:cmp 'eq) (set/c char? #:cmp 'eq)) (test-name '(set/c (set/c char?) #:cmp 'eqv) (set/c (set/c char? #:cmp 'dont-care) #:cmp 'eqv)) - (test-name '(set/c (-> char? char?) #:cmp 'eqv) (set/c (-> char? char?) #:cmp 'eqv)) + (test-name '(set/c (-> char? char?) #:cmp 'equal) (set/c (-> char? char?) #:cmp 'equal)) ;; NOT YET RELEASED #;