Improve error messages for invalid record definitions.
authorMark H Weaver <mhw@netris.org>
Fri, 9 Nov 2012 08:22:40 +0000 (03:22 -0500)
committerMark H Weaver <mhw@netris.org>
Sat, 10 Nov 2012 04:05:42 +0000 (23:05 -0500)
* module/srfi/srfi-9.scm (%define-record-type): Accept additional 'form'
  parameter which contains the original form of 'define-record-type' or
  'define-immutable-record-type'.  Add elaborate pattern guard which
  raises descriptive syntax errors for specific errors, and a fallback
  pattern to catch anything else.
  (define-record-type): Pass 'form' parameter to %define-record-type.

* module/srfi/srfi-9/gnu.scm (define-immutable-record-type): Pass 'form'
  parameter to %define-record-type.

* test-suite/tests/srfi-9.test: Add tests.

module/srfi/srfi-9.scm
module/srfi/srfi-9/gnu.scm
test-suite/tests/srfi-9.test

index 1dd132a..de49459 100644 (file)
                                  (let ((name (syntax->datum field)))
                                    (or (memq name field-names)
                                        (syntax-violation
-                                        'define-record-type
-                                        "unknown field in constructor-spec"
+                                        (syntax-case form ()
+                                          ((macro . args)
+                                           (syntax->datum #'macro)))
+                                        "unknown field in constructor spec"
                                         form field))
                                    (cons name field)))
                                #'(field ...))))
         (string-concatenate (make-list count desc))))
 
     (syntax-case x ()
-      ((_ immutable? type-name constructor-spec predicate-name
+      ((_ immutable? form type-name constructor-spec predicate-name
           field-spec ...)
-       (boolean? (syntax->datum #'immutable?))
+       (let ()
+         (define (syntax-error message subform)
+           (syntax-violation (syntax-case #'form ()
+                               ((macro . args) (syntax->datum #'macro)))
+                             message #'form subform))
+         (and (boolean? (syntax->datum #'immutable?))
+              (or (identifier? #'type-name)
+                  (syntax-error "expected type name" #'type-name))
+              (syntax-case #'constructor-spec ()
+                ((ctor args ...)
+                 (every identifier? #'(ctor args ...))
+                 #t)
+                (_ (syntax-error "invalid constructor spec"
+                                 #'constructor-spec)))
+              (or (identifier? #'predicate-name)
+                  (syntax-error "expected predicate name" #'predicate-name))
+              (every (lambda (spec)
+                       (syntax-case spec ()
+                         ((field getter) #t)
+                         ((field getter setter) #t)
+                         (_ (syntax-error "invalid field spec" spec))))
+                     #'(field-spec ...))))
        (let* ((field-ids   (field-identifiers  #'(field-spec ...)))
               (getter-ids  (getter-identifiers #'(field-spec ...)))
               (field-count (length field-ids))
                              ((ctor args ...) #'ctor)))
               (copier-id   (make-copier-id #'type-name)))
          #`(begin
-             #,(constructor x #'type-name #'constructor-spec field-names)
+             #,(constructor #'form #'type-name #'constructor-spec field-names)
 
              (define type-name
                (let ((rtd (make-struct/no-tail
              #,(copier #'type-name getter-ids copier-id)
              #,@(if immutable?
                     (functional-setters copier-id #'(field-spec ...))
-                    (setters #'type-name #'(field-spec ...)))))))))
+                    (setters #'type-name #'(field-spec ...))))))
+      ((_ immutable? form . rest)
+       (syntax-violation
+        (syntax-case #'form ()
+          ((macro . args) (syntax->datum #'macro)))
+        "invalid record definition syntax"
+        #'form)))))
 
 (define-syntax-rule (define-record-type name ctor pred fields ...)
-  (%define-record-type #f name ctor pred fields ...))
+  (%define-record-type #f (define-record-type name ctor pred fields ...)
+                       name ctor pred fields ...))
 
 ;;; srfi-9.scm ends here
index fa091fe..4f3a663 100644 (file)
@@ -34,7 +34,9 @@
   (struct-set! type vtable-index-printer thunk))
 
 (define-syntax-rule (define-immutable-record-type name ctor pred fields ...)
-  ((@@ (srfi srfi-9) %define-record-type) #t name ctor pred fields ...))
+  ((@@ (srfi srfi-9) %define-record-type)
+   #t (define-immutable-record-type name ctor pred fields ...)
+   name ctor pred fields ...))
 
 (define-syntax-rule (set-field (getter ...) s expr)
   (%set-fields #t (set-field (getter ...) s expr) ()
index d1f1555..4935148 100644 (file)
         (lambda (key whom what src form subform)
           (list key whom what form subform))))))
 
+\f
+(with-test-prefix "record type definition error reporting"
+
+  (pass-if-equal "invalid type name"
+      '(syntax-error define-immutable-record-type
+                     "expected type name"
+                     (define-immutable-record-type
+                       (foobar x y)
+                       foobar?
+                       (x foobar-x)
+                       (y foobar-y))
+                     (foobar x y))
+    (catch 'syntax-error
+      (lambda ()
+        (compile '(define-immutable-record-type
+                    (foobar x y)
+                    foobar?
+                    (x foobar-x)
+                    (y foobar-y))
+                 #:env (current-module))
+        #f)
+      (lambda (key whom what src form subform)
+        (list key whom what form subform))))
+
+  (pass-if-equal "invalid constructor spec"
+      '(syntax-error define-immutable-record-type
+                     "invalid constructor spec"
+                     (define-immutable-record-type :foobar
+                       (make-foobar x y 3)
+                       foobar?
+                       (x foobar-x)
+                       (y foobar-y))
+                     (make-foobar x y 3))
+    (catch 'syntax-error
+      (lambda ()
+        (compile '(define-immutable-record-type :foobar
+                    (make-foobar x y 3)
+                    foobar?
+                    (x foobar-x)
+                    (y foobar-y))
+                 #:env (current-module))
+        #f)
+      (lambda (key whom what src form subform)
+        (list key whom what form subform))))
+
+  (pass-if-equal "invalid predicate name"
+      '(syntax-error define-immutable-record-type
+                     "expected predicate name"
+                     (define-immutable-record-type :foobar
+                       (foobar x y)
+                       (x foobar-x)
+                       (y foobar-y))
+                     (x foobar-x))
+    (catch 'syntax-error
+      (lambda ()
+        (compile '(define-immutable-record-type :foobar
+                    (foobar x y)
+                    (x foobar-x)
+                    (y foobar-y))
+                 #:env (current-module))
+        #f)
+      (lambda (key whom what src form subform)
+        (list key whom what form subform))))
+
+  (pass-if-equal "invalid field spec"
+      '(syntax-error define-record-type
+                     "invalid field spec"
+                     (define-record-type :foobar
+                       (make-foobar x y)
+                       foobar?
+                       (x)
+                       (y foobar-y))
+                     (x))
+    (catch 'syntax-error
+      (lambda ()
+        (compile '(define-record-type :foobar
+                    (make-foobar x y)
+                    foobar?
+                    (x)
+                    (y foobar-y))
+                 #:env (current-module))
+        #f)
+      (lambda (key whom what src form subform)
+        (list key whom what form subform))))
+
+    (pass-if-equal "unknown field in constructor spec"
+      '(syntax-error define-record-type
+                     "unknown field in constructor spec"
+                     (define-record-type :foobar
+                       (make-foobar x z)
+                       foobar?
+                       (x foobar-x)
+                       (y foobar-y))
+                     z)
+    (catch 'syntax-error
+      (lambda ()
+        (compile '(define-record-type :foobar
+                    (make-foobar x z)
+                    foobar?
+                    (x foobar-x)
+                    (y foobar-y))
+                 #:env (current-module))
+        #f)
+      (lambda (key whom what src form subform)
+        (list key whom what form subform)))))
+
 (with-test-prefix "record compatibility"
 
   (pass-if "record?"