[ 
https://issues.apache.org/jira/browse/GROOVY-10099?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=17349833#comment-17349833
 ] 

Rachel Greenham commented on GROOVY-10099:
------------------------------------------

That fix won't work. It breaks too much. I'm sure you're as unsurprised as I 
am. I had fun though. :D

Fundamentally you can't fix it at runtime because you can't tell if a single 
null value is a null object reference or a null array reference. And there's 
much that depends on the implicit assumption that it's an array reference. For 
instance, if you change it, you can't set or initialise a POGO's array-typed 
field to null. Which doesn't break immediately, it breaks later, when something 
tries to use it.

If this can be fixed anywhere, it's in the compiler. Maybe the AstBuilder? It 
has more information. After all, it works as expected in {{@CompileStatic}}.

Even in {{@CompileDynamic}} mode, it presumably has (or could have) a better 
idea of what a given _expression type_ is; at least quite a lot of the time: if 
it's a call to a real method, or a typed object reference, or a cast or 
type-coercion in the expression itself. It would be able to tell if the 
expression's _type_ is an array type or not. And if it's not an array type, and 
the parameter it fits to is a varargs parameter, whatever value it evaluates to 
at runtime (including null) should be wrapped in an array of one, and it can 
write that array-wrapping operation into the compiled code. Like it does in 
{{@CompileStatic}}, like javac does... As with {{@TypeChecked}} mode, it would 
still be writing an invokeMethod call, but it would wrap that parameter in an 
array. Then all the runtime invoke code sees is that array and it just passes 
it through.

And if it _hasn't_ got a definitive idea of the expression type, because it's 
an untyped variable, or an expression that _must_ be resolved to a dynamic call 
(ie: something that {{@CompileStatic}} would balk at), it would just pass it in 
as it is, the same behaviour as now, so it breaks nothing. And if it doesn't 
behave how the coder expects, they'd be able to resolve it by disambiguating 
the type of that expression in their own code (eg: with a cast, a type 
coercion, or declaring a variable or method return type properly).

So it wouldn't solve it everywhere, 100%, as it couldn't solve it for dynamic 
expressions of unknowable type at compiletime. But solving it for known 
expression types would reduce the footprint of the problem a _lot_, I expect, 
and put the rest in the hands of the coder to eg: put a cast in if necessary.

This would have solved my original problem, as it was a situation where the 
expression type was known, or at least knowable. But it was just one line in a 
script that didn't otherwise need to be {{@CompileStatic}}.

I'm not averse to trying that approach, but I don't know where to start...

> A single null argument to a varargs parameter is received as null
> -----------------------------------------------------------------
>
>                 Key: GROOVY-10099
>                 URL: https://issues.apache.org/jira/browse/GROOVY-10099
>             Project: Groovy
>          Issue Type: Bug
>         Environment: Observed on Groovy 3.0.8 on macOS Big Sur (Intel), but I 
> don't think that's relevant; it'll be everywhere.
>            Reporter: Rachel Greenham
>            Priority: Major
>             Fix For: 4.x
>
>         Attachments: VarArgsTest.groovy, VarArgsTest.jsh
>
>
> (NB: I would set the priority to P2 default to be triaged, but I seem not to 
> have that option, so I left it at the default I was presented with.)
> When calling a method with a varargs parameter with a single value, that 
> value is wrapped into an array of length 1. This is the behaviour in Java, 
> and is the expected behaviour, and _most_ of the time is the behaviour in 
> Groovy too.
> But when that single value is null, Groovy will instead just pass the null 
> into the method. Java will not do this: You'll get an array with a single 
> null in it. Because Groovy's behaviour is unexpected, especially when 
> interfacing with Java code, NullPointerExceptions can often ensue.
> Adding to the inconsistencies, if the Groovy code calling the method is in a 
> {{@CompileStatic}} context, it behaves just like Java, and the method 
> (whether or not _it_ is statically compiled or a dynamic Groovy method) 
> receives an array with a null in it.
> So the behaviour in Groovy is inconsistent, both with itself in a 
> {{@CompileStatic}} situation, and with Java, and is requiring workarounds in 
> Java code to handle this normally-impossible eventuality. (Even if no varargs 
> parameter is given you get an empty array, as in fact you do in Groovy too.)
> This may be an "early instalment weirdness": There's an ancient ticket, from 
> not long after varargs were introduced into Java, which appears to have 
> argued - successfully at the time - that the normal behaviour is a bug: 
> https://issues.apache.org/jira/browse/GROOVY-1026 
> Further adding to the confusion may be that Groovy usually elides the 
> difference between an {{Object[]}} parameter and an {{Object...}} parameter: 
> They both behave the same.
> The offending code appears to be in 
> org.codehaus.groovy.reflection.ParameterTypes.java in method fitToVars, lines 
> 200-215 in master at the time of writing, which even includes a comment that 
> "if the last argument is null, then we don't have to do anything", with which 
> I respectfully disagree. :) That behaviour should be to return an array with 
> a single null in it (Handily, MetaClassHelper.ARRAY_WITH_NULL saves having to 
> make a new one.)
> In principle it's an easy fix (although I've left tagging to others as this 
> is my first issue here), but there'd be an obvious nervousness about changing 
> behaviour like this when there might be a lot of old code out there depending 
> on it behaving the way it does now. OTOH the way it behaves now is breaking 
> the expectations of those of us coming to Groovy from a lifetime of Java...
> Attachments:
> VarArgsTest.groovy - a script saved from, and runnable in, groovyConsole, 
> demonstrating the behaviour. The behaviour is the same regardless of whether 
> the console is launched with the -indy option. (The issue was initially 
> observed in indy.) The dynamic portion of the test, when run, ends in a 
> NullPointerException as Arrays.asList is not expecting a null varargs 
> parameter. Output seen (-indy or  not):
>  
> {code:java}
> name: the static name 1
> params is null? false
> params length is 1
> [blah]
> name: the static name 2
> params is null? false
> params length is 2
> [blah, blue]
> name: the static name 3 with blah=null
> params is null? false
> params length is 1
> [null]
> Arrays.asList(blah)? [null]
> name: the dynamic name 1
> params is null? false
> params length is 1
> [blah]
> name: the dynamic name 2
> params is null? false
> params length is 2
> [blah, blue]
> name: the dynamic name 3 with blah=null
> params is null? true
> Exception thrown
> java.lang.NullPointerException
> ...{code}
> (etc. stack trace not shown for formatting reasons.)
> VarArgsTest.jsh - a jshell script demonstrating Java's behaviour, very 
> similar to the groovy test, but omitting the dynamic portion of the test for 
> obvious reasons. (The statements in the Groovy script ending in semicolons 
> are left that way precisely to mark that they're identical to the Java test.) 
> Runnable with
>  
> {code:java}
> jshell PRINTING VarArgsTest.jsh
> {code}
> Output seen:
> {code:java}
> name: the static name 1
> params is null? false
> params length is 1
> [blah]
> name: the static name 2
> params is null? false
> params length is 2
> [blah, blue]
> name: the static name 3 with blah=null
> params is null? false
> params length is 1
> [null]
> Arrays.asList(blah)? [null]
> {code}



--
This message was sent by Atlassian Jira
(v8.3.4#803005)

Reply via email to