Rewrite TR pass for class top-level expressions

Instead of making separate passes over the expressions,
make a single pass to collect necessary information for
type-checking.

This enables simpler processing of type annotations,
super initialization arguments, and other things.
This commit is contained in:
Asumu Takikawa 2014-03-05 01:07:09 -05:00
parent 9c63978a58
commit 0bacba7a68
3 changed files with 169 additions and 135 deletions

View File

@ -411,10 +411,10 @@
(append rest-top (list plain-annotation new-clause)) (append rest-top (list plain-annotation new-clause))
private-fields)] private-fields)]
;; Just process this to add the property ;; Just process this to add the property
[(: name:id type:expr) [(: name:id . rst)
(define plain-annotation (define plain-annotation
(non-clause (tr:class:type-annotation-property (non-clause (tr:class:type-annotation-property
(syntax/loc stx (: name type)) #t))) (syntax/loc stx (: name . rst)) #t)))
(values methods (values methods
(append rest-top (list plain-annotation)) (append rest-top (list plain-annotation))
private-fields)] private-fields)]

View File

@ -10,6 +10,7 @@
racket/pretty ;; DEBUG ONLY racket/pretty ;; DEBUG ONLY
racket/set racket/set
racket/syntax racket/syntax
syntax/id-table
syntax/parse syntax/parse
"signatures.rkt" "signatures.rkt"
"tc-metafunctions.rkt" "tc-metafunctions.rkt"
@ -32,6 +33,13 @@
(import tc-if^ tc-lambda^ tc-app^ tc-let^ tc-expr^) (import tc-if^ tc-lambda^ tc-app^ tc-let^ tc-expr^)
(export check-class^) (export check-class^)
;; A super-init-stxs is a
;; (super-init-stxs (Listof Syntax) (Dict Symbol Syntax))
;;
;; interp. Represents the by-position and by-name initialization
;; arguments respectively provided by the class
(struct super-init-stxs (by-position by-name) #:transparent)
;; time debugging ;; time debugging
(define-syntax do-timing #f) (define-syntax do-timing #f)
(define start-time (make-parameter 0)) (define start-time (make-parameter 0))
@ -181,6 +189,17 @@
(~and make-methods :make-methods-class) (~and make-methods :make-methods-class)
(quote #f))))))) (quote #f)))))))
;; This is similar to `type-declaration` from "internal-forms.rkt", but
;; the expansion is slightly different in a class so we use this instead.
(define-syntax-class class-type-declaration
#:literal-sets (kernel-literals)
#:literals (values void :-internal)
#:attributes (name type)
(pattern (let-values
([() (begin (quote-syntax (:-internal name:id type:expr))
(#%plain-app values))])
(#%plain-app void))))
;; Syntax TCResults -> Type ;; Syntax TCResults -> Type
;; Type-check a class form by trawling its innards ;; Type-check a class form by trawling its innards
;; ;;
@ -326,44 +345,32 @@
(define make-methods-stx (hash-ref parse-info 'make-methods)) (define make-methods-stx (hash-ref parse-info 'make-methods))
(define top-level-exprs (define top-level-exprs
(trawl-for-property make-methods-stx tr:class:top-level-property)) (trawl-for-property make-methods-stx tr:class:top-level-property))
;; augment annotations go in their own table, because they're
;; Filter top level expressions into several groups, each processed
;; into appropriate data structures
;;
;; Augment annotations go in their own table, because they're
;; the only kind of type annotation that is allowed to be duplicate ;; the only kind of type annotation that is allowed to be duplicate
;; (i.e., m can have type Integer -> Integer and an augment type of ;; (i.e., m can have type Integer -> Integer and an augment type of
;; String -> String in the separate tables) ;; String -> String in the separate tables)
(define-values (annotation-table augment-annotation-table) (define-values (super-new initializers
((compose (setup-pubment-defaults (hash-ref parse-info 'pubment-names)) annotation-table augment-annotation-table
register-annotations) other-top-level-exprs)
top-level-exprs)) (handle-top-levels top-level-exprs))
(do-timestamp "built annotation table")
;; find the `super-new` call (or error if missing) (setup-pubment-defaults (hash-ref parse-info 'pubment-names)
(define super-new-stxs annotation-table
(trawl-for-property make-methods-stx tr:class:super-new-property)) augment-annotation-table)
(define super-new-stx (check-super-new-exists super-new-stxs))
(define-values (provided-pos-args provided-super-inits) ;; Calculate remaining inits, optional inits, etc.
(if super-new-stx ;;
(find-provided-inits super-new-stx super-inits)
(values null null)))
(define provided-init-names (dict-keys provided-super-inits))
(define pos-length (length provided-pos-args))
;; super-init-rest* - The init-rest passed to the `infer-self-type` function. ;; super-init-rest* - The init-rest passed to the `infer-self-type` function.
;; This reflects any changes to the `super-init-rest` type ;; This reflects any changes to the `super-init-rest` type
;; that are necessary due to the super constructor call in ;; that are necessary due to the super constructor call in
;; this class. ;; this class.
(define-values (super-init-rest* remaining-super-inits) (define-values (super-init-rest* remaining-super-inits)
(cond [;; too many init arguments, and no init-rest (handle-pos-inits super-new super-inits super-init-rest))
(and (not super-init-rest) (> pos-length (length super-inits)))
(values super-init-rest
(tc-error/expr "too many positional init arguments provided"
#:return null))]
[;; no remaining by-name inits, so change the init-rest type
;; and return a null remaining named inits list
(> pos-length (length super-inits))
(values (Un) null)]
[else
(values super-init-rest
(for/list ([(name val) (in-dict (drop super-inits pos-length))]
#:unless (member name provided-init-names))
(cons name val)))]))
;; define which init names are optional ;; define which init names are optional
(define optional-inits (hash-ref parse-info 'optional-inits)) (define optional-inits (hash-ref parse-info 'optional-inits))
(define optional-external (for/set ([n optional-inits]) (define optional-external (for/set ([n optional-inits])
@ -410,7 +417,7 @@
#:when (set-member? (hash-ref parse-info 'private-fields) name)) #:when (set-member? (hash-ref parse-info 'private-fields) name))
(hash-set! private-field-types name (list type))) (hash-set! private-field-types name (list type)))
(synthesize-private-field-types top-level-exprs (synthesize-private-field-types initializers
local-private-field-table local-private-field-table
private-field-types) private-field-types)
@ -434,15 +441,10 @@
self-type)) self-type))
(do-timestamp "built local tables") (do-timestamp "built local tables")
(with-lexical-env/extend lexical-names/top-level lexical-types/top-level (with-lexical-env/extend lexical-names/top-level lexical-types/top-level
(check-super-new provided-pos-args provided-super-inits (check-super-new super-new super-inits super-init-rest))
super-inits super-init-rest))
(do-timestamp "checked super-new") (do-timestamp "checked super-new")
(with-lexical-env/extend lexical-names/top-level lexical-types/top-level (with-lexical-env/extend lexical-names/top-level lexical-types/top-level
(for ([stx top-level-exprs] (for ([stx other-top-level-exprs])
;; avoid checking these to avoid duplication and to avoid checking
;; ignored expressions
#:unless (tr:class:super-new-property stx)
#:unless (tr:class:type-annotation-property stx))
(tc-expr stx))) (tc-expr stx)))
(do-timestamp "checked other top-level exprs") (do-timestamp "checked other top-level exprs")
(with-lexical-env/extend lexical-names/top-level lexical-types/top-level (with-lexical-env/extend lexical-names/top-level lexical-types/top-level
@ -491,6 +493,72 @@
(hash-ref parse-info 'fresh-parameters) (hash-ref parse-info 'fresh-parameters)
final-class-type))) final-class-type)))
;; handle-top-levels : (Listof Syntax) ->
;; super-init-stxs Dict Dict Hash (Listof Syntax)
;; Divide top level expressions into several categories, and put them
;; in appropriate data structures.
(define (handle-top-levels exprs)
(define super-new #f)
(define initializers (make-free-id-table))
(define annotations (make-hash))
(define augment-annotations (make-hash))
(define other-exprs
(for/fold ([other-exprs '()])
([expr exprs])
(syntax-parse expr
#:literal-sets (kernel-literals)
#:literals (:-augment)
;; FIXME: this case seems too loose, many things can match this syntax
;; we likely need to set a property or match against another name
[(let-values ([(obj:id) self])
(let-values ([(field:id) initial-value])
(#%plain-app setter:id _ _)))
;; only record the first one, which is the one that initializes
;; the field or private field
(unless (dict-has-key? initializers #'setter)
(free-id-table-set! initializers #'setter #'initial-value))
other-exprs]
[:tr:class:super-new^
(when super-new
(tc-error/expr "typed classes must only call super-new a single time"))
(set! super-new (find-provided-inits expr))
other-exprs]
[(~and t:class-type-declaration :tr:class:type-annotation^)
(define name (syntax-e #'t.name))
(define type (parse-type #'t.type))
(unless (check-duplicate-member annotations name type)
(hash-set! annotations name type))
other-exprs]
;; FIXME: use internal-forms for this instead
[(quote-syntax (:-augment name-stx:id type-stx))
(define name (syntax-e #'name-stx))
(define type (parse-type #'type-stx))
(unless (check-duplicate-member augment-annotations name type)
(hash-set! augment-annotations name type))
other-exprs]
[_ (cons expr other-exprs)])))
(unless super-new
(tc-error/expr "typed classes must call super-new at the class top-level")
(set! super-new (super-init-stxs null null)))
(values super-new
initializers
annotations
augment-annotations
other-exprs))
;; check-duplicate-member : Hash Symbol Type -> Boolean
;; return true if the class member is already annotated
(define (check-duplicate-member table name type)
(and (hash-has-key? table name)
(not (equal? (hash-ref table name) type))
(tc-error/expr/fields
"duplicate type annotation in class"
#:stx #`#,name
#:return #t
"for identifier" name
"new type" type
"previous type" (hash-ref table name))))
;; check-method-presence-and-absence : Dict Type Set<Symbol> ... -> Void ;; check-method-presence-and-absence : Dict Type Set<Symbol> ... -> Void
;; use the internal class: information to check whether clauses ;; use the internal class: information to check whether clauses
;; exist or are absent appropriately ;; exist or are absent appropriately
@ -898,30 +966,20 @@
(tc-expr form)] (tc-expr form)]
[_ (void)]))) [_ (void)])))
;; synthesize-private-field-types : (Listof Syntax) Dict Hash -> Void ;; synthesize-private-field-types : IdTable Dict Hash -> Void
;; Given top-level expressions in the class, synthesize types from ;; Given top-level expressions in the class, synthesize types from
;; the initialization expressions for private fields. ;; the initialization expressions for private fields.
(define (synthesize-private-field-types exprs locals types) (define (synthesize-private-field-types initializers locals types)
(for ([(name getter+setter) (in-dict locals)] (for ([(name getter+setter) (in-dict locals)]
#:unless (hash-has-key? types name)) #:unless (hash-has-key? types name))
(match-define (list _ setter) getter+setter) (match-define (list _ setter) getter+setter)
;; only the first setter expression is the initialization for (define init-expr-stx (free-id-table-ref initializers setter #f))
;; the field, the rest are set!s in the user code (when init-expr-stx
(for/or ([expr exprs]) (define type (tc-expr/t init-expr-stx))
(syntax-parse expr ;; FIXME: this always generalizes the private field
#:literal-sets (kernel-literals) ;; type, but it's better to only generalize if
[(let-values ([(obj) self]) ;; the field is actually mutated.
(let-values ([(field) initial-value]) (hash-set! types name (list (generalize type))))))
(#%plain-app setter* _ _)))
#:when (free-identifier=? setter #'setter*)
(define type (tc-expr/t #'initial-value))
;; FIXME: this always generalizes the private field
;; type, but it's better to only generalize if
;; the field is actually mutated.
(hash-set! types name (list (generalize type)))
;; done once we find the first one
#t]
[_ #f]))))
;; Syntax -> Dict<Symbol, Id> Dict<Symbol, Id> ;; Syntax -> Dict<Symbol, Id> Dict<Symbol, Id>
;; Dict<Symbol, (List Symbol Symbol)> Dict<Symbol, Id> ;; Dict<Symbol, (List Symbol Symbol)> Dict<Symbol, Id>
@ -1036,30 +1094,17 @@
(syntax->datum #'(augment ...)) (syntax->datum #'(augment ...))
(syntax->list #'(local-inner ...))))])) (syntax->list #'(local-inner ...))))]))
;; check-super-new-exists : Listof<Syntax> -> (U Syntax #f) ;; find-provided-inits : Syntax -> super-init-stxs
;; Check if a `super-new` call exists and if there is only
;; one call. Return #f on error.
(define (check-super-new-exists stxs)
(cond [(null? stxs)
(tc-error/expr
"typed classes must call super-new at the class top-level")
#f]
[(> (length stxs) 1)
(tc-error/expr
"typed classes must only call super-new a single time")
#f]
[else (car stxs)]))
;; find-provided-inits : Syntax Inits -> Listof<Syntax> Dict<Symbol, Syntax>
;; Find the init arguments that were provided via super-new ;; Find the init arguments that were provided via super-new
(define (find-provided-inits stx super-inits) (define (find-provided-inits stx)
(syntax-parse stx (syntax-parse stx
#:literals (#%plain-app list cons quote) #:literal-sets (kernel-literals)
#:literals (cons list)
[(#%plain-app [(#%plain-app
(#%plain-lambda args (#%plain-lambda args
(#%plain-app super-go _ _ _ _ _ _)) (#%plain-app super-go _ _ _ _ _ _))
pos-arg:expr ...) pos-arg:expr ...)
(values (syntax->list #'(pos-arg ...)) null)] (super-init-stxs (syntax->list #'(pos-arg ...)) null)]
[(#%plain-app super-go _ _ _ _ [(#%plain-app super-go _ _ _ _
(~or (#%plain-app list pos-arg:expr ...) (~or (#%plain-app list pos-arg:expr ...)
(~and _ (~bind [(pos-arg 1) '()]))) (~and _ (~bind [(pos-arg 1) '()])))
@ -1068,18 +1113,48 @@
(#%plain-app cons (quote init-id) arg:expr) (#%plain-app cons (quote init-id) arg:expr)
...)) ...))
(define provided-inits (syntax->datum #'(init-id ...))) (define provided-inits (syntax->datum #'(init-id ...)))
(for ([name provided-inits]) (super-init-stxs
(unless (dict-ref super-inits name #f)
(tc-error/expr "super-new: init argument ~a not accepted by superclass"
name)))
(values
(syntax->list #'(pos-arg ...)) (syntax->list #'(pos-arg ...))
(map cons provided-inits (syntax->list #'(arg ...))))])) (map cons provided-inits (syntax->list #'(arg ...))))]))
;; check-super-new : Listof<Syntax> Dict<Symbol, Syntax> ;; handle-pos-inits : super-init-stxs Dict (Option Type) -> Type Dict
;; Dict<Symbol, Type> Type -> Void ;; Check if the init-rest type works and return a potentially changed
;; init-rest type and the remaining init args after
(define (handle-pos-inits super-new super-inits super-init-rest)
(match-define (super-init-stxs provided-pos-args provided-super-inits)
super-new)
(define provided-init-names (dict-keys provided-super-inits))
(define pos-length (length provided-pos-args))
(cond [;; too many init arguments, and no init-rest
(and (not super-init-rest) (> pos-length (length super-inits)))
(values super-init-rest
(tc-error/expr "too many positional init arguments provided"
#:return null))]
[;; no remaining by-name inits, so change the init-rest type
;; and return a null remaining named inits list
(> pos-length (length super-inits))
(values (Un) null)]
[else
(values super-init-rest
(for/list ([(name val) (in-dict (drop super-inits pos-length))]
#:unless (member name provided-init-names))
(cons name val)))]))
;; check-by-name : super-init-stxs Dict -> Void
;; Check by-name inits for duplicates
(define (check-by-name init-stxs super-inits)
(match-define (super-init-stxs _ by-name) init-stxs)
(for/and ([(name _) (in-dict by-name)])
(and (dict-ref super-inits name #f)
(tc-error/expr "super-new: init argument ~a not accepted by superclass"
name
#:return #f))))
;; check-super-new : super-init-stxs Dict Type -> Void
;; Check if the super-new call is well-typed ;; Check if the super-new call is well-typed
(define (check-super-new provided-pos-args provided-inits super-inits init-rest) (define (check-super-new super-new super-inits init-rest)
(match-define (super-init-stxs provided-pos-args provided-inits)
super-new)
(define pos-init-diff (- (length provided-pos-args) (length super-inits))) (define pos-init-diff (- (length provided-pos-args) (length super-inits)))
(cond [(and (> pos-init-diff 0) (not init-rest)) (cond [(and (> pos-init-diff 0) (not init-rest))
;; errror case that's caught above, do nothing ;; errror case that's caught above, do nothing
@ -1133,58 +1208,17 @@
(recur-on-all #'(e ...))] (recur-on-all #'(e ...))]
[_ '()])) [_ '()]))
;; register-annotations : Listof<Syntax> ;; setup-pubment-defaults : Listof<Symbol> Hash Hash -> Void
;; -> Dict<Symbol, Type>, Dict<Symbol, Type>
;; Find : annotations and register them, error if duplicates are found
;; TODO: support `define-type`?
(define (register-annotations stxs)
;; check if the key is duplicated and return the new table
;; (erroring if it is a duplicate)
(define (check-duplicate table name type)
(cond [(and (hash-has-key? table name)
(not (equal? (hash-ref table name) type)))
(tc-error/expr
#:stx #'name
"Duplicate type annotation of ~a for ~a, previous was ~a"
type name (hash-ref table name))
table]
[else (hash-set table name type)]))
(for/fold ([table #hash()] [augment-table #hash()])
([stx stxs])
(syntax-parse stx
#:literals (let-values begin quote-syntax :-internal :-augment
#%plain-app values void)
[(let-values ((()
(begin
(quote-syntax (:-internal name-stx:id type-stx:expr))
(#%plain-app values))))
(#%plain-app void))
(define name (syntax-e #'name-stx))
(define type (parse-type #'type-stx))
(values (check-duplicate table name type) augment-table)]
[(quote-syntax (:-augment name-stx:id type-stx))
(define name (syntax-e #'name-stx))
(define type (parse-type #'type-stx))
(values table (check-duplicate augment-table name type))]
[_ (values table augment-table)])))
;; setup-pubment-defaults : Listof<Symbol> ->
;; Dict<Symbol, Type> Dict<Symbol, Type> ->
;; Dict<Symbol, Type> Dict<Symbol, Type>
;; this does a second pass through the type annotations and adds ;; this does a second pass through the type annotations and adds
;; the pubment types as default augment types if an augment type ;; the pubment types as default augment types if an augment type
;; was not already provided ;; was not already provided
(define ((setup-pubment-defaults pubment-names) (define (setup-pubment-defaults pubment-names annotations augment-annotations)
annotations augment-annotations) (for ([name pubment-names])
(for/fold ([annotations annotations] (when (and (not (hash-has-key? augment-annotations name))
[augment-annotations augment-annotations]) (hash-has-key? annotations name))
([name pubment-names]) (hash-set! augment-annotations
(cond [(and (not (dict-has-key? augment-annotations name)) name
(dict-has-key? annotations name)) (dict-ref annotations name)))))
(values annotations
(dict-set augment-annotations name
(dict-ref annotations name)))]
[else (values annotations augment-annotations)])))
;; infer-self-type : Dict RowVar Class Dict<Symbol, Type> Dict<Symbol, Type> ;; infer-self-type : Dict RowVar Class Dict<Symbol, Type> Dict<Symbol, Type>
;; Set<Symbol> Dict<Symbol, Symbol> ;; Set<Symbol> Dict<Symbol, Symbol>

View File

@ -1162,7 +1162,7 @@
(super-new) (super-new)
(: x String) (: x String)
(field [x : Symbol 0])) (field [x : Symbol 0]))
#:msg #rx"Duplicate type annotation of String"] #:msg #rx"duplicate type annotation.*new type: String"]
;; fails, expected type and annotation don't match ;; fails, expected type and annotation don't match
[tc-err (let () [tc-err (let ()
(: c% (Class (field [x String]))) (: c% (Class (field [x String])))