Add #:implements/inits for Class types

Allows some Class types to be written more concisely.
This commit is contained in:
Asumu Takikawa 2014-10-17 17:03:16 -04:00
parent 3d175cedc8
commit 56e7026b16
4 changed files with 111 additions and 11 deletions

View File

@ -252,6 +252,7 @@ additional provides all other bindings from @racketmodname[racket/class].
(field name+type ...) (field name+type ...)
(augment name+type ...) (augment name+type ...)
(code:line #:implements type-alias-id) (code:line #:implements type-alias-id)
(code:line #:implements/inits inits-id)
(code:line #:row-var row-var-id)] (code:line #:row-var row-var-id)]
[init-type name+type [init-type name+type
[id type #:optional]] [id type #:optional]]
@ -282,7 +283,8 @@ additional provides all other bindings from @racketmodname[racket/class].
The order of initialization arguments in the type is significant, because The order of initialization arguments in the type is significant, because
it determines the types of by-position arguments for use with it determines the types of by-position arguments for use with
@racket[make-object] and @racket[instantiate]. @racket[make-object] and @racket[instantiate]. A given @racket[Class] type
may also only contain a single @racket[init-rest] clause.
@ex[ @ex[
(define drink% (define drink%
@ -300,6 +302,15 @@ additional provides all other bindings from @racketmodname[racket/class].
for a subclass to include parts of its parent class type. The initialization argument for a subclass to include parts of its parent class type. The initialization argument
types of the parent, however, are @emph{not} included because a subclass does not necessarily types of the parent, however, are @emph{not} included because a subclass does not necessarily
share the same initialization arguments as its parent class. share the same initialization arguments as its parent class.
Initialization argument types can be included from the parent by providing
@racket[inits-id] with the @racket[#:implements/inits] keyword. This is identical
to the @racket[#:implements] clause except for the initialization argument
behavior. Only a single @racket[#:implements/inits] clause may be provided for
a single @racket[Class] type. The initialization arguments copied from the parent
type are appended to the initialization arguments specified via the @racket[init]
and @racket[init-field] clauses.
Multiple @racket[#:implements] clauses may be provided for a single class Multiple @racket[#:implements] clauses may be provided for a single class
type. type.

View File

@ -731,8 +731,14 @@
(syntax-parse stx (syntax-parse stx
[(kw (~var clause (class-type-clauses parse-type))) [(kw (~var clause (class-type-clauses parse-type)))
(add-disappeared-use #'kw) (add-disappeared-use #'kw)
(define parent-stxs (stx->list #'clause.extends-types)) (define parent-stxs (stx->list #'clause.implements))
(define parent-types (map parse-type parent-stxs)) (define parent/init-stx (attribute clause.implements/inits))
(define parent/init-type (and parent/init-stx (parse-type parent/init-stx)))
(define parent-types
(let ([types (map parse-type parent-stxs)])
(if parent/init-stx
(cons parent/init-type types)
types)))
(define given-inits (attribute clause.inits)) (define given-inits (attribute clause.inits))
(define given-fields (attribute clause.fields)) (define given-fields (attribute clause.fields))
(define given-methods (attribute clause.methods)) (define given-methods (attribute clause.methods))
@ -755,7 +761,7 @@
;; Only proceed to create a class type when the parsing ;; Only proceed to create a class type when the parsing
;; process isn't looking for recursive type alias references. ;; process isn't looking for recursive type alias references.
;; (otherwise the merging process will error) ;; (otherwise the merging process will error)
[(or (null? parent-stxs) [(or (and (null? parent-stxs) (not parent/init-stx))
(not (current-referenced-aliases))) (not (current-referenced-aliases)))
(check-function-types given-methods) (check-function-types given-methods)
@ -768,7 +774,8 @@
[methods given-methods] [methods given-methods]
[augments given-augments]) [augments given-augments])
([parent-type parent-types] ([parent-type parent-types]
[parent-stx parent-stxs]) [parent-stx (append (or (list parent/init-stx) null)
parent-stxs)])
(merge-with-parent-type row-var parent-type parent-stx (merge-with-parent-type row-var parent-type parent-stx
fields methods augments))) fields methods augments)))
@ -780,8 +787,15 @@
(check-constraints methods (caddr constraints)) (check-constraints methods (caddr constraints))
(check-constraints augments (cadddr constraints))) (check-constraints augments (cadddr constraints)))
;; For the #:implements/inits entry, put the inits into the type
;; as well. They are appended at the end to match the runtime behavior
;; of init arguments.
(define parent-inits (get-parent-inits parent/init-type))
(define class-type (define class-type
(make-Class row-var given-inits fields methods augments given-init-rest)) (make-Class row-var
(append given-inits parent-inits)
fields methods augments given-init-rest))
class-type] class-type]
[else [else
@ -809,12 +823,26 @@
(set-box! alias-box (cons (current-type-alias-name) (set-box! alias-box (cons (current-type-alias-name)
(unbox alias-box))) (unbox alias-box)))
(define class-box (current-referenced-class-parents)) (define class-box (current-referenced-class-parents))
(set-box! class-box (append parent-stxs (unbox class-box))) (set-box! class-box (append (if parent/init-stx
(cons parent/init-stx parent-stxs)
parent-stxs)
(unbox class-box)))
;; Ok to return Error here, since this type will ;; Ok to return Error here, since this type will
;; get reparsed in another pass ;; get reparsed in another pass
(make-Error) (make-Error)
])])) ])]))
;; get-parent-inits : (U Type #f) -> Inits
;; Extract the init arguments out of a parent class type
(define (get-parent-inits parent)
(cond [(not parent) null]
[else
(define resolved (resolve parent))
(match resolved
[(Class: _ inits _ _ _ _) inits]
[_ (parse-error "expected a class type for #:implements/inits clause"
"given" resolved)])]))
;; check-function-types : Dict<Name, Type> -> Void ;; check-function-types : Dict<Name, Type> -> Void
;; ensure all types recorded in the dictionary are function types ;; ensure all types recorded in the dictionary are function types
(define (check-function-types method-types) (define (check-function-types method-types)

View File

@ -211,11 +211,12 @@
;; dependencies ;; dependencies
(define-splicing-syntax-class (class-type-clauses parse-type) (define-splicing-syntax-class (class-type-clauses parse-type)
#:description "Class type clause" #:description "Class type clause"
#:attributes (row-var extends-types #:attributes (row-var implements implements/inits
inits fields methods augments init-rest) inits fields methods augments init-rest)
#:literal-sets (class-type-literals) #:literal-sets (class-type-literals)
(pattern (~seq (~or (~optional (~seq #:row-var row-var:id)) (pattern (~seq (~or (~optional (~seq #:row-var row-var:id))
(~seq #:implements extends-type:id) (~seq #:implements implements-id:id)
(~optional (~seq #:implements/inits implements/inits:id))
(~optional ((~or init-rest untyped:init-rest) (~optional ((~or init-rest untyped:init-rest)
init-rest-type:expr)) init-rest-type:expr))
(~var clause (type-clause parse-type))) (~var clause (type-clause parse-type)))
@ -226,7 +227,7 @@
#:attr augments (apply append (attribute clause.augment-entries)) #:attr augments (apply append (attribute clause.augment-entries))
#:attr init-rest (and (attribute init-rest-type) #:attr init-rest (and (attribute init-rest-type)
(parse-type (attribute init-rest-type))) (parse-type (attribute init-rest-type)))
#:with extends-types #'(extends-type ...) #:with implements #'(implements-id ...)
#:fail-when #:fail-when
(check-duplicate (map first (attribute inits))) (check-duplicate (map first (attribute inits)))
"duplicate init or init-field clause" "duplicate init or init-field clause"

View File

@ -1572,4 +1572,64 @@
(define/augment (m x) (inner 'bar-m m x)) (define/augment (m x) (inner 'bar-m m x))
(define/augment (o x y) (inner 'bar-o o x y)))) (define/augment (o x y) (inner 'bar-o o x y))))
(void)) (void))
-Void])) -Void]
;; Test implements clauses
[tc-e (let ()
(define-type-alias A% (Class [foo (-> Void)]))
(define-type-alias B% (Class #:implements A% [bar (-> Void)]))
(: b% B%)
(define b% (class object%
(super-new)
(define/public (foo) (void))
(define/public (bar) (void))))
(new b%))
(-object #:method ([foo (t:-> -Void)] [bar (t:-> -Void)]))]
[tc-err (let ()
(define-type-alias A% (Class [foo (-> Void)]))
(define-type-alias B% (Class #:implements A% [bar (-> Void)]))
(: b% B%)
(define b% (class object%
(super-new)
(define/public (bar) (void))))
(error "foo"))
#:msg "type mismatch.*required public method"]
[tc-e (let ()
(define-type-alias A% (Class (init [y Symbol])))
(define-type-alias B% (Class #:implements/inits A% (init [x String])))
(: b% B%)
(define b% (class object%
(super-new)
(init x y)))
(make-object b% "foo" 'bar)
(void))
-Void]
[tc-e (let ()
(define-type-alias A% (Class (init [y Symbol])))
(define-type-alias B% (Class [m (-> Void)]))
(define-type-alias C% (Class #:implements/inits A%
#:implements B%
(init [x String])))
(: c% C%)
(define c% (class object%
(super-new)
(define/public (m) (void))
(init x y)))
(make-object c% "foo" 'bar)
(void))
-Void]
[tc-err (let ()
(define-type-alias A% (Class (init [y String])))
(define-type-alias B% (Class #:implements/inits A%
#:implements/inits A%))
(error "foo"))
;; FIXME: this error message is pretty bad
#:msg "expected Class type clause"]
[tc-err (let ()
(define-type-alias A% (Class (init [y String])))
(define-type-alias B% (Class #:implements/inits A% (init [x String])))
(: b% B%)
(define b% (class object%
(super-new)
(init y x)))
(error "foo"))
#:msg "type mismatch"]))