From: Nick Levine
Subject: defmethod and implicit type declarations
Date: 
Message-ID: <8732fc48.0309010821.21245dd0@posting.google.com>
Consider:

  (defmethod foo ((baz standard-object))
    (loop while baz do 
          (etypecase baz
            (standard-object (setf baz (bar baz))))))

LispWorks issues a compiler warning ("Eliminating a test of a variable
with a declared type : BAZ [type STANDARD-OBJECT]"), and we can see
why that's happening - baz has been impicitly declared by the
implementation to be of type standard-object.

Now, it's clear that baz must be of this type on entry to the method
(otherwise the method would not have been applicable). But does the
spec say anywhere whether it has to remain of that type? We can't find
anything either way.

- nick

From: Steven M. Haflich
Subject: Re: defmethod and implicit type declarations
Date: 
Message-ID: <MfN4b.276$VM3.33754243@newssvr13.news.prodigy.com>
Nick Levine wrote:

> Consider:
> 
>   (defmethod foo ((baz standard-object))
>     (loop while baz do 
>           (etypecase baz
>             (standard-object (setf baz (bar baz))))))
> 
> LispWorks issues a compiler warning ("Eliminating a test of a variable
> with a declared type : BAZ [type STANDARD-OBJECT]"), and we can see
> why that's happening - baz has been impicitly declared by the
> implementation to be of type standard-object.
> 
> Now, it's clear that baz must be of this type on entry to the method
> (otherwise the method would not have been applicable). But does the
> spec say anywhere whether it has to remain of that type? We can't find
> anything either way.

In the general case, there are three ways the value of baz might violate
the implicit declaration that it is of the specialized type declared by
the specializer.  To see them all, let's rewrite your example just
slightly:

(defclass frob (standard-object) ())

(defmethod foo ((baz frob))
   (loop initially (mangle)
      while baz do
         (etypecase baz
            (frob (setf baz (bar baz)))))))

(1) If baz were a special variable, then it might be set somewhere else.
But the compiler knows about specialness at compile time, and presumably
baz is a lexical variable.

(2) If the object received by baz were subject to a change-class (perhaps
in the call to mangle) it might not still be a frob.  But there is no way
for it to no longer be a standard-object , which is why your original
example is not a very general situation.

(3) It might be setf within the method, after which it could be anything
at all.

The ANS beats around the bush in the description of change-class, saying
there may be difficulties in slot access after a change-class that violates
the implicit declaration.  But it says nothing about the actual type of
the received parameter.

I remember long ago in Allegro, the type declaration was assumed to apply
to the parameter variable.  Then I think we changed that because there was
conforming code (perhaps like yours) which violated that implicit
declaration.  Instead, the compiler tracks whether the variable (assuming
it is lexical) is ever set.  If not, we assume enough wiggle room by the
bush-beating on the change class page, and assume the type declaration is
valid.

This was a long time ago and things may have changed in both the
implementation and in my memory of the implementation...  Both are
mutable.
From: Nick Levine
Subject: Re: defmethod and implicit type declarations
Date: 
Message-ID: <8732fc48.0309012306.5be7d1e3@posting.google.com>
Hi Steve,

> (defclass frob (standard-object) ())
> 
> (defmethod foo ((baz frob))
>    (loop initially (mangle)
>       while baz do
>          (etypecase baz
>             (frob (setf baz (bar baz)))))))
> 
> (1) If baz were a special variable...
> (2) If the object received by baz were subject to a change-class...

Ha. Two interesting extensions to the problem I had in mind.

> (3) It might be setf within the method, after which it could be anything
> at all.
> 
> The ANS beats around the bush in the description of change-class, saying
> there may be difficulties in slot access after a change-class that violates
> the implicit declaration.  

I couldn't find that in the spec - where is that?

> I remember long ago in Allegro, the type declaration was assumed to apply
> to the parameter variable.

Do you mean that the type would have been assumed to apply to baz
through the entire method body above?

>                        Then I think we changed that because there was
> conforming code (perhaps like yours) which violated that implicit
> declaration.  Instead, the compiler tracks whether the variable (assuming
> it is lexical) is ever set. 

Makes sense. But brings me to my main question: where if anywhere in
the spec does it describe this "implicit declaration"? I hunted but I
didn't find.

Thanks,

- nick
From: Kalle Olavi Niemitalo
Subject: Re: defmethod and implicit type declarations
Date: 
Message-ID: <87znhnm0xg.fsf@Astalo.kon.iki.fi>
···@ravenbrook.com (Nick Levine) writes:

> I couldn't find that in the spec - where is that?

It's on the CHANGE-CLASS page, under the "Notes" heading.
"This implies that a programmer must not use change-class inside
a method if any methods for that generic function access any
slots, or the results are undefined."

That seems far too strict though.  In particular, I don't see
what could go wrong in *first* accessing slots of an object and
*then* calling CHANGE-CLASS in the same method.
From: james anderson
Subject: Re: defmethod and implicit type declarations
Date: 
Message-ID: <3F54FB8C.F9475F8E@setf.de>
that could violate the constraints inplicit in the computation of the
applicable methods which contribute to the effective method.

i would wonder if it's too lax.

Kalle Olavi Niemitalo wrote:
> 
> ···@ravenbrook.com (Nick Levine) writes:
> 
> > I couldn't find that in the spec - where is that?
> 
> It's on the CHANGE-CLASS page, under the "Notes" heading.
> "This implies that a programmer must not use change-class inside
> a method if any methods for that generic function access any
> slots, or the results are undefined."
> 
> That seems far too strict though.  In particular, I don't see
> what could go wrong in *first* accessing slots of an object and
> *then* calling CHANGE-CLASS in the same method.

...
From: Nick Levine
Subject: Re: defmethod and implicit type declarations
Date: 
Message-ID: <8732fc48.0309030048.58d0894c@posting.google.com>
> that could violate the constraints inplicit in the computation of the
> applicable methods which contribute to the effective method.

[...]

> > It's on the CHANGE-CLASS page, under the "Notes" heading.
> > "This implies that a programmer must not use change-class inside
> > a method if any methods for that generic function access any
> > slots, or the results are undefined."

Thanks to Kalle for the pointer.

> > That seems far too strict though.  In particular, I don't see
> > what could go wrong in *first* accessing slots of an object and
> > *then* calling CHANGE-CLASS in the same method.

So, does the spec say anywhere that the constraints inplicit in the
computation of the applicable methods have to stay satisfied? (Let's
suppose for the sake of brevity that we're not going to
call-next-method.  For that matter, neither my original example nor
Steve's rewrite involved any slot accesses.)

I can see that there's a warm feeling that once we're inside a method,
we all know the types of the specialized arguments. But warm feelings
cut no mustard - it looks like there is no explicit rule.

-nick
From: james anderson
Subject: Re: defmethod and implicit type declarations
Date: 
Message-ID: <3F55C0D5.2FFF0515@setf.de>
the clause cited below from the change-class definition discusses a "semantic
difficulty" which was prefaced "Second,". there was a "first".

presuming that the given method combination did not entail implicit next
method calls, and that the effective method included no :before, :after,
:around methods - or their equivalent for the given method combination, and
that the metaclass of the new class was the same?

ok.

Nick Levine wrote:
> 
> > that could violate the constraints inplicit in the computation of the
> > applicable methods which contribute to the effective method.
> 
> [...]
> 
> > > It's on the CHANGE-CLASS page, under the "Notes" heading.
> > > "This implies that a programmer must not use change-class inside
> > > a method if any methods for that generic function access any
> > > slots, or the results are undefined."
> 
> Thanks to Kalle for the pointer.
> 
> > > That seems far too strict though.  In particular, I don't see
> > > what could go wrong in *first* accessing slots of an object and
> > > *then* calling CHANGE-CLASS in the same method.
> 
> So, does the spec say anywhere that the constraints inplicit in the
> computation of the applicable methods have to stay satisfied?

an alternative approach is to be concerned about whether the spec defines the
effect of changing the class of a specialized argument in the middle of an
effective method.

>   (Let's
> suppose for the sake of brevity that we're not going to
> call-next-method.  For that matter, neither my original example nor
> Steve's rewrite involved any slot accesses.)

in general, the only defined control flow would be through return-from.

> 
> I can see that there's a warm feeling that once we're inside a method,
> we all know the types of the specialized arguments. But warm feelings
> cut no mustard - it looks like there is no explicit rule.
> 
> -nick
From: james anderson
Subject: Re: defmethod and implicit type declarations
Date: 
Message-ID: <3F53CDEE.85243BC8@setf.de>
?
is a program which engages in (2)-type behaviour conformant?

"Steven M. Haflich" wrote:
> 
> ...
> 
> (defclass frob (standard-object) ())
> 
> (defmethod foo ((baz frob))
>    (loop initially (mangle)
>       while baz do
>          (etypecase baz
>             (frob (setf baz (bar baz)))))))
> 
> ...
> 
> (2) If the object received by baz were subject to a change-class (perhaps
> in the call to mangle) it might not still be a frob.  But there is no way
> for it to no longer be a standard-object , which is why your original
> example is not a very general situation.
>
> ...

does not the restriction implicit in the rule asserted under the exceptional
conditions for call-next-method imply that actions such as the
hyper-class-change which follows below are not portable? by which logic, if
the compiler knows a variable is not special and not assigned, the "lispworks"
conclusion is defensible.

[3] CL-USER(40): (defClass alpha () ())
#<STANDARD-CLASS ALPHA>
[3] CL-USER(41): (defClass beta (alpha) ())
#<STANDARD-CLASS BETA>
[3] CL-USER(42): (defClass hyper (beta) ((provide-p :initform nil :initarg :provide)))
#<STANDARD-CLASS HYPER>
[3] CL-USER(46): (defGeneric test-next-method (arg op)
   (:method-combination standard)
   (:method ((arg t) (op t)) (cons (type-of arg) t))
   (:method ((arg alpha) (op t)) (cons 'alpha (call-next-method)))
   (:method ((arg beta) (op t)) (cons 'beta (call-next-method)))
   (:method ((arg hyper) (op t)) (cons 'hyper (call-next-method)))
   (:method ((arg hyper) (op function))
            (let ((provide-p (slot-value arg 'provide-p)))
              (cons (funcall op arg)
                    (cons provide-p
                          (if provide-p
                            (call-next-method arg op)
                            (call-next-method)))))))
#<STANDARD-GENERIC-FUNCTION TEST-NEXT-METHOD>
[3] CL-USER(47): (test-next-method (make-instance 'alpha) nil)
(ALPHA ALPHA . T)
[3] CL-USER(48): (test-next-method (make-instance 'beta) nil)
(BETA ALPHA BETA . T)
[3] CL-USER(49): (test-next-method (make-instance 'hyper) nil)
(HYPER BETA ALPHA HYPER . T)
[3] CL-USER(50): (test-next-method (make-instance 'hyper :provide nil)
                   #'(lambda (x) (change-class x (find-class 'alpha))))
(#<ALPHA @ #x3060234a> NIL HYPER BETA ALPHA ALPHA . T)
[3] CL-USER(51): (test-next-method (make-instance 'hyper :provide t)
                   #'(lambda (x) (change-class x (find-class 'alpha))))
(#<ALPHA @ #x30606f52> T HYPER BETA ALPHA ALPHA . T)
[3] CL-USER(52):