+(test-variant vanilla-struct :boa-constructor-p t)
+(test-variant vector-struct :colontype vector :boa-constructor-p t)
+(test-variant list-struct :colontype list :boa-constructor-p t)
+
+\f
+;;;; testing raw slots harder
+;;;;
+;;;; The offsets of raw slots need to be rescaled during the punning
+;;;; process which is used to access them. That seems like a good
+;;;; place for errors to lurk, so we'll try hunting for them by
+;;;; verifying that all the raw slot data gets written successfully
+;;;; into the object, can be copied with the object, and can then be
+;;;; read back out (with none of it ending up bogusly outside the
+;;;; object, so that it couldn't be copied, or bogusly overwriting
+;;;; some other raw slot).
+
+(defstruct manyraw
+ (a (expt 2 30) :type (unsigned-byte #.sb-vm:n-word-bits))
+ (b 0.1 :type single-float)
+ (c 0.2d0 :type double-float)
+ (d #c(0.3 0.3) :type (complex single-float))
+ unraw-slot-just-for-variety
+ (e #c(0.4d0 0.4d0) :type (complex double-float))
+ (aa (expt 2 30) :type (unsigned-byte #.sb-vm:n-word-bits))
+ (bb 0.1 :type single-float)
+ (cc 0.2d0 :type double-float)
+ (dd #c(0.3 0.3) :type (complex single-float))
+ (ee #c(0.4d0 0.4d0) :type (complex double-float)))
+
+(defvar *manyraw* (make-manyraw))
+
+(assert (eql (manyraw-a *manyraw*) (expt 2 30)))
+(assert (eql (manyraw-b *manyraw*) 0.1))
+(assert (eql (manyraw-c *manyraw*) 0.2d0))
+(assert (eql (manyraw-d *manyraw*) #c(0.3 0.3)))
+(assert (eql (manyraw-e *manyraw*) #c(0.4d0 0.4d0)))
+(assert (eql (manyraw-aa *manyraw*) (expt 2 30)))
+(assert (eql (manyraw-bb *manyraw*) 0.1))
+(assert (eql (manyraw-cc *manyraw*) 0.2d0))
+(assert (eql (manyraw-dd *manyraw*) #c(0.3 0.3)))
+(assert (eql (manyraw-ee *manyraw*) #c(0.4d0 0.4d0)))
+
+(setf (manyraw-aa *manyraw*) (expt 2 31)
+ (manyraw-bb *manyraw*) 0.11
+ (manyraw-cc *manyraw*) 0.22d0
+ (manyraw-dd *manyraw*) #c(0.33 0.33)
+ (manyraw-ee *manyraw*) #c(0.44d0 0.44d0))
+
+(let ((copy (copy-manyraw *manyraw*)))
+ (assert (eql (manyraw-a copy) (expt 2 30)))
+ (assert (eql (manyraw-b copy) 0.1))
+ (assert (eql (manyraw-c copy) 0.2d0))
+ (assert (eql (manyraw-d copy) #c(0.3 0.3)))
+ (assert (eql (manyraw-e copy) #c(0.4d0 0.4d0)))
+ (assert (eql (manyraw-aa copy) (expt 2 31)))
+ (assert (eql (manyraw-bb copy) 0.11))
+ (assert (eql (manyraw-cc copy) 0.22d0))
+ (assert (eql (manyraw-dd copy) #c(0.33 0.33)))
+ (assert (eql (manyraw-ee copy) #c(0.44d0 0.44d0))))
+
+\f
+;;;; Since GC treats raw slots specially now, let's try this with more objects
+;;;; and random values as a stress test.
+
+(setf *manyraw* nil)
+
+(defconstant +n-manyraw+ 10)
+(defconstant +m-manyraw+ 1000)
+
+(defun check-manyraws (manyraws)
+ (assert (eql (length manyraws) (* +n-manyraw+ +m-manyraw+)))
+ (loop
+ for m in (reverse manyraws)
+ for i from 0
+ do
+ ;; Compare the tagged reference values with raw reffer results.
+ (destructuring-bind (j a b c d e)
+ (manyraw-unraw-slot-just-for-variety m)
+ (assert (eql i j))
+ (assert (= (manyraw-a m) a))
+ (assert (= (manyraw-b m) b))
+ (assert (= (manyraw-c m) c))
+ (assert (= (manyraw-d m) d))
+ (assert (= (manyraw-e m) e)))
+ ;; Test the funny out-of-line OAOOM-style closures, too.
+ (mapcar (lambda (fn value)
+ (assert (= (funcall fn m) value)))
+ (list #'manyraw-a
+ #'manyraw-b
+ #'manyraw-c
+ #'manyraw-d
+ #'manyraw-e)
+ (cdr (manyraw-unraw-slot-just-for-variety m)))))
+
+(defstruct (manyraw-subclass (:include manyraw))
+ (stolperstein 0 :type (unsigned-byte 32)))
+
+;;; create lots of manyraw objects, triggering GC every now and then
+(dotimes (y +n-manyraw+)
+ (dotimes (x +m-manyraw+)
+ (let ((a (random (expt 2 32)))
+ (b (random most-positive-single-float))
+ (c (random most-positive-double-float))
+ (d (complex
+ (random most-positive-single-float)
+ (random most-positive-single-float)))
+ (e (complex
+ (random most-positive-double-float)
+ (random most-positive-double-float))))
+ (push (funcall (if (zerop (mod x 3))
+ #'make-manyraw-subclass
+ #'make-manyraw)
+ :unraw-slot-just-for-variety
+ (list (+ x (* y +m-manyraw+)) a b c d e)
+ :a a
+ :b b
+ :c c
+ :d d
+ :e e)
+ *manyraw*)))
+ (room)
+ (sb-ext:gc))
+(with-test (:name :defstruct-raw-slot-gc)
+ (check-manyraws *manyraw*))
+
+;;; try a full GC, too
+(sb-ext:gc :full t)
+(with-test (:name (:defstruct-raw-slot-gc :full))
+ (check-manyraws *manyraw*))
+
+;;; fasl dumper and loader also have special handling of raw slots, so
+;;; dump all of them into a fasl
+(defmethod make-load-form ((self manyraw) &optional env)
+ self env
+ :sb-just-dump-it-normally)
+(with-open-file (s "tmp-defstruct.manyraw.lisp"
+ :direction :output
+ :if-exists :supersede)
+ (write-string "(defun dumped-manyraws () '#.*manyraw*)" s))
+(compile-file "tmp-defstruct.manyraw.lisp")
+(delete-file "tmp-defstruct.manyraw.lisp")
+
+;;; nuke the objects and try another GC just to be extra careful
+(setf *manyraw* nil)
+(sb-ext:gc :full t)
+
+;;; re-read the dumped structures and check them
+(load "tmp-defstruct.manyraw.fasl")
+(with-test (:name (:defstruct-raw-slot load))
+ (check-manyraws (dumped-manyraws)))
+
+\f
+;;;; miscellaneous old bugs
+
+(defstruct ya-struct)
+(when (ignore-errors (or (ya-struct-p) 12))
+ (error "YA-STRUCT-P of no arguments should signal an error."))
+(when (ignore-errors (or (ya-struct-p 'too 'many 'arguments) 12))
+ (error "YA-STRUCT-P of three arguments should signal an error."))
+
+;;; bug 210: Until sbcl-0.7.8.32 BOA constructors had SAFETY 0
+;;; declared inside on the theory that slot types were already
+;;; checked, which bogusly suppressed unbound-variable and other
+;;; checks within the evaluation of initforms.
+(defvar *bug210*)
+(defstruct (bug210a (:constructor bug210a ()))
+ (slot *bug210*))
+(defstruct bug210b
+ (slot *bug210*))
+;;; Because of bug 210, this assertion used to fail.
+(assert (typep (nth-value 1 (ignore-errors (bug210a))) 'unbound-variable))
+;;; Even with bug 210, these assertions succeeded.
+(assert (typep (nth-value 1 (ignore-errors *bug210*)) 'unbound-variable))
+(assert (typep (nth-value 1 (ignore-errors (make-bug210b))) 'unbound-variable))
+
+;;; In sbcl-0.7.8.53, DEFSTRUCT blew up in non-toplevel contexts
+;;; because it implicitly assumed that EVAL-WHEN (COMPILE) stuff
+;;; setting up compiler-layout information would run before the
+;;; constructor function installing the layout was compiled. Make sure
+;;; that doesn't happen again.
+(defun foo-0-7-8-53 () (defstruct foo-0-7-8-53 x (y :not)))
+(assert (not (find-class 'foo-0-7-8-53 nil)))
+(foo-0-7-8-53)
+(assert (find-class 'foo-0-7-8-53 nil))
+(let ((foo-0-7-8-53 (make-foo-0-7-8-53 :x :s)))
+ (assert (eq (foo-0-7-8-53-x foo-0-7-8-53) :s))
+ (assert (eq (foo-0-7-8-53-y foo-0-7-8-53) :not)))
+\f
+;;; tests of behaviour of colliding accessors.
+(defstruct (bug127-foo (:conc-name bug127-baz-)) a)
+(assert (= (bug127-baz-a (make-bug127-foo :a 1)) 1))
+(defstruct (bug127-bar (:conc-name bug127-baz-) (:include bug127-foo)) b)
+(assert (= (bug127-baz-a (make-bug127-bar :a 1 :b 2)) 1))
+(assert (= (bug127-baz-b (make-bug127-bar :a 1 :b 2)) 2))
+(assert (= (bug127-baz-a (make-bug127-foo :a 1)) 1))
+
+(defun bug127-flurble (x)
+ x)
+(defstruct bug127 flurble)
+(assert (= (bug127-flurble (make-bug127 :flurble 7)) 7))
+
+(defstruct bug127-a b-c)
+(assert (= (bug127-a-b-c (make-bug127-a :b-c 9)) 9))
+(defstruct (bug127-a-b (:include bug127-a)) c)
+(assert (= (bug127-a-b-c (make-bug127-a :b-c 9)) 9))
+(assert (= (bug127-a-b-c (make-bug127-a-b :b-c 11 :c 13)) 11))
+
+(defstruct (bug127-e (:conc-name bug127--)) foo)
+(assert (= (bug127--foo (make-bug127-e :foo 3)) 3))
+(defstruct (bug127-f (:conc-name bug127--)) foo)
+(assert (= (bug127--foo (make-bug127-f :foo 3)) 3))
+(assert (raises-error? (bug127--foo (make-bug127-e :foo 3)) type-error))
+
+;;; FIXME: should probably do the same tests on DEFSTRUCT :TYPE
+\f
+;;; As noted by Paul Dietz for CMUCL, :CONC-NAME handling was a little
+;;; too fragile:
+(defstruct (conc-name-syntax :conc-name) a-conc-name-slot)
+(assert (eq (a-conc-name-slot (make-conc-name-syntax :a-conc-name-slot 'y))
+ 'y))
+;;; and further :CONC-NAME NIL was being wrongly treated:
+(defpackage "DEFSTRUCT-TEST-SCRATCH")
+(defstruct (conc-name-nil :conc-name)
+ defstruct-test-scratch::conc-name-nil-slot)
+(assert (= (defstruct-test-scratch::conc-name-nil-slot
+ (make-conc-name-nil :conc-name-nil-slot 1)) 1))
+(assert (raises-error? (conc-name-nil-slot (make-conc-name-nil))
+ undefined-function))
+\f
+;;; The named/typed predicates were a little fragile, in that they
+;;; could throw errors on innocuous input:
+(defstruct (list-struct (:type list) :named) a-slot)
+(assert (list-struct-p (make-list-struct)))
+(assert (not (list-struct-p nil)))
+(assert (not (list-struct-p 1)))
+(defstruct (offset-list-struct (:type list) :named (:initial-offset 1)) a-slot)
+(assert (offset-list-struct-p (make-offset-list-struct)))
+(assert (not (offset-list-struct-p nil)))
+(assert (not (offset-list-struct-p 1)))
+(assert (not (offset-list-struct-p '(offset-list-struct))))
+(assert (not (offset-list-struct-p '(offset-list-struct . 3))))
+(defstruct (vector-struct (:type vector) :named) a-slot)
+(assert (vector-struct-p (make-vector-struct)))
+(assert (not (vector-struct-p nil)))
+(assert (not (vector-struct-p #())))
+\f
+
+;;; bug 3d: type safety with redefined type constraints on slots
+#+#.(cl:if (cl:eq sb-ext:*evaluator-mode* :compile) '(and) '(or))
+(macrolet
+ ((test (type)
+ (let* ((base-name (intern (format nil "bug3d-~A" type)))
+ (up-name (intern (format nil "~A-up" base-name)))
+ (accessor (intern (format nil "~A-X" base-name)))
+ (up-accessor (intern (format nil "~A-X" up-name)))
+ (type-options (when type `((:type ,type)))))
+ `(progn
+ (defstruct (,base-name ,@type-options)
+ x y)
+ (defstruct (,up-name (:include ,base-name
+ (x "x" :type simple-string)
+ (y "y" :type simple-string))
+ ,@type-options))
+ (let ((ob (,(intern (format nil "MAKE-~A" up-name)))))
+ (setf (,accessor ob) 0)
+ (loop for decl in '(inline notinline)
+ for fun = `(lambda (s)
+ (declare (optimize (safety 3))
+ (,decl ,',up-accessor))
+ (,',up-accessor s))
+ do (assert (raises-error? (funcall (compile nil fun) ob)
+ type-error))))))))
+ (test nil)
+ (test list)
+ (test vector))
+
+(let* ((name (gensym))
+ (form `(defstruct ,name
+ (x nil :type (or null (function (integer)
+ (values number &optional foo)))))))
+ (eval (copy-tree form))
+ (eval (copy-tree form)))
+
+;;; 322: "DEFSTRUCT :TYPE LIST predicate and improper lists"
+;;; reported by Bruno Haible sbcl-devel "various SBCL bugs" from CLISP
+;;; test suite.
+(defstruct (bug-332a (:type list) (:initial-offset 5) :named))
+(defstruct (bug-332b (:type list) (:initial-offset 2) :named (:include bug-332a)))
+(assert (not (bug-332b-p (list* nil nil nil nil nil 'foo73 nil 'tail))))
+(assert (not (bug-332b-p 873257)))
+(assert (not (bug-332b-p '(1 2 3 4 5 x 1 2 bug-332a))))
+(assert (bug-332b-p '(1 2 3 4 5 x 1 2 bug-332b)))
+
+;;; Similar test for vectors, just for good measure.
+(defstruct (bug-332a-aux (:type vector)
+ (:initial-offset 5) :named))
+(defstruct (bug-332b-aux (:type vector)
+ (:initial-offset 2) :named
+ (:include bug-332a-aux)))
+(assert (not (bug-332b-aux-p #(1 2 3 4 5 x 1 premature-end))))
+(assert (not (bug-332b-aux-p 873257)))
+(assert (not (bug-332b-aux-p #(1 2 3 4 5 x 1 2 bug-332a-aux))))
+(assert (bug-332b-aux-p #(1 2 3 4 5 x 1 2 bug-332b-aux)))
+
+;;; In sbcl-0.8.11.8 FBOUNDPness potential collisions of structure
+;;; slot accessors signalled a condition at macroexpansion time, not
+;;; when the code was actually compiled or loaded.
+(let ((defstruct-form '(defstruct bug-in-0-8-11-8 x)))
+ (defun bug-in-0-8-11-8-x (z) (print "some unrelated thing"))
+ (handler-case (macroexpand defstruct-form)
+ (warning (c)
+ (error "shouldn't warn just from macroexpansion here"))))
+
+;;; bug 318 symptom no 1. (rest not fixed yet)
+(catch :ok
+ (handler-bind ((error (lambda (c)
+ ;; Used to cause stack-exhaustion
+ (unless (typep c 'storage-condition)
+ (throw :ok t)))))
+ (eval '(progn
+ (defstruct foo a)
+ (setf (find-class 'foo) nil)
+ (defstruct foo slot-1)))))
+
+;;; bug 348, evaluation order of slot writer arguments. Fixed by Gabor
+;;; Melis.
+(defstruct bug-348 x)
+
+(assert (eql -1 (let ((i (eval '-2))
+ (x (make-bug-348)))
+ (funcall #'(setf bug-348-x)
+ (incf i)
+ (aref (vector x) (incf i)))
+ (bug-348-x x))))
+
+;;; obsolete instance trapping
+;;;
+;;; FIXME: Both error conditions below should possibly be instances
+;;; of the same class. (Putting this FIXME here, since this is the only
+;;; place where they appear together.)
+
+(with-test (:name :obsolete-defstruct/print-object)
+ (eval '(defstruct born-to-change))
+ (let ((x (make-born-to-change)))
+ (handler-bind ((error 'continue))
+ (eval '(defstruct born-to-change slot)))
+ (assert (eq :error
+ (handler-case
+ (princ-to-string x)
+ (sb-pcl::obsolete-structure ()
+ :error))))))
+
+(with-test (:name :obsolete-defstruct/typep)
+ (eval '(defstruct born-to-change-2))
+ (let ((x (make-born-to-change-2)))
+ (handler-bind ((error 'continue))
+ (eval '(defstruct born-to-change-2 slot)))
+ (assert (eq :error2
+ (handler-case
+ (typep x (find-class 'standard-class))
+ (sb-kernel:layout-invalid ()
+ :error2))))))
+
+;; EQUALP didn't work for structures with float slots (reported by
+;; Vjacheslav Fyodorov).
+(defstruct raw-slot-equalp-bug
+ (b 0s0 :type single-float)
+ c
+ (a 0d0 :type double-float))
+
+(with-test (:name :raw-slot-equalp)
+ (assert (equalp (make-raw-slot-equalp-bug :a 1d0 :b 2s0)
+ (make-raw-slot-equalp-bug :a 1d0 :b 2s0)))
+ (assert (equalp (make-raw-slot-equalp-bug :a 1d0 :b 0s0)
+ (make-raw-slot-equalp-bug :a 1d0 :b -0s0)))
+ (assert (not (equalp (make-raw-slot-equalp-bug :a 1d0 :b 2s0)
+ (make-raw-slot-equalp-bug :a 1d0 :b 3s0))))
+ (assert (not (equalp (make-raw-slot-equalp-bug :a 1d0 :b 2s0)
+ (make-raw-slot-equalp-bug :a 2d0 :b 2s0)))))
+
+;;; Check that all slot types (non-raw and raw) can be initialized with
+;;; constant arguments.
+(defstruct constant-arg-inits
+ (a 42 :type t)
+ (b 1 :type fixnum)
+ (c 2 :type sb-vm:word)
+ (d 3.0 :type single-float)
+ (e 4.0d0 :type double-float)
+ (f #c(5.0 5.0) :type (complex single-float))
+ (g #c(6.0d0 6.0d0) :type (complex double-float)))
+(defun test-constant-arg-inits ()
+ (let ((foo (make-constant-arg-inits)))
+ (declare (dynamic-extent foo))
+ (assert (eql 42 (constant-arg-inits-a foo)))
+ (assert (eql 1 (constant-arg-inits-b foo)))
+ (assert (eql 2 (constant-arg-inits-c foo)))
+ (assert (eql 3.0 (constant-arg-inits-d foo)))
+ (assert (eql 4.0d0 (constant-arg-inits-e foo)))
+ (assert (eql #c(5.0 5.0) (constant-arg-inits-f foo)))
+ (assert (eql #c(6.0d0 6.0d0) (constant-arg-inits-g foo)))))
+(make-constant-arg-inits)
+
+;;; bug reported by John Morrison, 2008-07-22 on sbcl-devel
+(defstruct (raw-slot-struct-with-unknown-init (:constructor make-raw-slot-struct-with-unknown-init ()))
+ (x (#:unknown-function) :type double-float))
+\f
+;;; Some checks for the behavior of incompatibly redefining structure
+;;; classes. We don't actually check that our detection of
+;;; "incompatible" is comprehensive, only that if an incompatible
+;;; definition is processed, we do various things.
+(defmacro with-files ((&rest vars) &body body)
+ "Evaluate BODY with VARS bound to a number of filenames, then
+delete the files at the end."
+ (let* ((paths (loop for var in vars
+ as index upfrom 0
+ collect (make-pathname
+ :case :common
+ :name (format nil
+ "DEFSTRUCT-REDEF-TEST-~D"
+ index)
+ :type "LISP")))
+ (binding-spec (mapcar
+ (lambda (var path) `(,var ,path)) vars paths)))
+ (labels ((frob (n)
+ `((unwind-protect
+ (progn
+ ,@(if (plusp n)
+ (frob (1- n))
+ body))
+ (delete-file ,(elt paths n))))))
+ `(let ,binding-spec
+ ,@(frob (1- (length vars)))))))
+
+(defun noclobber (pathspec &rest forms)
+ "Write FORMS to the file named by PATHSPEC, erroring if
+PATHSPEC already names an existing file."
+ (with-open-file (*standard-output* pathspec :direction :output
+ :if-exists :error)
+ (print '(in-package "CL-USER"))
+ (mapc #'print forms)))
+
+(defun compile-file-assert (file &optional (want-error-p t) (want-warning-p t))
+ "Compile FILE and assert some things about the results."
+ (multiple-value-bind (fasl errors-p warnings-p)
+ (compile-file file)
+ (assert fasl)
+ (assert (eq errors-p want-error-p))
+ (assert (eq warnings-p want-warning-p))
+ fasl))
+
+(defun continue-from-incompatible-defstruct-error (error)
+ "Invoke the CONTINUE restart for an incompatible DEFSTRUCT
+redefinition."
+ ;; FIXME: want distinct error type for incompatible defstruct.
+ (when (search "attempt to redefine" (simple-condition-format-control error))
+ (when (find-restart 'continue)
+ (invoke-restart 'continue))))
+
+(defun recklessly-continue-from-incompatible-defstruct-error (error)
+ "Invoke the RECKLESSLY-CONTINUE restart for an incompatible DEFSTRUCT
+redefinition."
+ ;; FIXME: want distinct error type for incompatible defstruct.
+ (when (search "attempt to redefine" (simple-condition-format-control error))
+ (when (find-restart 'sb-kernel::recklessly-continue)
+ (invoke-restart 'sb-kernel::recklessly-continue))))
+
+(defun assert-is (predicate instance)
+ (assert (funcall predicate instance)))
+
+(defun assert-invalid (predicate instance)
+ (assert (typep (nth-value 1 (ignore-errors (funcall predicate instance)))
+ 'sb-kernel::layout-invalid)))
+
+;; Don't try to understand this macro; just look at its expansion.
+(defmacro with-defstruct-redefinition-test (name
+ (&rest defstruct-form-bindings)
+ (&rest path-form-specs)
+ handler-function
+ &body body)
+ (labels ((make-defstruct-form (&key class-name super-name slots)
+ (let* ((predicate-name
+ (read-from-string (format nil "~A-p" class-name)))
+ (constructor-name
+ (read-from-string (format nil "make-~A" class-name))))
+ `(values
+ '(defstruct (,class-name
+ (:constructor ,constructor-name)
+ ,@(when super-name
+ `((:include ,super-name))))
+ ,@slots)
+ ',constructor-name
+ ',predicate-name)))
+ (frob (bindspecs classno)
+ (if bindspecs
+ `((multiple-value-bind ,(first (first bindspecs))
+ ,(apply #'make-defstruct-form (rest (first bindspecs)))
+ (declare (ignorable ,@(first (first bindspecs))))
+ ,@(frob (rest bindspecs) (1+ classno))))
+ `((with-files ,(mapcar #'first path-form-specs)
+ ,@(mapcar (lambda (path-form) `(noclobber ,@path-form))
+ path-form-specs)
+ (handler-bind
+ ((simple-error ',handler-function))
+ ,@body))))))
+ `(with-test (:name ,name)
+ ,(first (frob defstruct-form-bindings 0)))))
+
+;; When eyeballing these, it's helpful to see when various things are
+;; happening.
+(setq *compile-verbose* t *load-verbose* t)
+\f
+;;; Tests begin.
+;; Base case: recklessly-continue.
+(with-defstruct-redefinition-test :defstruct/recklessly
+ (((defstruct ctor pred) :class-name redef-test-1 :slots (a))
+ ((defstruct*) :class-name redef-test-1 :slots (a b)))
+ ((path1 defstruct)
+ (path2 defstruct*))
+ recklessly-continue-from-incompatible-defstruct-error
+ (load path1)
+ (let ((instance (funcall ctor)))
+ (load path2)
+ (assert-is pred instance)))
+
+;; Base case: continue (i.e., invalidate instances).
+(with-defstruct-redefinition-test :defstruct/continue
+ (((defstruct ctor pred) :class-name redef-test-2 :slots (a))
+ ((defstruct*) :class-name redef-test-2 :slots (a b)))
+ ((path1 defstruct)
+ (path2 defstruct*))
+ continue-from-incompatible-defstruct-error
+ (load path1)
+ (let ((instance (funcall ctor)))
+ (load path2)
+ (assert-invalid pred instance)))
+
+;; Compiling a file with an incompatible defstruct should emit a
+;; warning and an error, but the fasl should be loadable.
+(with-defstruct-redefinition-test :defstruct/compile-file-should-warn
+ (((defstruct) :class-name redef-test-3 :slots (a))
+ ((defstruct*) :class-name redef-test-3 :slots (a b)))
+ ((path1 defstruct)
+ (path2 defstruct*))
+ continue-from-incompatible-defstruct-error
+ (load path1)
+ (load (compile-file-assert path2)))
+
+;; After compiling a file with an incompatible DEFSTRUCT, load the
+;; fasl and ensure that an old instance remains valid.
+(with-defstruct-redefinition-test :defstruct/compile-file-reckless
+ (((defstruct ctor pred) :class-name redef-test-4 :slots (a))
+ ((defstruct*) :class-name redef-test-4 :slots (a b)))
+ ((path1 defstruct)
+ (path2 defstruct*))
+ recklessly-continue-from-incompatible-defstruct-error
+ (load path1)
+ (let ((instance (funcall ctor)))
+ (load (compile-file-assert path2))
+ (assert-is pred instance)))
+
+;; After compiling a file with an incompatible DEFSTRUCT, load the
+;; fasl and ensure that an old instance has become invalid.
+(with-defstruct-redefinition-test :defstruct/compile-file-continue
+ (((defstruct ctor pred) :class-name redef-test-5 :slots (a))
+ ((defstruct*) :class-name redef-test-5 :slots (a b)))
+ ((path1 defstruct)
+ (path2 defstruct*))
+ continue-from-incompatible-defstruct-error
+ (load path1)
+ (let ((instance (funcall ctor)))
+ (load (compile-file-assert path2))
+ (assert-invalid pred instance)))