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 <o...@ocs.cz> wrote: > > Jochen, > >> On 14 Aug 2018, at 6:34 PM, Jochen Theodorou <blackd...@gmx.org >> <mailto:blackd...@gmx.org>> 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 >