From 3b80ae71f9d84abb717f92d16723a8f1f6d27347 Mon Sep 17 00:00:00 2001 From: Eric Dobson Date: Tue, 24 Mar 2015 22:28:59 -0700 Subject: [PATCH] correct keyword function conversion Keyword functions are a little tricky. This PR addresses issues checking the body of kw functions. Basically, a function with keyword arguments such as inc: (define (inc x #:n [n 1]) (+ x n)) actually expands into a more complex function with 3 arguments that looks something resembling the following: (define (inc-expanded n* n-given? x) (let ([n (if n-given? n* 1)]) (+ x n))) and calls to inc are converted to match this form: (inc 42) => (inc-expanded #f #f 42) (inc 42 #:n 2) => (inc-expanded 2 #t 42) Note that each optional keyword argument has a boolean flag argument that signals whether or not the caller provided that keyword argument. This PR takes advantage of the observation that the value for the n* argument in inc is only reachable in code when n-given? is #t, and so, assuming the kw-expansion protocol always only accesses n* if n-given? is #t, we can actually safely check the body of the function against the following simple but correct type: (-> Number Boolean Number Number) An alternative previous approach expanded the function type into every possible combination of optional argument and optional argument flag, but this was prohibitively expensive. --- .../typed-racket/rep/type-rep.rkt | 9 +- .../typed-racket/types/kw-types.rkt | 152 +++++++++--------- typed-racket-test/fail/gh56.rkt | 5 + 3 files changed, 87 insertions(+), 79 deletions(-) create mode 100644 typed-racket-test/fail/gh56.rkt diff --git a/typed-racket-lib/typed-racket/rep/type-rep.rkt b/typed-racket-lib/typed-racket/rep/type-rep.rkt index 922eb5f3..323b3bea 100644 --- a/typed-racket-lib/typed-racket/rep/type-rep.rkt +++ b/typed-racket-lib/typed-racket/rep/type-rep.rkt @@ -462,11 +462,17 @@ [#:for-each (f) (f ty)]) +(define (keyword-sorted/c kws) + (or (empty? kws) + (= (length kws) 1) + (apply keyword Type -(define (convert kw-t plain-t opt-t rng rest drest split?) - (define-values (mand-kw-t opt-kw-t) (partition (match-lambda [(Keyword: _ _ m) m]) kw-t)) - +(define (convert kw-ts plain-ts opt-ts rng rest drest split?) (when drest (int-err "drest passed to kw-convert")) - - (define arities - (for/list ([i (in-range (length opt-t))]) - (make-arr* (append plain-t (take opt-t i)) - rng - #:kws kw-t - #:rest rest - #:drest drest))) ;; the kw function protocol passes rest args as an explicit list - (define rest-type (if rest (-lst rest) empty)) - (define ts - (flatten - (list - (for/list ([k (in-list kw-t)]) - (match k - [(Keyword: _ t #t) t] - [(Keyword: _ t #f) (list (-opt t) -Boolean)])) - plain-t - (for/list ([t (in-list opt-t)]) (-opt t)) - (for/list ([t (in-list opt-t)]) -Boolean) - rest-type))) + (define rest-type (if rest (list (-lst rest)) empty)) + ;; the kw protocol puts the arguments in keyword-sorted order in the ;; function header, so we need to sort the types to match (define sorted-kws - (sort kw-t keyword (Listof Keyword) @@ -153,14 +144,17 @@ (define (inner-kw-convert arrs actual-kws split?) (define table (find-prefixes arrs)) (define fns - (for/set ([(k v) (in-dict table)]) - (match k - [(arr: mand rng rest drest kws) - (define kws* (if actual-kws - (handle-extra-or-missing-kws kws actual-kws) - kws)) - (convert kws* mand v rng rest drest split?)]))) - (apply cl->* (set->list fns))) + ;; use for/list and remove duplicates afterwards instead of + ;; set and set->list to retain determinism + (remove-duplicates + (for/list ([(k v) (in-dict table)]) + (match k + [(arr: mand rng rest drest kws) + (define kws* (if actual-kws + (handle-extra-or-missing-kws kws actual-kws) + kws)) + (convert kws* mand v rng rest drest split?)])))) + (apply cl->* fns)) ;; kw-convert : Type (Option LambdaKeywords) [Boolean] -> Type ;; Given an ordinary function type, convert it to a type that matches the keyword @@ -331,19 +325,21 @@ [(arr: args result _ _ _) #f])) (define (opt-convert ft required-pos optional-pos) - (let/ec exit - (let loop ((ft ft)) - (match ft - [(Function: arrs) - (let ((arrs (map (opt-convert-arr required-pos optional-pos) arrs))) - (if (andmap values arrs) - (make-Function arrs) - (exit #f)))] - [(Poly-names: names f) - (make-Poly names (loop f))] - [(PolyDots-names: names f) - (make-PolyDots names (loop f))] - [t t])))) + (let loop ([ft ft]) + (match ft + [(Function: arrs) + (let ([arrs (map (opt-convert-arr required-pos optional-pos) arrs)]) + (and (andmap values arrs) + (make-Function arrs)))] + [(Poly-names: names f) + (match (loop f) + [#f #f] + [t (make-Poly names t)])] + [(PolyDots-names: names f) + (match (loop f) + [#f #f] + [t (make-PolyDots names t)])] + [t t]))) ;; opt-unconvert : Type (Listof Syntax) -> Type ;; Given a type for a core optional arg function, unconvert it to a diff --git a/typed-racket-test/fail/gh56.rkt b/typed-racket-test/fail/gh56.rkt new file mode 100644 index 00000000..17f40f31 --- /dev/null +++ b/typed-racket-test/fail/gh56.rkt @@ -0,0 +1,5 @@ +#lang typed/racket + +(: f (Number [#:y Boolean] -> Number)) +(define (f x #:y [y #f] #:z [z 'this-can-be-anything]) + (if y "y is truthy" x))