/racket/collects/racket/contract/private/arity-checking.rkt

http://github.com/plt/racket · Racket · 238 lines · 222 code · 14 blank · 2 comment · 58 complexity · 3aa6cf07eb9ce7a96f936f48b49ea7ab MD5 · raw file

  1. #lang racket/base
  2. (require "blame.rkt"
  3. "kwd-info-struct.rkt"
  4. "list.rkt")
  5. (provide do-arity-checking
  6. ;; for test suites
  7. arity-as-string
  8. raw-arity-as-string)
  9. (define (do-arity-checking blame val
  10. ->stct-doms
  11. ->stct-rest
  12. ->stct-min-arity
  13. ->stct-kwd-infos
  14. method?)
  15. (define proc/meth (if method? "a method" "a procedure"))
  16. (let/ec k
  17. (unless (procedure? val)
  18. (k
  19. (λ (neg-party)
  20. (raise-blame-error blame #:missing-party neg-party val
  21. `(expected: ,proc/meth
  22. given: "~e")
  23. val))))
  24. (define-values (actual-mandatory-kwds actual-optional-kwds) (procedure-keywords val))
  25. (define arity (if (list? (procedure-arity val))
  26. (procedure-arity val)
  27. (list (procedure-arity val))))
  28. (define exra-required-args (if (ellipsis-rest-arg-ctc? ->stct-rest)
  29. (length (*list-ctc-suffix ->stct-rest))
  30. 0))
  31. ;; the function must be ok for *all* the arities the contract says are ok
  32. (for/and ([base-number-of-non-keyword-args (in-range ->stct-min-arity (add1 (length ->stct-doms)))])
  33. (define expected-number-of-non-keyword-args (+ base-number-of-non-keyword-args exra-required-args))
  34. (define matching-arity?
  35. (and (for/or ([a (in-list arity)])
  36. (or (and (equal? expected-number-of-non-keyword-args a))
  37. (and (arity-at-least? a)
  38. (>= expected-number-of-non-keyword-args (arity-at-least-value a)))))
  39. (if ->stct-rest
  40. (let ([lst (car (reverse arity))])
  41. (and (arity-at-least? lst)
  42. (<= (arity-at-least-value lst) (+ exra-required-args ->stct-min-arity))))
  43. #t)))
  44. (unless matching-arity?
  45. (k
  46. (λ (neg-party)
  47. (define expected-number-of-non-keyword-args*
  48. ((if method? sub1 values) expected-number-of-non-keyword-args))
  49. (raise-blame-error blame #:missing-party neg-party val
  50. `(expected:
  51. ,(string-append proc/meth
  52. " that accepts ~a non-keyword argument~a~a")
  53. given: "~e"
  54. "\n ~a")
  55. expected-number-of-non-keyword-args*
  56. (if (= expected-number-of-non-keyword-args* 1) "" "s")
  57. (if ->stct-rest
  58. " and arbitrarily many more"
  59. "")
  60. val
  61. (arity-as-string val))))))
  62. (define (should-have-supplied kwd)
  63. (k
  64. (λ (neg-party)
  65. (raise-blame-error blame #:missing-party neg-party val
  66. `(expected:
  67. ,(string-append proc/meth " that accepts the ~a keyword argument")
  68. given: "~e"
  69. "\n ~a")
  70. kwd
  71. val
  72. (arity-as-string val method?)))))
  73. (define (should-not-have-supplied kwd)
  74. (k
  75. (λ (neg-party)
  76. (raise-blame-error blame #:missing-party neg-party val
  77. `(expected:
  78. ,(string-append proc/meth " that does not require the ~a keyword argument")
  79. given: "~e"
  80. "\n ~a")
  81. kwd
  82. val
  83. (arity-as-string val method?)))))
  84. (when actual-optional-kwds ;; when all kwds are okay, no checking required
  85. (let loop ([mandatory-kwds actual-mandatory-kwds]
  86. [all-kwds actual-optional-kwds]
  87. [kwd-infos ->stct-kwd-infos])
  88. (cond
  89. [(null? kwd-infos)
  90. (unless (null? mandatory-kwds)
  91. (should-not-have-supplied (car mandatory-kwds)))]
  92. [else
  93. (define kwd-info (car kwd-infos))
  94. (define-values (mandatory? kwd new-mandatory-kwds new-all-kwds)
  95. (cond
  96. [(null? all-kwds)
  97. (should-have-supplied (kwd-info-kwd kwd-info))]
  98. [else
  99. (define mandatory?
  100. (and (pair? mandatory-kwds)
  101. (equal? (car mandatory-kwds) (car all-kwds))))
  102. (values mandatory?
  103. (car all-kwds)
  104. (if mandatory?
  105. (cdr mandatory-kwds)
  106. mandatory-kwds)
  107. (cdr all-kwds))]))
  108. (cond
  109. [(equal? kwd (kwd-info-kwd kwd-info))
  110. (when (and (not (kwd-info-mandatory? kwd-info))
  111. mandatory?)
  112. (k
  113. (λ (neg-party)
  114. (raise-blame-error
  115. blame #:missing-party neg-party val
  116. `(expected:
  117. ,(string-append proc/meth " that optionally accepts the keyword ~a (this one is mandatory)")
  118. given: "~e"
  119. "\n ~a")
  120. val
  121. kwd
  122. (arity-as-string val method?)))))
  123. (loop new-mandatory-kwds new-all-kwds (cdr kwd-infos))]
  124. [(keyword<? kwd (kwd-info-kwd kwd-info))
  125. (when mandatory?
  126. (should-not-have-supplied kwd))
  127. (loop new-mandatory-kwds new-all-kwds kwd-infos)]
  128. [else
  129. (loop new-mandatory-kwds new-all-kwds kwd-infos)])])))
  130. #f))
  131. (define (arity-as-string v [method? #f])
  132. (define prefix (if (object-name v)
  133. (format "~a accepts: " (object-name v))
  134. (format "accepts: ")))
  135. (string-append prefix (raw-arity-as-string v method?)))
  136. (define (raw-arity-as-string v [method? #f])
  137. (define ar (procedure-arity v))
  138. (define adjust (if method? sub1 values))
  139. (define (plural n) (if (= n 1) "" "s"))
  140. (define-values (man-kwds all-kwds) (procedure-keywords v))
  141. (define opt-kwds (if all-kwds (remove* man-kwds all-kwds) #f))
  142. (define normal-str (if (null? all-kwds) "" "normal "))
  143. (define normal-args
  144. (cond
  145. [(null? ar) "no arguments"]
  146. [(number? ar)
  147. (define ar* (adjust ar))
  148. (format "~a ~aargument~a" ar* normal-str (plural ar*))]
  149. [(arity-at-least? ar) (format "~a or arbitrarily many more ~aarguments"
  150. (adjust (arity-at-least-value ar))
  151. normal-str)]
  152. [else
  153. (define comma
  154. (if (and (= (length ar) 2)
  155. (not (arity-at-least? (list-ref ar 1))))
  156. ""
  157. ","))
  158. (apply
  159. string-append
  160. (let loop ([ar ar])
  161. (cond
  162. [(null? (cdr ar))
  163. (define v (car ar))
  164. (cond
  165. [(arity-at-least? v)
  166. (list
  167. (format "~a, or arbitrarily many more ~aarguments"
  168. (arity-at-least-value (adjust v))
  169. normal-str))]
  170. [else
  171. (list (format "or ~a ~aarguments" (adjust v) normal-str))])]
  172. [else
  173. (cons (format "~a~a " (adjust (car ar)) comma)
  174. (loop (cdr ar)))])))]))
  175. (cond
  176. [(and (null? man-kwds) (null? opt-kwds))
  177. normal-args]
  178. [(and (null? man-kwds) (not opt-kwds))
  179. (string-append normal-args " and optionally any keyword")]
  180. [(and (null? man-kwds) (pair? opt-kwds))
  181. (string-append normal-args
  182. " and the optional keyword"
  183. (plural (length opt-kwds))
  184. " "
  185. (kwd-list-as-string opt-kwds))]
  186. [(and (pair? man-kwds) (not opt-kwds))
  187. (string-append normal-args
  188. ", the mandatory keyword"
  189. (plural (length man-kwds))
  190. " "
  191. (kwd-list-as-string man-kwds)
  192. ", and optionally any keyword")]
  193. [(and (pair? man-kwds) (null? opt-kwds))
  194. (string-append normal-args
  195. " and the mandatory keyword"
  196. (plural (length man-kwds))
  197. " "
  198. (kwd-list-as-string man-kwds))]
  199. [(and (pair? man-kwds) (pair? opt-kwds))
  200. (string-append normal-args
  201. ", the mandatory keyword"
  202. (plural (length man-kwds))
  203. " "
  204. (kwd-list-as-string man-kwds)
  205. ", and the optional keyword"
  206. (plural (length opt-kwds))
  207. " "
  208. (kwd-list-as-string opt-kwds))]))
  209. (define (kwd-list-as-string kwds)
  210. (cond
  211. [(null? (cdr kwds))
  212. (format "~a" (list-ref kwds 0))]
  213. [(null? (cddr kwds))
  214. (format "~a and ~a" (list-ref kwds 0) (list-ref kwds 1))]
  215. [else
  216. (apply
  217. string-append
  218. (let loop ([kwds kwds])
  219. (cond
  220. [(null? (cdr kwds))
  221. (list (format "and ~a" (car kwds)))]
  222. [else
  223. (cons (format "~a, " (car kwds))
  224. (loop (cdr kwds)))])))]))