Jochen,
sorry for the example (I have written it in hurry); it should have looked
rather like this:
===
266 /tmp> <q.groovy
class q {
static main(args) {
def mc=new OCSNMC(org.codehaus.groovy.runtime.NullObject)
mc.initialize()
org.codehaus.groovy.runtime.NullObject.metaClass=mc
println "null.foo() is OK: ${null.foo()==null}"
println "null+null is OK: ${null+null==null}"
println "null[null] is OK (and no 3 needed, ha!): ${null[null]==null}"
println "null.foo though we won't see: ${null.foo==null}"
}
}
class OCSNMC extends DelegatingMetaClass {
OCSNMC(Class clazz) { super(clazz) }
Object invokeMethod(Object object, String methodName, Object[] arguments) {
null }
Object getProperty(Class sender, Object receiver, String property, boolean
isCallToSuper, boolean fromInsideClass) {
println "getProperty called!" // actually never happens, dunno why
null
}
}
267 /tmp> /usr/local/groovy-2.4.15/bin/groovy q
null.foo() is OK: true
null+null is OK: true
null[null] is OK (and no 3 needed, ha!): true
Caught: java.lang.NullPointerException: Cannot get property 'foo' on null object
java.lang.NullPointerException: Cannot get property 'foo' on null object
at q.main(q.groovy:10)
at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at
java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at
java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
268 /tmp>
===
Thanks and all the best,
OC
> On 14 Aug 2018, at 7:10 PM, ocs@ocs <[email protected]> wrote:
>
> Jochen,
>
>> On 14 Aug 2018, at 6:34 PM, Jochen Theodorou <[email protected]
>> <mailto:[email protected]>> wrote:
>> [...]
>>> For the moment, the best solution — far as I have been able to ascertain —
>>> consists of [*]
>>> (a) at launch, setting own DelegatingMetaClass subclass for a
>>> Null.metaclass; it essentially would return null from invokeMethod, but
>>> still needs to process special cases (like e.g., null.is <http://null.is/>
>>> <http://null.is <http://null.is/>>(foo)) explicitly;
>>
>> so for general Groovy... how are we supposed to recognized in a transform if
>> "is" has been replaced?
>
> It is definitely highly arguable, but in the very specific case of
> “null?.is(null)“, which currently returns an absurd (though technically
> understandable) false value, I would rather suggest to break the backward
> compatibility.
>
> I can't really see anyone whose code would actually use "?.is", and moreover,
> rely on that “null?.is(null)“ is valid and returns null (instead of true). Is
> there such a person in the sweet world?
>
> Anyway, the point is, I'd say, rather at the unimportant side, for I believe
> is() is being won't be widely used anyway, given we got === and !=== now.
>
> Thus, even if we decide that breaking backward compatibility even in this
> slightly insane case is a big no-no, we just can keep the current behaviour
> of “null?.is(null)==null“ unchanged. There's no real harm; new code would
> simply use === instead, and old one would work as before.
>
>>> (b) since the above for some godforsaken reason does not work for property
>>> access at all, still implement an ASTT with a
>>> ClassCodeExpressionTransformer to set expression.safe=true for
>>> get/setProperty, guarding explicitly against the known problematic cases
>>> (e.g., super.getProperty).
>>
>> are you saying x?.foo will NPE if x is null? Or that x?.getFoo() will NPE in
>> that case? Not sure how to read your comment.
>
> Provided only (a) “the Null.metaclass; returning null from invokeMethod” is
> used and no ASTT, then yes, it would NPE. Easiest thing on earth to try:
>
> ===
> 262 /tmp> <q.groovy
> class q {
> static main(args) {
> // ExpandoMetaClass.enableGlobally() // I thought this is needed;
> seems not (though my application use it anyway)
> def mc=new OCSNMC(org.codehaus.groovy.runtime.NullObject)
> mc.initialize()
> org.codehaus.groovy.runtime.NullObject.metaClass=mc
>
> println "null.foo() is OK: ${null.foo()==null}"
> println "null.foo we won't see: ${null.foo==null}"
> }
> }
>
> class OCSNMC extends DelegatingMetaClass {
> OCSNMC(Class clazz){
> super(clazz)
> }
> Object invokeMethod(Object object, String methodName, Object[] arguments)
> {
> null
> }
> }
> 263 /tmp> /usr/local/groovy-2.4.15/bin/groovy q
> null.foo() is OK: true
> Caught: java.lang.NullPointerException: Cannot get property 'foo' on null
> object
> java.lang.NullPointerException: Cannot get property 'foo' on null object
> at q.main(q.groovy:9)
> at
> java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
> at
> java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
> at
> java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
> at
> java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
> at
> java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
> at
> java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
> 264 /tmp>
> ===
>
>>> For all I know, this probably would not work properly with @CompileStatic
>>> (which I do not use at all myself, but others do frequently).
>>
>> the result type could be a problem... Worth to check.
>
> Definitely :)
>
>>> Trust me, been there, done that. I am pretty darn sure it would be
>>> /infinitely/ easier and, what's important, more reliable in the core with
>>> an explicit compiler support.
>>
>> How about making a small github project to dump the current state there?
>
> Not that easy, for my code is mixed up with other ASTT and runtime stuff; but
> I'll try to make some simple proof-of-concept ASAP and send here.
>
> Thanks and all the best,
> OC
>