Generate ->* contracts for functions with both optional and keyword arguments.
This commit is contained in:
parent
149d8535eb
commit
844e898a54
37
collects/tests/typed-racket/succeed/contract-opt+kw.rkt
Normal file
37
collects/tests/typed-racket/succeed/contract-opt+kw.rkt
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
#lang racket/load
|
||||||
|
|
||||||
|
(module defs typed/racket
|
||||||
|
(provide (all-defined-out))
|
||||||
|
|
||||||
|
(: foo (case-> ([#:extra Integer] -> Integer)
|
||||||
|
(Integer [#:extra Integer] -> Integer)))
|
||||||
|
(define (foo [x 0] #:extra [y 0]) (+ x y))
|
||||||
|
|
||||||
|
;; this is not contractable, yet (keywords not the same)
|
||||||
|
(: bar (case-> (Integer [#:extra Integer] -> Integer)
|
||||||
|
(Integer [#:extra String] -> Integer)))
|
||||||
|
(define (bar x #:extra [y "a"]) (+ x (if (integer? y) y (string-length y))))
|
||||||
|
|
||||||
|
(: baz (case-> (#:extra Integer -> Integer)
|
||||||
|
(Integer #:extra Integer -> Integer)))
|
||||||
|
(define (baz [x 0] #:extra y) (+ x y))
|
||||||
|
|
||||||
|
(: qux (case-> (#:extra Integer [#:super-extra Integer] -> Integer)
|
||||||
|
(Integer #:extra Integer [#:super-extra Integer] -> Integer)))
|
||||||
|
(define (qux [x 0] #:extra y #:super-extra [z 0]) (+ x y z)))
|
||||||
|
|
||||||
|
(require 'defs)
|
||||||
|
(foo)
|
||||||
|
(foo 1)
|
||||||
|
(foo #:extra 1)
|
||||||
|
(foo 1 #:extra 1)
|
||||||
|
|
||||||
|
; (bar 3) ; not contractable
|
||||||
|
|
||||||
|
(baz #:extra 1)
|
||||||
|
(baz 1 #:extra 1)
|
||||||
|
|
||||||
|
(qux #:extra 1)
|
||||||
|
(qux 1 #:extra 1)
|
||||||
|
(qux #:extra 1 #:super-extra 2)
|
||||||
|
(qux 1 #:extra 1 #:super-extra 3)
|
|
@ -121,14 +121,62 @@
|
||||||
[(Function: (list (top-arr:))) #'procedure?]
|
[(Function: (list (top-arr:))) #'procedure?]
|
||||||
[(Function: arrs)
|
[(Function: arrs)
|
||||||
(set-chaperone!)
|
(set-chaperone!)
|
||||||
(let ()
|
;; Try to generate a single `->*' contract if possible.
|
||||||
|
;; This allows contracts to be generated for functions with both optional and keyword args.
|
||||||
|
;; (and don't otherwise require full `case->')
|
||||||
|
(define conv (match-lambda [(Keyword: kw kty _) (list kw (t->c/neg kty))]))
|
||||||
|
(define (partition-kws kws) (partition (match-lambda [(Keyword: _ _ mand?) mand?]) kws))
|
||||||
|
(define (process-dom dom*) (if method? (cons #'any/c dom*) dom*))
|
||||||
|
(define (process-rngs rngs*)
|
||||||
|
(match rngs*
|
||||||
|
[(list r) r]
|
||||||
|
[_ #`(values #,@rngs*)]))
|
||||||
|
(cond
|
||||||
|
;; To generate a single `->*', everything must be the same for all arrs, except for positional
|
||||||
|
;; arguments which only need to be monotonically increasing.
|
||||||
|
;; TODO sufficient condition, but may not be necessary
|
||||||
|
[(and
|
||||||
|
(> (length arrs) 1)
|
||||||
|
;; Keyword args, range and rest specs all the same.
|
||||||
|
(let ([xs (map (match-lambda [(arr: _ rng rest-spec _ kws) (list rng rest-spec kws)]) arrs)])
|
||||||
|
(foldl equal? (first xs) (rest xs)))
|
||||||
|
;; Positionals are monotonically increasing.
|
||||||
|
(let-values ([(_ ok?)
|
||||||
|
(for/fold ([positionals '()]
|
||||||
|
[ok-so-far? #t])
|
||||||
|
([arr (in-list arrs)])
|
||||||
|
(match arr
|
||||||
|
[(arr: dom _ _ _ _)
|
||||||
|
(values dom
|
||||||
|
(and ok-so-far?
|
||||||
|
(>= (length dom) (length positionals))
|
||||||
|
(equal? positionals (take dom (length positionals)))))]))])
|
||||||
|
ok?))
|
||||||
|
(match* ((first arrs) (last arrs))
|
||||||
|
[((arr: first-dom (Values: (list (Result: rngs (FilterSet: (Top:) (Top:)) (Empty:)) ...)) rst #f kws)
|
||||||
|
(arr: last-dom _ _ _ _)) ; all but dom is the same for all
|
||||||
|
(with-syntax
|
||||||
|
([(dom* ...)
|
||||||
|
;; Mandatory arguments are positionals of the first arr
|
||||||
|
;; (smallest set, since postitionals are monotonically increasing)
|
||||||
|
;; and mandatory kw args.
|
||||||
|
(let*-values ([(mand-kws opt-kws) (partition-kws kws)])
|
||||||
|
(process-dom (append (map t->c/neg first-dom)
|
||||||
|
(append-map conv mand-kws))))]
|
||||||
|
[(opt-dom* ...)
|
||||||
|
(let-values ([(mand-kws opt-kws) (partition-kws kws)])
|
||||||
|
(append (map t->c/neg (drop last-dom (length first-dom)))
|
||||||
|
(append-map conv opt-kws)))]
|
||||||
|
[rng* (process-rngs (map t->c rngs))]
|
||||||
|
[(rst-spec ...) (if rst #'(#:rest (listof #,(t->c/neg rest))) #'())])
|
||||||
|
#'((dom* ...) (opt-dom* ...) rst-spec ... . ->* . rng*))])]
|
||||||
|
[else
|
||||||
(define ((f [case-> #f]) a)
|
(define ((f [case-> #f]) a)
|
||||||
(define-values (dom* opt-dom* rngs* rst)
|
(define-values (dom* opt-dom* rngs* rst)
|
||||||
(match a
|
(match a
|
||||||
;; functions with no filters or objects
|
;; functions with no filters or objects
|
||||||
[(arr: dom (Values: (list (Result: rngs (FilterSet: (Top:) (Top:)) (Empty:)) ...)) rst #f kws)
|
[(arr: dom (Values: (list (Result: rngs (FilterSet: (Top:) (Top:)) (Empty:)) ...)) rst #f kws)
|
||||||
(let-values ([(mand-kws opt-kws) (partition (match-lambda [(Keyword: _ _ mand?) mand?]) kws)]
|
(let-values ([(mand-kws opt-kws) (partition-kws kws)])
|
||||||
[(conv) (match-lambda [(Keyword: kw kty _) (list kw (t->c/neg kty))])])
|
|
||||||
(values (append (map t->c/neg dom) (append-map conv mand-kws))
|
(values (append (map t->c/neg dom) (append-map conv mand-kws))
|
||||||
(append-map conv opt-kws)
|
(append-map conv opt-kws)
|
||||||
(map t->c rngs)
|
(map t->c rngs)
|
||||||
|
@ -143,21 +191,19 @@
|
||||||
(exit (fail)))]
|
(exit (fail)))]
|
||||||
[_ (exit (fail))]))
|
[_ (exit (fail))]))
|
||||||
(with-syntax*
|
(with-syntax*
|
||||||
([(dom* ...) (if method? (cons #'any/c dom*) dom*)]
|
([(dom* ...) (process-dom dom*)]
|
||||||
[(opt-dom* ...) opt-dom*]
|
[(opt-dom* ...) opt-dom*]
|
||||||
[rng* (match rngs*
|
[rng* (process-rngs rngs*)]
|
||||||
[(list r) r]
|
[rst* rst]
|
||||||
[_ #`(values #,@rngs*)])]
|
[(rst-spec ...) (if rst #'(#:rest (listof rst*)) #'())])
|
||||||
[rst* rst]
|
;; Garr, I hate case->!
|
||||||
[(rst-spec ...) (if rst #'(#:rest (listof rst*)) #'())])
|
(if (and (pair? (syntax-e #'(opt-dom* ...))) case->)
|
||||||
;; Garr, I hate case->!
|
(exit (fail))
|
||||||
(if (and (pair? (syntax-e #'(opt-dom* ...))) case->)
|
(if (or rst (pair? (syntax-e #'(opt-dom* ...))))
|
||||||
(exit (fail))
|
(if case->
|
||||||
(if (or rst (pair? (syntax-e #'(opt-dom* ...))))
|
#'(dom* ... rst-spec ... . -> . rng*)
|
||||||
(if case->
|
#'((dom* ...) (opt-dom* ...) rst-spec ... . ->* . rng*))
|
||||||
#'(dom* ... rst-spec ... . -> . rng*)
|
#'(dom* ... . -> . rng*)))))
|
||||||
#'((dom* ...) (opt-dom* ...) rst-spec ... . ->* . rng*))
|
|
||||||
#'(dom* ... . -> . rng*)))))
|
|
||||||
(unless (no-duplicates (for/list ([t arrs])
|
(unless (no-duplicates (for/list ([t arrs])
|
||||||
(match t
|
(match t
|
||||||
[(arr: dom _ _ _ _) (length dom)]
|
[(arr: dom _ _ _ _) (length dom)]
|
||||||
|
@ -166,7 +212,7 @@
|
||||||
(exit (fail)))
|
(exit (fail)))
|
||||||
(match (map (f (not (= 1 (length arrs)))) arrs)
|
(match (map (f (not (= 1 (length arrs)))) arrs)
|
||||||
[(list e) e]
|
[(list e) e]
|
||||||
[l #`(case-> #,@l)]))]
|
[l #`(case-> #,@l)])])]
|
||||||
[_ (int-err "not a function" f)]))
|
[_ (int-err "not a function" f)]))
|
||||||
|
|
||||||
;; Helpers for contract requirements
|
;; Helpers for contract requirements
|
||||||
|
|
Loading…
Reference in New Issue
Block a user