1
Fork 0
mirror of https://git.savannah.gnu.org/git/guile.git synced 2025-04-30 03:40:34 +02:00

Add support for immutable fields in core records

* module/ice-9/boot-9.scm (make-record-type): Allow (mutable NAME)
  or (immutable NAME) as a field name, and record field mutability in a
  bitfield.
  (record-modifier): Throw an error if the field isn't mutable.
* test-suite/tests/records.test ("records"): Add tests.
* doc/ref/api-data.texi (Records): Update.
This commit is contained in:
Andy Wingo 2019-10-28 16:58:22 +01:00
parent f963bdf02d
commit 315fabdfe7
3 changed files with 78 additions and 8 deletions

View file

@ -8637,8 +8637,10 @@ Create and return a new @dfn{record-type descriptor}.
@var{type-name} is a string naming the type. Currently it's only used @var{type-name} is a string naming the type. Currently it's only used
in the printed representation of records, and in diagnostics. in the printed representation of records, and in diagnostics.
@var{field-names} is a list of symbols naming the fields of a record @var{field-names} is a list of elements of the form @code{(immutable
of the type. Duplicates are not allowed among these symbols. @var{name})}, @code{(mutable @var{name})}, or @var{name}, where
@var{name} are symbols naming the fields of a record of the type.
Duplicates are not allowed among these symbols.
@example @example
(make-record-type "employee" '(name age salary)) (make-record-type "employee" '(name age salary))
@ -8680,6 +8682,11 @@ the record type. @xref{rnrs records procedural}, for full details. The
@code{#:opaque?} flag is used by Guile's R6RS layer to record this @code{#:opaque?} flag is used by Guile's R6RS layer to record this
information. The default is determined by whether the parent type, if information. The default is determined by whether the parent type, if
any, was opaque. any, was opaque.
Fields are mutable by default, meaning that @code{record-modifier} will
return a procedure that can update a record in place. Specifying a
field using the form @code{(immutable @var{name})} instead marks a field
as immutable.
@end deffn @end deffn
@deffn {Scheme Procedure} record-constructor rtd @deffn {Scheme Procedure} record-constructor rtd

View file

@ -1191,10 +1191,10 @@ VALUE."
;; ;;
;; It should print OBJECT to PORT. ;; It should print OBJECT to PORT.
;; 0: type-name, 1: fields, 2: constructor, 3: flags, 4: parents ;; 0: type-name, 1: fields, 2: constructor, 3: flags, 4: parents 5: mutable bitmask
(define record-type-vtable (define record-type-vtable
(let ((s (make-vtable (string-append standard-vtable-fields (let ((s (make-vtable (string-append standard-vtable-fields
"pwpwpwpwpw") "pwpwpwpwpwpw")
(lambda (s p) (lambda (s p)
(display "#<record-type " p) (display "#<record-type " p)
(display (record-type-name s) p) (display (record-type-name s) p)
@ -1235,6 +1235,11 @@ VALUE."
(error 'not-a-record-type rtd)) (error 'not-a-record-type rtd))
(struct-ref rtd (+ 4 vtable-offset-user))) (struct-ref rtd (+ 4 vtable-offset-user)))
(define (record-type-mutable-fields rtd)
(unless (record-type? rtd)
(error 'not-a-record-type rtd))
(struct-ref rtd (+ 5 vtable-offset-user)))
(define prefab-record-types (define prefab-record-types
(make-hash-table)) (make-hash-table))
@ -1329,12 +1334,36 @@ VALUE."
(cons field tail)))) (cons field tail))))
(define computed-fields (define computed-fields
(begin (let ((fields (map (lambda (field)
(cond
((symbol? field) field)
(else
(unless (and (pair? field)
(memq (car field) '(mutable immutable))
(pair? (cdr field))
(null? (cddr field)))
(error (error "bad field declaration" field)))
(cadr field))))
fields)))
(check-fields fields) (check-fields fields)
(if parent (if parent
(append-fields (record-type-fields parent) fields) (append-fields (record-type-fields parent) fields)
fields))) fields)))
(define mutable-fields
(let lp ((fields fields)
(i (if parent (length (record-type-fields parent)) 0))
(mutable (if parent (record-type-mutable-fields parent) 0)))
(if (null? fields)
mutable
(let ((field (car fields)))
(lp (cdr fields)
(1+ i)
(if (or (not (pair? field))
(eq? (car field) 'mutable))
(logior mutable (ash 1 i))
mutable))))))
(define name-sym (define name-sym
(cond (cond
((symbol? type-name) type-name) ((symbol? type-name) type-name)
@ -1359,7 +1388,8 @@ VALUE."
(equal? (record-type-fields rtd) computed-fields) (equal? (record-type-fields rtd) computed-fields)
(not printer) (not printer)
(equal? (record-type-properties rtd) properties) (equal? (record-type-properties rtd) properties)
(equal? (record-type-parents rtd) parents)) (equal? (record-type-parents rtd) parents)
(equal? (record-type-mutable-fields rtd) mutable-fields))
(error "prefab record type declaration incompatible with previous" (error "prefab record type declaration incompatible with previous"
rtd)) rtd))
rtd)) rtd))
@ -1374,7 +1404,8 @@ VALUE."
computed-fields computed-fields
#f ; Constructor initialized below. #f ; Constructor initialized below.
properties properties
parents))) parents
mutable-fields)))
(struct-set! rtd (+ vtable-offset-user 2) (struct-set! rtd (+ vtable-offset-user 2)
(make-constructor rtd (length computed-fields))) (make-constructor rtd (length computed-fields)))
@ -1446,6 +1477,8 @@ VALUE."
(pos (or (list-index (record-type-fields rtd) field-name) (pos (or (list-index (record-type-fields rtd) field-name)
(error 'no-such-field field-name))) (error 'no-such-field field-name)))
(pred (record-predicate rtd))) (pred (record-predicate rtd)))
(unless (logbit? pos (record-type-mutable-fields rtd))
(error "field is immutable" rtd field-name))
(lambda (obj val) (lambda (obj val)
(unless (pred obj) (unless (pred obj)
(scm-error 'wrong-type-arg "record-modifier" (scm-error 'wrong-type-arg "record-modifier"

View file

@ -166,4 +166,34 @@
(pass-if (not (record-type-opaque? b))) (pass-if (not (record-type-opaque? b)))
(pass-if (record-type-opaque? c)) (pass-if (record-type-opaque? c))
(pass-if-exception "non-opaque" '(misc-error . "opaque") (pass-if-exception "non-opaque" '(misc-error . "opaque")
(make-record-type 'd '() #:opaque? #f #:parent a))))) (make-record-type 'd '() #:opaque? #f #:parent a))))
(with-test-prefix "immutable fields"
(let ()
(define a (make-record-type 'a '(s t (mutable u) (immutable v))
#:extensible? #t))
(define b (make-record-type 'b '(w (immutable x)) #:parent a))
(pass-if-exception "bad field" '(misc-error . "field")
(make-record-type 'a '("foo")))
(pass-if-exception "bad field" '(misc-error . "field")
(make-record-type 'a '((mutable u x))))
(pass-if-exception "bad field" '(misc-error . "field")
(make-record-type 'a '((qux u))))
(pass-if-equal (record-type-mutable-fields a) #b0111)
(pass-if-equal (record-type-mutable-fields b) #b010111)
(pass-if (procedure? (record-modifier a 's)))
(pass-if (procedure? (record-modifier a 't)))
(pass-if (procedure? (record-modifier a 'u)))
(pass-if-exception "immutable" '(misc-error . "immutable")
(record-modifier a 'v))
(pass-if (procedure? (record-modifier b 's)))
(pass-if (procedure? (record-modifier b 't)))
(pass-if (procedure? (record-modifier b 'u)))
(pass-if-exception "immutable" '(misc-error . "immutable")
(record-modifier b 'v))
(pass-if (procedure? (record-modifier b 'w)))
(pass-if-exception "immutable" '(misc-error . "immutable")
(record-modifier b 'x)))))