#lang racket/load

(require "test-harness.rkt"
         racket/unit
         racket/contract
         rackunit)

(define temp-unit-blame-re "\\(unit temp[0-9]*\\)")
(define top-level "top-level")

(define (match-blame re msg)
  (or (regexp-match? (format "blaming: ~a" re) msg)
      (regexp-match? (format "broke its own contract:.*blaming: ~a" re) msg)))

(define (match-obj re msg)
  (or (regexp-match? (format "~a: contract violation" re) msg)
      (regexp-match? (format "~a: broke its own contract" re) msg)))

(define (get-ctc-err msg)
  (cond
    [(regexp-match  #rx"contract violation\n *([^\n]*)\n" msg)
     =>
     (λ (x) (cadr x))]
    [(regexp-match #rx"broke its own contract;?\n *([^\n]*)\n" msg)
     =>
     (lambda (x) (cadr x))]
    [else (error 'test-contract-error
                 (format "no specific error in message: \"~a\"" msg))]))

(define-syntax-rule (test-contract-error blame obj err expr)
  (test-contract-error/regexp
   (regexp-quote blame) (regexp-quote obj) (regexp-quote err)
   expr))

(define-syntax test-contract-error/regexp
  (syntax-rules ()
    ((_ blame obj err expr)
     (check-exn (λ (exn)
                  (and (exn:fail:contract? exn)
                       (let ([msg (exn-message exn)])
                         (and (match-blame blame msg)
                              (match-obj obj msg)
                              (regexp-match? err msg)))))
                (λ () expr)))))

(define-signature sig1
  ((contracted [x number?])))
(define-signature sig2
  ((contracted [f (-> number? number?)])))
(define-signature sig3 extends sig2
  ((contracted [g (-> number? boolean?)])))
(define-signature sig4
  ((contracted [a number?] [b (-> boolean? number?)])))
(define-signature sig5
  ((contracted [c string?])
   (contracted [d symbol?])))

(define-unit unit1
  (import)
  (export sig1)
  (define x #f))

(define-unit unit2
  (import sig1)
  (export sig2)
  (define (f n) x))

(define-unit unit3
  (import sig3 sig4)
  (export)

  (b (g a)))

(define-unit unit4
  (import sig3 sig4)
  (export)

  (g (b a)))

(define-unit unit5
  (import)
  (export sig5)

  (define-values (c d) (values "foo" 3)))

(test-syntax-error
 "misuse of define-signature keyword"
 contracted)
(test-syntax-error
 "expected a list of [id contract]"
  (define-signature x ((contracted x y))))
(test-syntax-error
 "expected a list of [id contract]"
  (define-signature x ((contracted [(-> number? number?) x]))))

(test-syntax-error
 "unbound identifier"
  (module h?-test racket
    (define-signature s^
      ((define-values (f?) (values number?))
       (define-syntaxes (g?) (make-rename-transformer #'number?))
       (contracted [f (-> f? (and/c g? h?))])))))

(test-syntax-error
 "undefined export"
  (unit (import) (export sig3 sig4)
        (define a #t)
        (define g zero?)
        (define (b t) (if t 3 0))))

(test-contract-error "(unit unit1)" "x" "number?"
  (invoke-unit unit1))

(test-contract-error "(unit unit1)" "x"  "number?"
  (invoke-unit (compound-unit (import) (export)
                              (link (((S1 : sig1)) unit1)
                                    (() unit2 S1)))))
(test-contract-error/regexp temp-unit-blame-re "a" "number?"
  (invoke-unit (compound-unit (import) (export)
                              (link (((S3 : sig3) (S4 : sig4))
                                     (unit (import) (export sig3 sig4)
                                           (define a #t)
                                           (define f add1)
                                           (define g zero?)
                                           (define (b t) (if t 3 0))))
                                    (() unit3 S3 S4)))))

(test-contract-error/regexp temp-unit-blame-re "g" "boolean?"
  (invoke-unit (compound-unit (import) (export)
                              (link (((S3 : sig3) (S4 : sig4))
                                     (unit (import) (export sig3 sig4)
                                           (define a 3)
                                           (define f add1)
                                           (define g values)
                                           (define (b t) (if t 3 0))))
                                    (() unit3 S3 S4)))))

(test-contract-error "(unit unit4)" "b" "boolean?"
  (invoke-unit (compound-unit (import) (export)
                              (link (((S3 : sig3) (S4 : sig4))
                                     (unit (import) (export sig3 sig4)
                                           (define a 3)
                                           (define f add1)
                                           (define g zero?)
                                           (define (b t) (if t 3 0))))
                                    (() unit4 S3 S4)))))

(test-contract-error "(unit unit5)" "d" "symbol?"
  (invoke-unit unit5))

(define-unit unit6
  (import)
  (export sig1)
  (define-unit unit6-1
    (import)
    (export sig1)
    (define x 3))
  (define-values/invoke-unit unit6-1
    (import)
    (export sig1)))

(invoke-unit unit6)

(define-signature sig6
  ((contracted [x boolean?])))

(define-unit unit7
  (import)
  (export sig6)
  (define-unit unit7-1
    (import)
    (export sig1)
    (define x 3))
  (define-values/invoke-unit unit7-1
    (import)
    (export sig1)))

(test-contract-error "(unit unit7)" "x" "boolean?"
  (invoke-unit unit7))

(define-unit unit8
  (import)
  (export)
  (define-unit unit8-1
    (import)
    (export sig2)
    (define f values))
  (define-values/invoke-unit unit8-1
    (import)
    (export sig2))
  (f #t))

(test-contract-error "(unit unit8)" "f" "number?"
  (invoke-unit unit8))

(define-unit unit9
  (import)
  (export)
  (define-unit unit9-1
    (import)
    (export sig2)
    (define f zero?))
  (define-values/invoke-unit unit9-1
    (import)
    (export sig2))
  (f 3))

(test-contract-error "(unit unit9-1)" "f" "number?"
  (invoke-unit unit9))

(define-values/invoke-unit
  (unit
   (import) (export sig2)
   (define f values))
  (import)
  (export sig2))

(test-contract-error top-level "f" "number?"
  (f #t))

(define-unit unit10
  (import sig1 sig2) (export)
  (if (zero? x)
      (f 3)
      (f #t)))

(let ()
  (define x 0)
  (define f (lambda (x) #t))
  (test-contract-error "(unit u)" "f" "number?"
    (invoke-unit unit10 (import sig1 sig2))))

(let ()
  (define x 1)
  (define f values)
  (test-contract-error "(unit unit10)" "f" "number?"
    (invoke-unit unit10 (import sig1 sig2))))

;; testing that contracts from extended signatures are checked properly
(define-unit unit11
  (import) (export sig3)
  (define (f n) #t)
  (define (g n) 3))

(let ()
  (define-values/invoke-unit unit11
    (import)
    (export sig3))
  (test-contract-error "(unit unit11)" "f" "number?"
    (f 3))
  (test-contract-error top-level "f" "number?"
    (f #t)))

;; unit/new-import-export tests

(define-signature sig7 (f))
(define-signature sig8 ((contracted [f (-> number? number?)])))
(define-signature sig9 ((contracted [f (-> number? number?)])))

;; All units that play nicely
(define-unit unit12
  (import sig7)
  (export)
  (f 3))
(define-unit unit13
  (import sig8)
  (export)
  (f 3))
(define-unit unit14
  (import)
  (export sig7)
  (define f (λ (n) 3)))
(define-unit unit15
  (import)
  (export sig8)
  (define f (λ (n) 3)))

;; All units that don't play nicely (or won't after converted)
(define-unit unit16
  (import sig7)
  (export)
  (f #t))
(define-unit unit17
  (import sig8)
  (export)
  (f #t))
(define-unit unit18
  (import)
  (export sig7)
  (define f (λ (n) #t)))
(define-unit unit19
  (import)
  (export sig8)
  (define f (λ (n) #t)))

;; Converting units without internal contract violations

;; uncontracted import -> contracted import
(define-unit/new-import-export unit20
  (import sig8)
  (export)
  (() unit12 sig7))
(let ()
  (define-compound-unit unit21
    (import)
    (export)
    (link [((S : sig8)) unit15]
          [() unit20 S]))
  (invoke-unit unit21))
(let ()
  (define-compound-unit unit22
    (import)
    (export)
    (link [((S : sig8)) unit19]
          [() unit20 S]))
  (test-contract-error "(unit unit19)" "f" "number?"
    (invoke-unit unit22)))

;; contracted import -> uncontracted import
(define-unit/new-import-export unit23
  (import sig7)
  (export)
  (() unit13 sig8))
(let ()
  (define-compound-unit unit24
    (import)
    (export)
    (link [((S : sig7)) unit14]
          [() unit23 S]))
  (invoke-unit unit24))
(let ()
  (define-compound-unit unit25
    (import)
    (export)
    (link [((S : sig7)) unit18]
          [() unit23 S]))
  (test-contract-error "(unit unit23)" "f" "number?"
    (invoke-unit unit25)))

;; contracted import -> contracted import
(define-unit/new-import-export unit26
  (import sig9)
  (export)
  (() unit13 sig8))
(let ()
  (define-unit unit27-1
    (import)
    (export sig9)
    (define (f n) 3))
  (define-compound-unit unit27-2
    (import)
    (export)
    (link [((S : sig9)) unit27-1]
          [() unit26 S]))
  (invoke-unit unit27-2))
(let ()
  (define-unit unit28-1
    (import)
    (export sig9)
    (define (f n) #f))
  (define-compound-unit unit28-2
    (import)
    (export)
    (link [((S : sig9)) unit28-1]
          [() unit26 S]))
  (test-contract-error "(unit unit28-1)" "f" "number?"
    (invoke-unit unit28-2)))

;; uncontracted export -> contracted export
(define-unit/new-import-export unit29
  (import)
  (export sig8)
  ((sig7) unit14))
(let ()
  (define-compound-unit unit30
    (import)
    (export)
    (link [((S : sig8)) unit29]
          [() unit13 S]))
  (invoke-unit unit30))
(let ()
  (define-compound-unit unit31
    (import)
    (export)
    (link [((S : sig8)) unit29]
          [() unit17 S]))
  (test-contract-error "(unit unit17)" "f" "number?"
    (invoke-unit unit31)))

;; contracted export -> uncontracted export
(define-unit/new-import-export unit32
  (import)
  (export sig7)
  ((sig8) unit15))
(let ()
  (define-compound-unit unit33
    (import)
    (export)
    (link [((S : sig7)) unit32]
          [() unit14 S]))
  (invoke-unit unit33))
(let ()
  (define-compound-unit unit34
    (import)
    (export)
    (link [((S : sig7)) unit32]
          [() unit16 S]))
  (test-contract-error "(unit unit32)" "f" "number?"
    (invoke-unit unit34)))

;; contracted export -> contracted export
(define-unit/new-import-export unit35
  (import)
  (export sig9)
  ((sig8) unit15))
(let ()
  (define-unit unit36-1
    (import sig9)
    (export)
    (f 3))
  (define-compound-unit unit36-2
    (import)
    (export)
    (link [((S : sig9)) unit35]
          [() unit36-1 S]))
  (invoke-unit unit36-2))
(let ()
  (define-unit unit37-1
    (import sig9)
    (export)
    (f #f))
  (define-compound-unit unit37-2
    (import)
    (export)
    (link [((S : sig9)) unit35]
          [() unit37-1 S]))
  (test-contract-error "(unit unit37-1)" "f" "number?"
    (invoke-unit unit37-2)))

;; Converting units with internal contract violations

;; uncontracted import -> contracted import
(define-unit/new-import-export unit38
  (import sig8)
  (export)
  (() unit16 sig7))
(let ()
  (define-compound-unit unit39
    (import)
    (export)
    (link [((S : sig8)) unit15]
          [() unit38 S]))
  (test-contract-error "(unit unit38)" "f" "number?"
    (invoke-unit unit39)))
(let ()
  (define-compound-unit unit40
    (import)
    (export)
    (link [((S : sig8)) unit19]
          [() unit38 S]))
  (test-contract-error "(unit unit38)" "f" "number?"
    (invoke-unit unit40)))

;; contracted import -> uncontracted import
(define-unit/new-import-export unit41
  (import sig7)
  (export)
  (() unit17 sig8))
(let ()
  (define-compound-unit unit42
    (import)
    (export)
    (link [((S : sig7)) unit14]
          [() unit41 S]))
  (test-contract-error "(unit unit17)" "f" "number?"
    (invoke-unit unit42)))
(let ()
  (define-compound-unit unit43
    (import)
    (export)
    (link [((S : sig7)) unit18]
          [() unit41 S]))
  (test-contract-error "(unit unit17)" "f" "number?"
    (invoke-unit unit43)))

;; contracted import -> contracted import
(define-unit/new-import-export unit44
  (import sig9)
  (export)
  (() unit17 sig8))
(let ()
  (define-unit unit45-1
    (import)
    (export sig9)
    (define (f n) 3))
  (define-compound-unit unit45-2
    (import)
    (export)
    (link [((S : sig9)) unit45-1]
          [() unit44 S]))
  (test-contract-error "(unit unit17)" "f" "number?"
    (invoke-unit unit45-2)))
(let ()
  (define-unit unit46-1
    (import)
    (export sig9)
    (define (f n) #t))
  (define-compound-unit unit46-2
    (import)
    (export)
    (link [((S : sig9)) unit46-1]
          [() unit44 S]))
  (test-contract-error "(unit unit17)" "f" "number?"
    (invoke-unit unit46-2)))

;; uncontracted export -> contracted export
(define-unit/new-import-export unit47
  (import)
  (export sig8)
  ((sig7) unit18))
(let ()
  (define-compound-unit unit48
    (import)
    (export)
    (link [((S : sig8)) unit47]
          [() unit13 S]))
  (test-contract-error "(unit unit47)" "f" "number?"
    (invoke-unit unit48)))
(let ()
  (define-compound-unit unit49
    (import)
    (export)
    (link [((S : sig8)) unit47]
          [() unit17 S]))
  (test-contract-error "(unit unit17)" "f" "number?"
    (invoke-unit unit49)))

;; contracted import -> uncontracted import
(define-unit/new-import-export unit50
  (import)
  (export sig7)
  ((sig8) unit19))
(let ()
  (define-compound-unit unit51
    (import)
    (export)
    (link [((S : sig7)) unit50]
          [() unit12 S]))
  (test-contract-error "(unit unit19)" "f" "number?"
    (invoke-unit unit51)))
(let ()
  (define-compound-unit unit52
    (import)
    (export)
    (link [((S : sig7)) unit50]
          [() unit16 S]))
  (test-contract-error "(unit unit50)" "f" "number?"
    (invoke-unit unit52)))

;; contracted export -> contracted export
(define-unit/new-import-export unit53
  (import)
  (export sig9)
  ((sig8) unit19))
(let ()
  (define-unit unit54-1
    (import sig9)
    (export)
    (f 3))
  (define-compound-unit unit54-2
    (import)
    (export)
    (link [((S : sig9)) unit53]
          [() unit54-1 S]))
  (test-contract-error "(unit unit19)" "f" "number?"
    (invoke-unit unit54-2)))
(let ()
  (define-unit unit55-1
    (import sig9)
    (export)
    (f #t))
  (define-compound-unit unit55-2
    (import)
    (export)
    (link [((S : sig9)) unit53]
          [() unit55-1 S]))
  (test-contract-error "(unit unit55-1)" "f" "number?"
    (invoke-unit unit55-2)))

(module m1 racket
  (define-signature foo^ (x))
  (define-signature bar^ (y))
  (provide foo^ bar^)
  
  (define-unit U@
    (import foo^)
    (export bar^)
    (define (y s)
      (if (eq? s 'bork)
          3
          (string-append (symbol->string s) " " (if (x 3) "was true on 3" "was not true on 3")))))
  (provide/contract [U@ (unit/c (import (foo^ [x (-> number? boolean?)]))
                                (export (bar^ [y (-> symbol? string?)])))]))

(module m2 racket
  (require 'm1)
  
  (define x zero?)
  (define-values/invoke-unit U@
    (import foo^)
    (export bar^))
  
  (define (z)
    (y 'a))
  (define (w)
    (y "foo"))
  (define (v)
    (y 'bork))
  
  (provide z w v))

(require (prefix-in m2: 'm2))

(m2:z)
(test-contract-error "m2" "U@" "symbol?" (m2:w))
(test-contract-error "m1" "U@" "string?" (m2:v))

(test-syntax-error
 "identifier not member of signature"
  (unit/c (import (sig1 [y number?]))
          (export)))
(test-syntax-error
 "duplicate identifier found"
  (unit/c (import)
          (export (sig1 [x string?] [x number?]))))
(test-syntax-error
 "unit/c: unknown signature"
  (unit/c (import faux^) (export)))

(test-contract-error "(definition bad-export@)" "bad-export@" "unit must export signature sig1"
  (let ()
    (define/contract bad-export@
      (unit/c (import) (export sig1))
      (unit (import) (export)))
    bad-export@))

(test-contract-error "(definition bad-import@)" "bad-import@" "contract does not list import sig1"
  (let ()
    (define/contract bad-import@
      (unit/c (import) (export))
      (unit (import sig1) (export) (+ x 1)))
    bad-import@))

(test-contract-error "(definition not-a-unit)" "not-a-unit" "not a unit"
  (let ()
    (define/contract not-a-unit
      (unit/c (import) (export))
      3)
    not-a-unit))

;; Adding a test to make sure that contracts can refer
;; to other parts of the signature.

(module m3 racket
  (define-signature toy-factory^
    ((contracted
      [build-toys (-> integer? (listof toy?))]
      [repaint    (-> toy? symbol? toy?)]
      [toy?       (-> any/c boolean?)]
      [toy-color  (-> toy? symbol?)])))

  (define-unit simple-factory@
    (import)
    (export toy-factory^)

    (define-struct toy (color) #:transparent)

    (define (build-toys n)
      (for/list ([i (in-range n)])
        (make-toy 'blue)))

    (define (repaint t col)
      (make-toy col)))

  (provide toy-factory^ simple-factory@))

(module m4 racket
  (define-signature foo^ (x? (contracted [f (-> x? boolean?)])))

  (define-unit U@
    (import)
    (export foo^)
    (define (x? x) #f)
    (define (f x) (x? x)))

  (define-values/invoke-unit/infer U@)
  
  (provide f x?))

(require (prefix-in m4: 'm4))

(test-contract-error "m4" "f" " x?"
  (m4:f 3))

(module m4:f racket
  (define-signature foo^ (x? (contracted [f (-> x? boolean?)])))

  (define-unit U@
    (import)
    (export foo^)
    (define (x? x) #f)
    (define (f x) (x? x)))

  (define-values/invoke-unit U@ (import) (export [prefix f: foo^]))
  
  (provide f:f f:x?))

(require (prefix-in m4: 'm4:f))

(test-contract-error "m4:f" "f:f" "x?"
  (m4:f:f 3))

(require (prefix-in m3: 'm3))

(test-contract-error top-level "build-toys" "integer?"
  (let ()
    (define-values/invoke-unit/infer m3:simple-factory@)
    (build-toys #f)))

(module m5 racket
  (define-signature foo^ (f (contracted [x? (-> any/c boolean?)])))
  
  (define-unit U@
    (import)
    (export foo^)
    (define (x? n) (zero? n))
    (define (f x) (x? x)))
  
  (provide foo^)
  (provide/contract
   [U@
    (unit/c (import)
            (export (foo^ [f (-> x? boolean?)])))]))

(require (prefix-in m5: 'm5))

(define-values/invoke-unit m5:U@
  (import)
  (export (prefix m5: m5:foo^)))

(m5:f 0)

(test-contract-error top-level "U@" " x?"
  (m5:f 3))

(let ()
  (define-signature foo^ (x? f))
  (define-signature bar^ ((contracted [x? (-> number? boolean?)]
                                      [f  (-> x? number?)])))
  (define-unit U@
    (import)
    (export bar^)
    (define x? zero?)
    (define f values))
  
  (define-unit/new-import-export V@
    (import)
    (export bar^)
    ((bar^) U@))
  
  (define-values/invoke-unit/infer V@)
  
  (f 0)
  (test-contract-error top-level "f" "zero?"
    (f 3)))

(let ()
  (define-signature foo^ ((contracted [x? (-> number? boolean?)]
                                      [f (-> x? number?)])))
  (define-signature bar^ (f (contracted [x? (-> any/c boolean?)])))
  (define-unit U@
    (import)
    (export foo^)
    (define x? zero?)
    (define f values))
  
  (define-unit/new-import-export V@
    (import)
    (export bar^)
    ((foo^) U@))
  
  (define-values/invoke-unit/infer V@)
  
  (f 0)
  (test-contract-error "(unit V@)" "f" "zero?"
    (f 3)))

(let ()
  (define-signature foo^ (x y))
  (define-unit/contract U@
    (import)
    (export (foo^ [x (-> number? number?)]))
    (define (x n) (zero? n))
    (define y 4))
  (define-unit V@
    (import foo^)
    (export)
    (x 4))
  (define-compound-unit/infer W@
    (import) (export) (link U@ V@))
  (define-values/invoke-unit/infer U@)
  y
  (test-contract-error top-level "U@" "number?"
    (x #t))
  (test-contract-error "(unit U@)" "U@" "number?"
    (x 3))
  (test-contract-error "(unit U@)" "U@" "number?"
    (invoke-unit W@)))

(let ()
  (define-signature foo^ (x? f))
  (define-unit/contract U@
    (import)
    (export (foo^ [f (-> x? number?)]))
    (define (x? n) (or (= n 3)
                       (zero? n)))
    (define (f n) (if (= n 3) #t n)))
  (define-unit V@
    (import foo^)
    (export)
    (test-contract-error top-level "U@" " x?"
      (f 2))
    (test-contract-error "(unit U@)" "U@" " number?"
      (f 3)))
  (define-compound-unit/infer W@
    (import) (export) (link U@ V@))
  (define-values/invoke-unit/infer U@)
  (test-contract-error top-level "U@" " x?"
    (f 4))
  (test-contract-error "(unit U@)" "U@" "number?"
    (f 3))
  (invoke-unit W@))

(let ()
  (define-signature foo^
    ((contracted 
      [x? (-> number? boolean?)]
      [f (-> x? number?)])))
  
  (define-unit/contract foo@
    (import)
    (export (foo^ [x? (-> any/c boolean?)]))
    
    (define (x? n) (zero? n))
    (define (f x) 3))
  
  (define-values/invoke-unit/infer foo@)
  
  (f 0)
  (test-contract-error top-level "f" " x?"
    (f 4))
  ;; This is a weird one.  The definition for foo@ has two conflicting
  ;; contracts.  Who gets blamed?  Still the top-level, since foo@ can't
  ;; get blamed for breaking its own contract.  In theory you could say
  ;; that perhaps the top-level shouldn't be blamed, and that it should
  ;; just be an "overriding" contract, but a) that won't really work and
  ;; b) what about other units that might link with foo@, that expect
  ;; the stronger contract?
  (test-contract-error top-level "x?" "number?"
    (f #t)))

(let ()
  (define-signature student^
    ((struct/ctc student ([name string?] [id number?]))))
  (define-unit student@
    (import)
    (export student^)
    (struct student (name id)))
  (define-values/invoke-unit/infer student@)
  (student "foo" 3)
  (test-contract-error top-level "student" "string?"
    (student 4 3))
  (test-contract-error top-level "student-id" "student?"
    (student-id 'a)))

;; Test that prefixing doesn't cause issues.
(let ()
  (define-signature t^
    ((contracted (t? (any/c . -> . boolean?))
                 (make-t (-> t?)))))
  
  (define-unit t@
    (import)
    (export t^)
    (define-struct t ()))
  
  (define-signature s^ (new-make-t))
  
  (define-unit s@
    (import (prefix pre: t^))
    (export s^)
    (define new-make-t pre:make-t))
  
  (define c@ (compound-unit (import)
                            (export S)
                            (link [((T : t^)) t@]
                                  [((S : s^)) s@ T])))
  (define-values/invoke-unit c@ (import) (export s^))
  (new-make-t))


(let ()
  (define-signature s^ ((contracted [n number?])))
  (define-unit s0@
    (import)
    (export s^)
    (define n 0))

  (define-unit s1@
    (import)
    (export s^)
    (define n 1))

  (define-unit/contract a0@
    (import)
    (export)
    #:invoke/contract (-> integer? integer?)
    (lambda (n) (add1 n)))

  (define-unit/contract a1@
    (import)
    (export)
    (init-depend)
    #:invoke/contract (-> integer? integer?)
    (lambda (n) "bad"))

  ((invoke-unit a0@) 1)
  (test-contract-error "(unit a1@)" "a1@" " integer?" ((invoke-unit a1@) 1))

  (define-unit t@
    (import s^)
    (export)
    (if (zero? n) "zero" n))

  (define c@/c (unit/c (import) (export) number?))
  (define/contract c0@ c@/c
    (compound-unit (import) (export) (link [((S : s^)) s0@] [() t@ S])))
  (define/contract c1@ c@/c
    (compound-unit (import) (export) (link [((S : s^)) s1@] [() t@ S])))
  (test-contract-error "(definition c0@)" "c0@" "number?" (invoke-unit c0@))
  (invoke-unit c1@))

;; tests for values case of unit/c contracts


(let ()
  ;; first order
  (define c1 (unit/c (import) (export) (values integer?)))
  (define c2 (unit/c (import) (export) (values integer? string?)))

  (define/contract u1 c1 (unit (import) (export) 5))
  (define/contract u2 c1 (unit (import) (export) "bad"))
  (define/contract u3 c1 (unit (import) (export) (values 1 2)))
  (define/contract u4 c2 (unit (import) (export) (values 1 "two")))
  (define/contract u5 c2 (unit (import) (export) (values 1 2)))
  (define/contract u6 c2 (unit (import) (export) "bad"))

  ;; passing
  (invoke-unit u1)
  (invoke-unit u4)

  ;; failing
  (test-contract-error "(definition u2)" "u2" "promised: integer?" (invoke-unit u2))
  (test-contract-error "(definition u5)" "u5" "promised: string?" (invoke-unit u5)) 
  ;; wrong number of values
  (test-contract-error "(definition u3)" "u3" "expected 1 values" (invoke-unit u3))
  (test-contract-error "(definition u6)" "u6" "expected 2 values" (invoke-unit u6))

  ;; higher order
  (define c3 (unit/c (import) (export) (values (-> integer? string?))))
  (define c4 (unit/c (import) (export) (values (-> integer? integer?) (-> string? string?))))

  (define/contract u7 c3 (unit (import) (export) (λ (n) "ok")))
  (define/contract u8 c3 (unit (import) (export) (λ (n) 'bad)))
  (define/contract u9 c4 (unit (import) (export) (values (λ (n) n) (λ (s) s))))
  (define/contract u10 c4 (unit (import) (export) (values (λ (n) "bad") (λ (s) 'bad))))

  (define-values (f1) (invoke-unit u7))
  (define-values (f2) (invoke-unit u8))
  (define-values (f3 f4) (invoke-unit u9))
  (define-values (f5 f6) (invoke-unit u10))

  ;; ok
  (f1 1)
  (f3 3)
  (f4 "ok")
  ;; errors
  (test-contract-error "top-level" "u7" "expected: integer?" (f1 "bad"))
  (test-contract-error "top-level" "u8" "expected: integer?" (f2 "bad"))
  (test-contract-error "(definition u8)" "u8" "promised: string?" (f2 5))
  (test-contract-error "top-level" "u9" "expected: integer?" (f3 "bad"))
  (test-contract-error "top-level" "u9" "expected: string?" (f4 5))
  (test-contract-error "top-level" "u10" "expected: integer?" (f5 "bad"))
  (test-contract-error "(definition u10)" "u10" "promised: integer?" (f5 5))
  (test-contract-error "(definition u10)" "u10" "promised: string?" (f6 "bad"))
  (test-contract-error "top-level" "u10" "expected: string?" (f6 6)))



;; tests for init-depends in unit contracts
(let ()
  (define-signature a^ ())
  (define-signature b^ ())
  (define-signature c^ ())

  (define/contract u@
    (unit/c (import a^ b^) (export) (init-depend a^ b^))
    (unit (import a^ b^) (export) (init-depend a^ b^)))

  (define/contract v@
    (unit/c (import a^ b^) (export) (init-depend a^ b^))
    (unit (import a^) (export) (init-depend a^)))

  (test-contract-error
   "(definition w@)" "w@" "contract does not list initialization dependency a^"
   (let ()
     (define/contract w@
       (unit/c (import a^) (export))
       (unit (import a^) (export) (init-depend a^)))
     w@))

  ;; make sure that extended dependencies are checked correctly
  (define-signature a-sub^ extends a^ ())
  (define/contract x@
    (unit/c (import a-sub^) (export) (init-depend a-sub^))
    (unit (import a^) (export) (init-depend a^)))

  ;; make sure tags are checked correctly for init-depends
  (test-contract-error
   "(definition y@)" "y@" "contract does not list initialization dependency a^ with tag A"
   (let ()
     (define/contract y@
       (unit/c (import (tag A a^) a^) (export) (init-depend a^))
       (unit (import (tag A a^) a^) (export) (init-depend (tag A a^))))
     y@))

  (test-syntax-error
   "unit/c: initialization dependency on unknown import"
   (let ()
     (define-signature a^ ())
     (define-signature a-sub^ extends a^ ())
     (unit/c (import a^) (export) (init-depend a-sub^))))
  (test-syntax-error
   "unit/c: initialization dependency on unknown import"
   (let ()
     (define-signature a^ ())
     (unit/c (import) (export) (init-depend a^))))

  (test-syntax-error
   "unit/c: unknown signature"
   (unit/c (import x^) (export) (init-depend)))

  (test-syntax-error
   "unit/c: unknown signature"
   (unit/c (import) (export) (init-depend x^)))

  (test-syntax-error
   "unit/c: unknown signature"
   (unit/c (import) (export x^) (init-depend)))

  (void))
