diff --git a/pkgs/racket-test/tests/units/test-unit-contracts.rkt b/pkgs/racket-test/tests/units/test-unit-contracts.rkt index 04c6b473f3..a14b9bf169 100644 --- a/pkgs/racket-test/tests/units/test-unit-contracts.rkt +++ b/pkgs/racket-test/tests/units/test-unit-contracts.rkt @@ -1069,3 +1069,193 @@ (unit/c (import) (export x^) (init-depend))) (void)) + +;; ---------------------------------------- +;; Ensure contracted bindings have the right scopes across modules (racket/racket#1652) + +(parameterize ([current-namespace (make-base-namespace)]) + (eval + '(module sig racket/base + (require racket/contract racket/unit) + (provide foo^) + (define-signature foo^ + [foo? + (contracted [make-foo (-> foo?)])]))) + (eval + '(module unit racket/base + (require racket/unit 'sig) + (provide foo@) + (define-unit foo@ + (import) + (export foo^) + (define (foo? x) #f) + (define (make-foo) #f)))) + (eval + '(module use racket/base + (require racket/unit 'unit) + (define-values/invoke-unit/infer foo@) + (make-foo))) + (test-runtime-error exn:fail:contract? + "make-foo: broke its own contract" + (dynamic-require ''use #f))) + +;; ---------------------------------------- +;; Ellipses in signature bodies should not cause problems due to syntax template misuse + +(parameterize ([current-namespace (make-base-namespace)]) + (eval + '(module m racket/base + (require racket/contract + racket/unit) + + (provide result) + + (define-signature foo^ + [(contracted [foo (-> integer? ... integer?)])]) + + (define-unit foo@ + (import) + (export foo^) + (define (foo . xs) (apply + xs))) + + (define (foo+1@ foo-base@) + (define-values/invoke-unit foo-base@ + (import) + (export (prefix base: foo^))) + (unit + (import) + (export foo^) + (define (foo . xs) (add1 (apply base:foo xs))))) + + (define-values/invoke-unit (foo+1@ foo@) + (import) + (export foo^)) + + (define result (foo 1 2 3)))) + (test 7 (dynamic-require ''m 'result))) + +;; ---------------------------------------- +;; Contracts that use other contracted bindings work when exporting through define-values/invoke-unit + +(parameterize ([current-namespace (make-base-namespace)]) + (eval + '(module m racket/base + (require racket/contract + racket/unit) + + (provide g) + + (define-signature a^ + [(contracted + [f (-> integer? contract?)] + [g (f 10)])]) + + (define-unit a@ + (import) + (export a^) + (define (f x) (>=/c x)) + (define g 11)) + + (define-values/invoke-unit a@ + (import) + (export a^)))) + (test 11 (dynamic-require ''m 'g))) + +;; ---------------------------------------- +;; Contracts in signatures that use bindings in the signature should work no matter what order +;; they’re declared in + +(let () + (define-signature a^ + [(contracted + [pred? (-> any/c boolean?)] + [proc (-> pred? pred?)])]) + + (define-signature b^ + [(contracted + [proc (-> pred? pred?)] + [pred? (-> any/c boolean?)])]) + + (define-values [a@ b@] + (let () + (define (pred? x) (integer? x)) + (define (proc x) (add1 x)) + + (values (unit-from-context a^) + (unit-from-context b^)))) + + (define-unit c@ + (import) + (export a^) + + (define (pred? x) (integer? x)) + (define (proc x) (add1 x))) + + (define-unit d@ + (import) + (export a^) + + (define (proc x) (add1 x)) + (define (pred? x) (integer? x))) + + (define-values/invoke-unit a@ + (import) + (export (prefix a: a^))) + (define-values/invoke-unit b@ + (import) + (export (prefix b: b^))) + (define-values/invoke-unit c@ + (import) + (export (prefix c: a^))) + (define-values/invoke-unit d@ + (import) + (export (prefix d: a^))) + + (test 11 (a:proc 10)) + (test 11 (b:proc 10)) + (test 11 (c:proc 10)) + (test 11 (d:proc 10)) + + (test-contract-error top-level "a:proc" "pred?" (a:proc 'not-an-integer)) + (test-contract-error top-level "b:proc" "pred?" (b:proc 'not-an-integer)) + (test-contract-error top-level "c:proc" "pred?" (c:proc 'not-an-integer)) + (test-contract-error top-level "d:proc" "pred?" (d:proc 'not-an-integer))) + +;; ---------------------------------------- +;; Signature definitions created with define-values-for-export are inside the contract boundary + +(let () + (define-signature a^ + [(contracted + [only-ints-please (-> integer? any/c)]) + (define-values-for-export [inside] (only-ints-please 'not-an-integer)) + inside]) + + (define-unit a@ + (import) + (export a^) + (define (only-ints-please x) x)) + + (define-values/invoke-unit/infer a@) + + (test 'not-an-integer inside)) + +;; ---------------------------------------- +;; Contracted bindings work even if the export is renamed + +(let () + (define-signature a^ + [(contracted + [ctc contract?] + [proc (-> ctc any/c)])]) + + (define-unit a@ + (import) + (export (rename a^ [val/c ctc])) + (define val/c integer?) + (define proc add1)) + + (define-values/invoke-unit/infer a@) + + (test 11 (proc 10)) + (test-contract-error top-level "proc" "integer?" (proc 'not-an-integer))) diff --git a/pkgs/racket-test/tests/units/test-unit.rkt b/pkgs/racket-test/tests/units/test-unit.rkt index ea50ee3e65..9f996c4a7a 100644 --- a/pkgs/racket-test/tests/units/test-unit.rkt +++ b/pkgs/racket-test/tests/units/test-unit.rkt @@ -2212,94 +2212,3 @@ (define a1 1) (define a2 2) (test 1 (invoke-unit v (import a2^)))) - -;; ---------------------------------------- -;; Ensure contracted bindings have the right scopes across modules (racket/racket#1652) - -(parameterize ([current-namespace (make-base-namespace)]) - (eval - '(module sig racket/base - (require racket/contract racket/unit) - (provide foo^) - (define-signature foo^ - [foo? - (contracted [make-foo (-> foo?)])]))) - (eval - '(module unit racket/base - (require racket/unit 'sig) - (provide foo@) - (define-unit foo@ - (import) - (export foo^) - (define (foo? x) #f) - (define (make-foo) #f)))) - (eval - '(module use racket/base - (require racket/unit 'unit) - (define-values/invoke-unit/infer foo@) - (make-foo))) - (test-runtime-error exn:fail:contract? - "make-foo: broke its own contract" - (dynamic-require ''use #f))) - -;; ---------------------------------------- -;; Ellipses in signature bodies should not cause problems due to syntax template misuse - -(parameterize ([current-namespace (make-base-namespace)]) - (eval - '(module m racket/base - (require racket/contract - racket/unit) - - (provide result) - - (define-signature foo^ - [(contracted [foo (-> integer? ... integer?)])]) - - (define-unit foo@ - (import) - (export foo^) - (define (foo . xs) (apply + xs))) - - (define (foo+1@ foo-base@) - (define-values/invoke-unit foo-base@ - (import) - (export (prefix base: foo^))) - (unit - (import) - (export foo^) - (define (foo . xs) (add1 (apply base:foo xs))))) - - (define-values/invoke-unit (foo+1@ foo@) - (import) - (export foo^)) - - (define result (foo 1 2 3)))) - (test 7 (dynamic-require ''m 'result))) - -;; ---------------------------------------- -;; Contracts that use other contracted bindings work when exporting through define-values/invoke-unit - -(parameterize ([current-namespace (make-base-namespace)]) - (eval - '(module m racket/base - (require racket/contract - racket/unit) - - (provide g) - - (define-signature a^ - [(contracted - [f (-> integer? contract?)] - [g (f 10)])]) - - (define-unit a@ - (import) - (export a^) - (define (f x) (>=/c x)) - (define g 11)) - - (define-values/invoke-unit a@ - (import) - (export a^)))) - (test 11 (dynamic-require ''m 'g))) diff --git a/racket/collects/racket/unit.rkt b/racket/collects/racket/unit.rkt index ab1baa7f51..6fa2bbdf11 100644 --- a/racket/collects/racket/unit.rkt +++ b/racket/collects/racket/unit.rkt @@ -17,8 +17,7 @@ "private/unit-compiletime.rkt" "private/unit-syntax.rkt")) -(require racket/block - racket/unsafe/undefined +(require racket/unsafe/undefined racket/contract/base racket/contract/region racket/stxparam @@ -602,7 +601,7 @@ #'(((int-sid ...) sbody) ...) #'(((int-vid ...) vbody) ...))))) -;; build-post-val-defs : sig -> (list syntax-object) +;; build-post-val-defs+ctcs : sig -> (list/c stx-list? (listof syntax?)) (define-for-syntax (build-post-val-defs+ctcs sig) (define introduced-sig (map-sig (lambda (x) x) (make-syntax-introducer) @@ -767,19 +766,15 @@ (x (identifier? #'x) (loop (cdr sig-exprs) (cons #'x bindings) val-defs stx-defs post-val-defs (cons #f ctcs))) - ((x (y z) ...) - (and (identifier? #'x) - (free-identifier=? #'x #'contracted) - (andmap identifier? (syntax->list #'(y ...)))) + ((contracted (y z) ...) + (andmap identifier? (syntax->list #'(y ...))) (loop (cdr sig-exprs) - (append (syntax->list #'(y ...)) bindings) + (append (reverse (syntax->list #'(y ...))) bindings) val-defs stx-defs post-val-defs - (append (syntax->list #'(z ...)) ctcs))) - ((x . z) - (and (identifier? #'x) - (free-identifier=? #'x #'contracted)) + (append (reverse (syntax->list #'(z ...))) ctcs))) + ((contracted . z) (raise-syntax-error 'define-signature "expected a list of [id contract] pairs after the contracted keyword" @@ -1102,6 +1097,14 @@ (list #'(define-values (id ...) rhs))))] [else (list defn-or-expr)]))) defns&exprs)))] + [ends-in-defn? + (syntax-case expanded-body () + [(_ ... (x . _)) + (and (identifier? #'x) + (member #'x + (list #'define-values #'define-syntaxes #'define-syntax) + free-identifier=?))] + [_ #f])] ;; Get all the defined names, sorting out variable definitions ;; from syntax definitions. [defined-names-table @@ -1160,51 +1163,75 @@ "definition for imported identifier" (var-info-id defid))))) (syntax->list (localify #'ivars def-ctx))) - - (let ([marker (lambda (id) ((make-syntax-introducer) (datum->syntax #f (syntax-e id))))]) - (with-syntax ([(defn-or-expr ...) - (apply append - (map (λ (defn-or-expr) - (syntax-case defn-or-expr (define-values) - [(define-values (id ...) body) - (let* ([ids (syntax->list #'(id ...))] - [tmps (map marker ids)] - [do-one - (λ (id tmp) - (let ([var-info (bound-identifier-mapping-get - defined-names-table - id)]) - (cond - [(var-info-exported? var-info) - => - (λ (export-loc) - (let ([ctc (var-info-ctc var-info)]) - (list (if ctc - (quasisyntax/loc defn-or-expr - (begin - (contract #,ctc #,tmp - (current-contract-region) - 'cant-happen - (quote #,id) - (quote-srcloc #,id)) - (set-box! #,export-loc - (cons #,tmp (current-contract-region))))) - (quasisyntax/loc defn-or-expr - (set-box! #,export-loc #,tmp))) - (quasisyntax/loc defn-or-expr - (define-syntax #,id - (make-id-mapper (quote-syntax #,tmp)))))))] - [else (list (quasisyntax/loc defn-or-expr - (define-syntax #,id - (make-rename-transformer (quote-syntax #,tmp)))))])))]) - (cons (quasisyntax/loc defn-or-expr - (define-values #,tmps body)) - (apply append (map do-one ids tmps))))] - [else (list defn-or-expr)])) - expanded-body))]) - (internal-definition-context-track - def-ctx - #'(block defn-or-expr ...))))))))) + + ;; Handles redirection of exported definitions and collects + ;; positive-blaming `contract` expressions + (define (process-defn-or-expr defn-or-expr) + (syntax-case defn-or-expr (define-values) + [(define-values (id ...) body) + (let* ([ids (syntax->list #'(id ...))] + [tmps (generate-temporaries ids)] + [do-one + (λ (id tmp) + (let ([var-info (bound-identifier-mapping-get + defined-names-table + id)]) + (cond + [(var-info-exported? var-info) + => + (λ (export-loc) + (let ([ctc (var-info-ctc var-info)]) + (values + #`(begin + #,(quasisyntax/loc defn-or-expr + (set-box! #,export-loc + #,(if ctc + #`(cons #,tmp (current-contract-region)) + tmp))) + #,(quasisyntax/loc defn-or-expr + (define-syntax #,id + (make-id-mapper (quote-syntax #,tmp))))) + (and ctc + #`(contract #,ctc #,tmp + (current-contract-region) + 'cant-happen + (quote #,id) + (quote-srcloc #,id))))))] + [else (values (quasisyntax/loc defn-or-expr + (define-syntax #,id + (make-rename-transformer (quote-syntax #,tmp)))) + #f)])))]) + (define-values (defns-and-exprs ctc-exprs) + (for/lists [defns-and-exprs ctc-exprs] + ([id (in-list ids)] + [tmp (in-list tmps)]) + (do-one id tmp))) + (list (cons (quasisyntax/loc defn-or-expr + (define-values #,tmps + #,(if (and (pair? ids) (null? (cdr ids))) + (syntax-property #'body 'inferred-name (car ids)) + #'body))) + defns-and-exprs) + (filter values ctc-exprs)))] + [else (list (list defn-or-expr) '())])) + + (internal-definition-context-track + def-ctx + (if (null? expanded-body) + #'(void) + (with-syntax ([([(defn-or-expr ...) (ctc-expr ...)] ...) + (map process-defn-or-expr expanded-body)]) + (if ends-in-defn? + #'(let () + defn-or-expr ... ... + ctc-expr ... ... + (void)) + (with-syntax ([(defn-or-expr ... last-defn-or-expr) #'(defn-or-expr ... ...)]) + #'(let () + defn-or-expr ... + (begin0 + last-defn-or-expr + ctc-expr ... ...)))))))))))) (define-for-syntax (redirect-imports/exports import?) (lambda (table-stx