Add prop:struct-field-info

This commit adds `prop:struct-field-info` which is implemented to provide
static information about field names. The property is attached to all
struct types generated by `define-struct`.

The commit also modifies kernstructs to have the property.
Finally, the commit switches `struct-copy` to use the static field name
information when it's available.

It remains to change `contract-out` and `match`'s `struct*` to
recognize/attach this new property, but this could be done
separately in the future.

This PR is largely based on racket/racket#732, though the approach is slightly
different.
This commit is contained in:
sorawee 2020-06-29 10:06:38 -07:00 committed by GitHub
parent 1118705bef
commit dff6259ff0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 203 additions and 29 deletions

View File

@ -789,7 +789,8 @@ derived from @racket[struct:struct-info] or with the
@racket[prop:struct-info] property that also implements
@racket[prop:procedure], and where the instance is further is wrapped
by @racket[make-set!-transformer]. In addition, the representation may
implement the @racket[prop:struct-auto-info] property.
implement the @racket[prop:struct-auto-info] and
@racket[prop:struct-field-info] properties.
Use @racket[struct-info?] to recognize all allowed forms of the
information, and use @racket[extract-struct-info] to obtain a list
@ -919,6 +920,36 @@ subset of the accessor identifiers for the structure type described by
@racket[sai], and the second list should be a subset of the mutator
identifiers. The two subsets correspond to @racket[#:auto] fields.}
@deftogether[(
@defthing[prop:struct-field-info struct-type-property?]
@defproc[(struct-field-info? [v any/c]) boolean?]
@defproc[(struct-field-info-list [sfi struct-field-info?]) (listof symbol?)])]{
The @racket[prop:struct-field-info] property is implemented to provide
static information about field names in a structure type. The property
value must be a procedure that accepts an instance structure to which
the property is given, and the result must be a list of symbols
suitable as a result from @racket[struct-field-info-list].
The @racket[struct-field-info?] predicate recognizes values that
implement the @racket[prop:struct-field-info] property.
The @racket[struct-field-info-list] function extracts a list of
symbols from a value that implements the @racket[prop:struct-field-info] property.
The list should contain every immediate field name
(that is, not including fields from its super struct type)
in the reverse order.
@examples[#:escape no-escape
#:eval struct-eval
(struct foo (x))
(struct bar foo (y z))
(define-syntax (get-bar-field-names stx)
#`'#,(struct-field-info-list (syntax-local-value #'bar)))
(get-bar-field-names)
]
}
@; ----------------------------------------------------------------------
@close-eval[struct-eval]

View File

@ -1467,4 +1467,14 @@
;; ----------------------------------------
(let ()
(struct foo (x))
(struct bar foo (y z))
(define-syntax (get-bar-field-names stx)
#`'#,(struct-field-info-list (syntax-local-value #'bar)))
(define (get-bar-field-names*) (get-bar-field-names))
(test '(z y) get-bar-field-names*))
;; ----------------------------------------
(report-errs)

View File

@ -20,13 +20,25 @@
(for-syntax
(rename checked-struct-info-rec? checked-struct-info?)))
(define-values-for-syntax
(struct:struct-field-info
make-struct-field-info
struct-field-info-rec?
struct-field-info-ref
struct-field-info-set!)
(make-struct-type 'struct-field-info struct:struct-info
1 0 #f
(list (cons prop:struct-field-info
(lambda (rec)
(struct-field-info-ref rec 0))))))
(define-values-for-syntax
(struct:struct-auto-info
make-struct-auto-info
struct-auto-info-rec?
struct-auto-info-ref
struct-auto-info-set!)
(make-struct-type 'struct-auto-info struct:struct-info
(make-struct-type 'struct-auto-info struct:struct-field-info
1 0 #f
(list (cons prop:struct-auto-info
(lambda (rec)
@ -48,10 +60,10 @@
"bad syntax;\n identifier for static struct-type information cannot be used as an expression"
stx))
null
(lambda (proc autos info)
(lambda (proc fields autos info)
(if (and (procedure? proc)
(procedure-arity-includes? proc 0))
(values proc autos)
(values proc fields autos)
(raise-argument-error 'make-struct-info
"(procedure-arity-includes/c 0)"
proc)))))
@ -412,7 +424,7 @@
(and (identifier? #'id)
(identifier? #'super-id))
(values #'id #'super-id)]
[else
[_
(raise-syntax-error
#f
"bad syntax;\n expected <id> for structure-type name or (<id> <id>) for name and supertype\n name"
@ -725,6 +737,7 @@
(if super-expr
#f
#t))))
'#,(map field-id (reverse fields))
#,@(if include-autos?
(list #`(list (list #,@(map protect
(list-tail sels (- (length sels) auto-count)))
@ -896,26 +909,46 @@
"unable to cope with a struct type whose predicate doesn't end with `?'"
orig-stx)]))
(define-for-syntax (find-accessor the-struct-info fld stx)
(define-for-syntax (find-accessor/no-field-info the-struct-info fld stx)
(define accessors (list-ref the-struct-info 3))
(define parent (list-ref the-struct-info 5))
(define num-fields (length accessors))
(define num-super-fields
(if (identifier? parent) (length (cadddr (id->struct-info parent stx))) 0))
(if (identifier? parent)
(let-values ([(parent-struct-info _) (id->struct-info parent stx)])
(length (cadddr parent-struct-info)))
0))
(define num-own-fields (- num-fields num-super-fields))
(define own-accessors (take accessors num-own-fields))
(define struct-name (predicate->struct-name stx (list-ref the-struct-info 2)))
(define accessor-name (string->symbol (format "~a-~a" struct-name (syntax-e fld))))
(or (findf (λ (a) (eq? accessor-name (syntax-e a))) own-accessors)
(raise-syntax-error
#f "accessor name not associated with the given structure type"
#f "field name not associated with the given structure type"
stx fld)))
(define-for-syntax (find-accessor/field-info the-struct-info the-field-info fld stx)
(define accessors (list-ref the-struct-info 3))
(define num-own-fields (length the-field-info))
(define own-accessors (take accessors num-own-fields))
(car
(or (findf (λ (a) (eq? (syntax-e fld) (cdr a))) (map cons own-accessors the-field-info))
(raise-syntax-error
#f "field name not associated with the given structure type"
stx fld))))
(define-for-syntax (find-accessor the-struct-info maybe-field-info fld stx)
(if maybe-field-info
(find-accessor/field-info the-struct-info maybe-field-info fld stx)
(find-accessor/no-field-info the-struct-info fld stx)))
(define-for-syntax (id->struct-info id stx)
(define the-struct-info (syntax-local-value id (lambda () #f)))
(unless (struct-info? the-struct-info)
(define compile-time-info (syntax-local-value id (lambda () #f)))
(unless (struct-info? compile-time-info)
(raise-syntax-error #f "identifier is not bound to a structure type" stx id))
(extract-struct-info the-struct-info))
(values (extract-struct-info compile-time-info)
(and (struct-field-info? compile-time-info)
(struct-field-info-list compile-time-info))))
(define-for-syntax (struct-copy-core stx)
(with-syntax ([(form-name info struct-expr field+val ...) stx])
@ -953,7 +986,7 @@
an)]))
ans)
(define the-struct-info (id->struct-info #'info stx))
(define-values (the-struct-info maybe-field-info) (id->struct-info #'info stx))
(define construct (cadr the-struct-info))
(define pred (caddr the-struct-info))
(define accessors (cadddr the-struct-info))
@ -970,7 +1003,8 @@
[else
(let ([v (syntax-local-value parent (lambda () #f))])
(unless (struct-info? v)
(raise-syntax-error #f "unknown parent struct" stx id)) ;; probably won't happen(?)
;; this is possible: https://gist.github.com/deeglaze/6ae7424cc2d093661df2
(raise-syntax-error #f "unknown parent struct" stx id))
(let ([v (extract-struct-info v)])
(loop (list-ref v 5))))])))
@ -978,15 +1012,16 @@
(map (lambda (an)
(syntax-case an ()
[(field expr)
(list (find-accessor the-struct-info #'field stx)
(list (find-accessor the-struct-info maybe-field-info #'field stx)
#'expr
(car (generate-temporaries (list #'field))))]
[(field #:parent id expr)
(begin
(ensure-really-parent #'id)
(list (find-accessor (id->struct-info #'id stx) #'field stx)
#'expr
(car (generate-temporaries (list #'field)))))]))
(let-values ([(the-struct-info maybe-field-info) (id->struct-info #'id stx)])
(list (find-accessor the-struct-info maybe-field-info #'field stx)
#'expr
(car (generate-temporaries (list #'field))))))]))
ans))
;; new-binding-for : syntax[field-name] -> (union syntax[expression] #f)

View File

@ -6,12 +6,28 @@
(#%require "define.rkt")
(#%require (for-syntax "struct-info.rkt"))
(#%provide (all-defined))
(define-values-for-syntax
(struct:struct-field-info
make-struct-field-info
struct-field-info-rec?
struct-field-info-ref
struct-field-info-set!)
(make-struct-type
'struct-field-info
struct:struct-info
1
0
#f
(list
(cons
prop:struct-field-info
(lambda (rec) (struct-field-info-ref rec 0))))))
(define-values-for-syntax
(make-self-ctr-struct-info)
(letrec-values (((struct: make- ? ref set!)
(make-struct-type
'self-ctor-struct-info
struct:struct-info
struct:struct-field-info
1
0
#f
@ -46,6 +62,7 @@
(quote-syntax exn-message))
'(#f #f)
#t))
'(continuation-marks message)
(λ () (quote-syntax kernel:exn)))))
(begin
(#%require (rename '#%kernel kernel:exn:fail exn:fail))
@ -62,6 +79,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn)))
'()
(λ () (quote-syntax kernel:exn:fail)))))
(begin
(#%require (rename '#%kernel kernel:exn:fail:contract exn:fail:contract))
@ -78,6 +96,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail)))
'()
(λ () (quote-syntax kernel:exn:fail:contract)))))
(begin
(#%require
@ -95,6 +114,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail:contract)))
'()
(λ () (quote-syntax kernel:exn:fail:contract:arity)))))
(begin
(#%require
@ -115,6 +135,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail:contract)))
'()
(λ () (quote-syntax kernel:exn:fail:contract:divide-by-zero)))))
(begin
(#%require
@ -135,6 +156,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail:contract)))
'()
(λ () (quote-syntax kernel:exn:fail:contract:non-fixnum-result)))))
(begin
(#%require
@ -155,6 +177,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail:contract)))
'()
(λ () (quote-syntax kernel:exn:fail:contract:continuation)))))
(begin
(#%require
@ -175,6 +198,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn:fail:contract)))
'(id)
(λ () (quote-syntax kernel:exn:fail:contract:variable)))))
(begin
(#%require (rename '#%kernel kernel:exn:fail:syntax exn:fail:syntax))
@ -192,6 +216,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn:fail)))
'(exprs)
(λ () (quote-syntax kernel:exn:fail:syntax)))))
(begin
(#%require
@ -210,6 +235,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn:fail:syntax)))
'()
(λ () (quote-syntax kernel:exn:fail:syntax:unbound)))))
(begin
(#%require
@ -232,6 +258,7 @@
(quote-syntax exn-message))
'(#f #f #f #f)
(quote-syntax exn:fail:syntax)))
'(path)
(λ () (quote-syntax kernel:exn:fail:syntax:missing-module)))))
(begin
(#%require (rename '#%kernel kernel:exn:fail:read exn:fail:read))
@ -249,6 +276,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn:fail)))
'(srclocs)
(λ () (quote-syntax kernel:exn:fail:read)))))
(begin
(#%require (rename '#%kernel kernel:exn:fail:read:eof exn:fail:read:eof))
@ -266,6 +294,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn:fail:read)))
'()
(λ () (quote-syntax kernel:exn:fail:read:eof)))))
(begin
(#%require
@ -284,6 +313,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn:fail:read)))
'()
(λ () (quote-syntax kernel:exn:fail:read:non-char)))))
(begin
(#%require
@ -301,6 +331,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail)))
'()
(λ () (quote-syntax kernel:exn:fail:filesystem)))))
(begin
(#%require
@ -320,6 +351,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail:filesystem)))
'()
(λ () (quote-syntax kernel:exn:fail:filesystem:exists)))))
(begin
(#%require
@ -340,6 +372,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail:filesystem)))
'()
(λ () (quote-syntax kernel:exn:fail:filesystem:version)))))
(begin
(#%require
@ -360,6 +393,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn:fail:filesystem)))
'(errno)
(λ () (quote-syntax kernel:exn:fail:filesystem:errno)))))
(begin
(#%require
@ -381,6 +415,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn:fail:filesystem)))
'(path)
(λ () (quote-syntax kernel:exn:fail:filesystem:missing-module)))))
(begin
(#%require (rename '#%kernel kernel:exn:fail:network exn:fail:network))
@ -397,6 +432,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail)))
'()
(λ () (quote-syntax kernel:exn:fail:network)))))
(begin
(#%require
@ -415,6 +451,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn:fail:network)))
'(errno)
(λ () (quote-syntax kernel:exn:fail:network:errno)))))
(begin
(#%require
@ -432,6 +469,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail)))
'()
(λ () (quote-syntax kernel:exn:fail:out-of-memory)))))
(begin
(#%require
@ -449,6 +487,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail)))
'()
(λ () (quote-syntax kernel:exn:fail:unsupported)))))
(begin
(#%require (rename '#%kernel kernel:exn:fail:user exn:fail:user))
@ -465,6 +504,7 @@
(quote-syntax exn-message))
'(#f #f)
(quote-syntax exn:fail)))
'()
(λ () (quote-syntax kernel:exn:fail:user)))))
(begin
(#%require (rename '#%kernel kernel:exn:break exn:break))
@ -482,6 +522,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn)))
'(continuation)
(λ () (quote-syntax kernel:exn:break)))))
(begin
(#%require (rename '#%kernel kernel:exn:break:hang-up exn:break:hang-up))
@ -499,6 +540,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn:break)))
'()
(λ () (quote-syntax kernel:exn:break:hang-up)))))
(begin
(#%require
@ -517,6 +559,7 @@
(quote-syntax exn-message))
'(#f #f #f)
(quote-syntax exn:break)))
'()
(λ () (quote-syntax kernel:exn:break:terminate)))))
(begin
(#%require (rename '#%kernel kernel:arity-at-least arity-at-least))
@ -531,6 +574,7 @@
(list (quote-syntax arity-at-least-value))
'(#f)
#t))
'(value)
(λ () (quote-syntax kernel:arity-at-least)))))
(begin
(#%require (rename '#%kernel kernel:date date))
@ -555,6 +599,16 @@
(quote-syntax date-second))
'(#f #f #f #f #f #f #f #f #f #f)
#t))
'(time-zone-offset
dst?
year-day
week-day
year
month
day
hour
minute
second)
(λ () (quote-syntax kernel:date)))))
(begin
(#%require (rename '#%kernel kernel:date* date*))
@ -581,6 +635,7 @@
(quote-syntax date-second))
'(#f #f #f #f #f #f #f #f #f #f #f #f)
(quote-syntax date)))
'(time-zone-name nanosecond)
(λ () (quote-syntax kernel:date*)))))
(begin
(#%require (rename '#%kernel kernel:srcloc srcloc))
@ -600,4 +655,5 @@
(quote-syntax srcloc-source))
'(#f #f #f #f #f)
#t))
'(span position column line source)
(λ () (quote-syntax kernel:srcloc))))))

View File

@ -13,7 +13,11 @@
prop:struct-auto-info
struct-auto-info?
struct-auto-info-lists)
struct-auto-info-lists
prop:struct-field-info
struct-field-info?
struct-field-info-list)
(define-values (prop:struct-info has-struct-info-prop? struct-info-prop-ref)
(make-struct-type-property 'struct-info
@ -131,4 +135,25 @@
(error 'struct-auto-info-lists
"struct-auto-info procedure result not properly formed: ~e"
l))
l)))
(define-values (prop:struct-field-info
struct-field-info?
struct-field-info-ref)
(make-struct-type-property 'struct-field-info
(lambda (val info)
(unless (and (procedure? val)
(procedure-arity-includes? val 1))
(raise-argument-error 'guard-for-prop:struct-field-info "(procedure-arity-includes/c 1)" val))
val)))
(define-values (struct-field-info-list)
(lambda (v)
(unless (struct-field-info? v)
(raise-argument-error 'struct-field-info-list "struct-field-info?" v))
(let ([l ((struct-field-info-ref v) v)])
(unless (and (list? l) (andmap symbol? l))
(error 'struct-field-info-list
"struct-field-info procedure result not properly formed: ~e"
l))
l))))

View File

@ -208,9 +208,22 @@ Not an exception in the above sense:
(#%provide (all-defined))
(define-values-for-syntax
(struct:struct-field-info
make-struct-field-info
struct-field-info-rec?
struct-field-info-ref
struct-field-info-set!)
(make-struct-type 'struct-field-info struct:struct-info
1 0 #f
(list (cons prop:struct-field-info
(lambda (rec)
(struct-field-info-ref rec 0))))))
(define-values-for-syntax (make-self-ctr-struct-info)
(letrec-values ([(struct: make- ? ref set!)
(make-struct-type 'self-ctor-struct-info struct:struct-info
(make-struct-type 'self-ctor-struct-info struct:struct-field-info
1 0 #f
(list (cons prop:procedure
(lambda (v stx)
@ -230,7 +243,7 @@ Not an exception in the above sense:
(define (non-parent x)
(or (equal? #f x) (equal? #t x)))
(define (gen-ds name-string fields parent)
(define (gen-ds name-string fields num-selector parent)
(let* ([name (sss name-string)]
[kern-name (sss "kernel:" name)]
[sn (sss "struct:" name)]
@ -238,7 +251,11 @@ Not an exception in the above sense:
[pn (sss name "?")]
[fds `(list ,@(map (λ (x) `(quote-syntax ,x)) fields))]
[fdsset! `'(,@(map (λ (x) #f) fields))]
[prnt (if (non-parent parent) #t `(quote-syntax ,parent))])
[prnt (if (non-parent parent) #t `(quote-syntax ,parent))]
[name-length (string-length (symbol->string name))]
[field-names (for/list ([fld (take fields num-selector)])
;; add1 for hyphen
(string->symbol (substring (symbol->string fld) (add1 name-length))))])
`(begin
(#%require (rename '#%kernel ,kern-name ,name))
(define ,mn ,kern-name)
@ -248,6 +265,7 @@ Not an exception in the above sense:
(quote-syntax ,pn)
,fds
,fdsset! ,prnt))
',field-names
(λ () (quote-syntax ,kern-name)))))))
(define (parent-sym x)
@ -262,17 +280,16 @@ Not an exception in the above sense:
(if (non-parent exn)
null
(append (reverse (map (λ (field) (field-name exn field)) (ex-args exn))) (fields (ex-parent exn)))))
(define exceptions (map (λ (x) (gen-ds (ex-string x) (fields x) (parent-sym x))) l))
(define exceptions (map (λ (x) (gen-ds (ex-string x) (fields x) (length (ex-args x)) (parent-sym x))) l))
(define structs (map (λ (x) (apply gen-ds x))
'((arity-at-least (arity-at-least-value) #t)
'((arity-at-least (arity-at-least-value) 1 #t)
(date (date-time-zone-offset date-dst? date-year-day date-week-day date-year
date-month date-day date-hour date-minute date-second) #t)
date-month date-day date-hour date-minute date-second) 10 #t)
(date* (date*-time-zone-name date*-nanosecond
date-time-zone-offset date-dst? date-year-day date-week-day date-year
date-month date-day date-hour date-minute date-second) date)
(srcloc (srcloc-span srcloc-position srcloc-column srcloc-line srcloc-source) #t))))
date-month date-day date-hour date-minute date-second) 2 date)
(srcloc (srcloc-span srcloc-position srcloc-column srcloc-line srcloc-source) 5 #t))))
(with-output-to-file filename #:exists 'replace
(λ ()