Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Change DEFINE-CONDITION: signals a TYPE-ERROR if trying to supertype a non-CONDITION #11

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion portable-condition-system.asd
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@
:serial t
:pathname "src"
:depends-on (#:alexandria
#:split-sequence)
#:split-sequence
#:closer-mop)
:components ((:file "package")
(:file "conditions")
(:file "condition-hierarchy")
Expand Down Expand Up @@ -37,6 +38,7 @@
(:file "debugger")
(:file "ansi-test-data")
(:file "ansi-test-support")
(:file "more-tests")
(:module "ansi-test"
:components
((:file "condition")
Expand Down
14 changes: 14 additions & 0 deletions src/condition-hierarchy.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -139,3 +139,17 @@ function ABORT failed to transfer control outside of the function.")
(:documentation "A condition type signaled when a case assertion
(such as ECASE, ETYPECASE, CCASE, or CTYPECASE) fails to match its keyform.")
(:report report-case-failure))

(defun report-invalid-superclass (condition stream)
(format stream "~S cannot superclass ~S:~%~S"
(invalid-superclass-class condition)
(invalid-superclass-superclass condition)
(invalid-superclass-reason condition)))

(define-condition invalid-superclass (condition)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's an obvious ERROR rather than a CONDITION.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

((class :reader invalid-superclass-class :initarg :class)
(superclass :reader invalid-superclass-superclass :initarg :superclass)
(reason :reader invalid-superclass-reason :initarg :reason))
(:documentation "A condition type signaled when a class tries to superclass an
invalid superclass; the violation being described by REASON.")
(:report report-invalid-superclass))
37 changes: 32 additions & 5 deletions src/conditions.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,44 @@ PRINT-OBJECT method defined on the class named by NAME."
`(let ((,method (find-method #'print-object '() '(,name t) nil)))
(when ,method (remove-method #'print-object ,method)))))

(defvar *in-define-condition-p* nil
"This will be dynamically binded to indicate an entry into DEFINE-CONDITION,
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/binded/bound/

to distinguish its DEFCLASS from an external DEFCLASS.")

(defmethod ensure-class-using-class :before (class name &rest args &key direct-superclasses &allow-other-keys)
"In DEFINE-CONDITION: Signals a TYPE-ERROR at the first direct superclass unconforming
with DEFINE-CONDITION, that is, the first encountered superclass in DIRECT-SUPERCLASSES
that is not a subtype of CONDITION.
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...subclass of CONDITION

In DEFCLASSes external to DEFINE-CONDITION: Signals an INVALID-SUPERCLASS at the first
encountered superclass that is subtype of CONDITION."
(declare (ignore args))
(if *in-define-condition-p*
(loop :for superclass :in direct-superclasses
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe switch out the LOOPs for DOLISTs? Just a style thing.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure

:do (when (not (subtypep superclass 'condition))
(error 'type-error
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am kind of interested in the topic of dogfooding ourselves with this one. We will be using PCS:ERROR to signal PCS:INVALID-SUPERCLASS during class definition, which will mostly only work with Slime if PCS integration is loaded. Nice.

:datum superclass
:expected-type 'condition)))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error is wrong - such a TYPE-ERROR would mean that the superclass is not of type CONDITION, whereas here the error should say that the superclass is not a subtype of CONDITION. Hence, I think that you should go for signaling INVALID-SUPERCLASS in here too.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️

(loop :for superclass :in direct-superclasses
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ditto.

:do (when (subtypep superclass 'condition)
(error 'invalid-superclass
:class name
:superclass superclass
:reason (format nil "Only ~S should~% define a subtype of ~S"
'define-condition 'condition))))))

(defun expand-define-condition (name supertypes direct-slots options)
"Defines a new condition type via DEFCLASS, handling the :REPORT options via
defining a PRINT-object method on the newly created class."
(let* ((report-option (find :report options :key #'car))
(other-options (remove report-option options))
(supertypes (or supertypes '(condition))))
`(progn (defclass ,name ,supertypes ,direct-slots ,@other-options)
,@(if report-option
`(,(expand-define-condition-report-method name report-option))
`(,(expand-define-condition-remove-report-method name)))
',name)))
`(progn
(let ((*in-define-condition-p* t))
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This solution creates some new problems. The main issue of having a LET here is that the DEFCLASS is no longer toplevel which means that condition classes are not going to be picked up by the compiler, which can result in undefined type warnings.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure its a good idea, but an alternative I see would be to add a dummy class to supertypes here in EXPAND-DEFINE-CONDITION . Do you have any suggestion?

(defclass ,name ,supertypes ,direct-slots ,@other-options))
,@(if report-option
`(,(expand-define-condition-report-method name report-option))
`(,(expand-define-condition-remove-report-method name)))
',name)))

(defmacro define-condition (name (&rest supertypes) direct-slots &rest options)
"Defines or redefines a condition type."
Expand Down
1 change: 1 addition & 0 deletions src/package.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
(:nicknames #:pcs)
(:import-from #:split-sequence #:split-sequence)
(:import-from #:alexandria #:parse-body)
(:import-from #:closer-mop #:ensure-class-using-class)
(:shadow ,@symbols)
(:export ,@symbols))
(uiop:define-package #:common-lisp+portable-condition-system
Expand Down
35 changes: 35 additions & 0 deletions t/ansi-test/define-condition.lisp
Original file line number Diff line number Diff line change
Expand Up @@ -579,6 +579,41 @@
(notnot-mv (typep #'condition-27/s1 'generic-function))
t)

;;; Test non-CONDITION supertypes
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you move this whole section to more-tests.lisp? I'd rather not modify the ANSI-TEST files themselves.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✔️


(defclass not-a-condition-1 nil nil)
(defclass not-a-condition-2 nil nil)

(deftest condition-with-non-condition-supertype-1.1
(signals-type-error not-a-condition 'not-a-condition-1
(define-condition condition-with-non-condition-supertype-1a (not-a-condition-1)
()
(:report "condition-with-non-condition-supertype-1a")))
t)

(define-condition-with-tests condition-28a nil nil)
(define-condition-with-tests condition-28b nil nil)

(deftest condition-with-non-condition-supertype-1.2
(signals-type-error not-a-condition 'not-a-condition-1
(define-condition condition-with-non-condition-supertype-1b (condition-28a
not-a-condition-1
condition-28b)
()
(:report "condition-with-non-condition-sypertype-1b")))
t)

(deftest condition-with-non-condition-supertype-1.3
(signals-type-error not-a-condition 'not-a-condition-2
(define-condition condition-with-non-condition-supertype-1c (condition-28b
not-a-condition-2
not-a-condition-1)
()
(:report "condition-with-non-condition-supertype-1c")))
t)

;;;

;;; Documentation

;;; Pitman says this should have been in the spec, but it isn't really
Expand Down
26 changes: 26 additions & 0 deletions t/more-tests.lisp
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
;;;; t/more-tests.lisp

(in-package #:portable-condition-system/test)

;;; Test superclassing a CONDITION in DEFCLASS

(defclass not-a-condition-0 nil nil)

(define-condition-with-tests condition-in-super-1a nil nil)
(define-condition-with-tests condition-in-super-1b nil nil)

(deftest condition-in-defclass-superclass-1.1
(signals-error (defclass condition-in-defclass-superclass-1a (condition-in-super-1a)
())
portable-condition-system::invalid-superclass)
t)

(deftest condition-in-defclass-superclass-1.2
(signals-error (defclass condition-in-defclass-superclass-1b (not-a-condition-0
condition-in-super-1b
condition-in-super-1a)
())
portable-condition-system::invalid-superclass)
t)

;;;