I am suggesting that when a closure is encountered, it is treated like an 
optional branch of the code.  So assignments within alter the inferred type the 
same way an assignment within an if with no else would.  I am not looking to 
examine the code flow and distinguish between closure executed immediately and 
closure that is executed asynchronously or later or not at all.  I have this 
change in hand, but did not want to alter the "second pass" checking without 
discussing it first.

If you are talking about using a union type, I don't think that is the safe way 
to go.  Static compilation is enforcing a typecast to the inferred type and 
this is a runtime error for 9344.

class A {}
class B extends A{ def b() {}}
class C extends A{}

@CompileStatic
void test() {
  def x = new B()
  ({ x = new C() })()
  def z = x // new behavior: typeof(x) and typeof(z) is A
  z.b() // new behavior: STC error because "b()" is not a member of A; user 
would need to use block instead of closure or add a typecast or type checking 
extension
}

-----Original Message-----
From: Jochen Theodorou <blackd...@gmx.org> 
Sent: Thursday, June 4, 2020 11:35 AM
To: dev@groovy.apache.org
Subject: Re: STC: Closure shared variable assignment handling

On 04.06.20 17:07, Milles, Eric (TR Tech, Content & Ops) wrote:
> There are a couple open bugs related to STC handling of closure shared 
> variables.  When a shared variable is assigned a value within the 
> closure, the assigned type is not included in the inferred type 
> outside the closure/lambda and this leads to some problems.
>
> If this behavior is changed to save the LUB(A,B) as the inferred type 
> of "x", as is suggested in 9516 and seems required by 9344, the second 
> pass checks for method calls will be replaced by no matching method errors.
> That is "Cannot find matching method java.lang.Object#something().
> Please check if the declared type is correct and if the method exists."
> will replace "A closure shared variable [x] has been assigned with 
> various types and the method [charAt(int)] does not exist in the 
> lowest upper bound...".* Is this an acceptable change in STC 
> behavior?*
>
> @groovy.transform.CompileStatic
>
> void test() {
>
>    def x = new A();
>
>    { -> x = new B() } // may also be called immediately
>
>    x.something() // current STC infers typeof(x) == A
>
> }

as this is currently failing you are suggesting to let it pass and use the type 
A?

> I think it was done this way because you could define the closure but 
> not call it (like above).  Or call it asynchronously:
>
> @groovy.transform.CompileStatic
>
> void test() {
>
>    def x = new A();
>
>    Thread.start { -> x = new B() }
>
>    x.something() // current STC infers typeof(x) == A
>
> }

would you not agree, just by looking at the realized flow here, that x should 
not be A?

> https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fissu
> es.apache.org%2Fjira%2Fbrowse%2FGROOVY-9516&amp;data=02%7C01%7Ceric.mi
> lles%40thomsonreuters.com%7C7b12f318629043d33b9d08d808a54e51%7C62ccb86
> 46a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637268853358509821&amp;sdata=zoylX
> L4c9Iic%2FHhoVXxE9iXSV2kp4BBQW2m8wEUYeeM%3D&amp;reserved=0

this is

> class A {}
> class B extends A{ def b() {}}
> class C extends A{}
>
> @CompileStatic
> static foo() {
>   def x = new B()
>   ({ x = new C() })()
>   def z = x
>   z.b()
> }

ah.. now I get the problem. but to say z is of type C is also wrong.
What we really need here is not really the LUB in the sense that
LUB(B,C) = A, iff B extends A && C extends A && B and C are siblings.
What we would really need is a type A plus all methods defined on B and C. I 
vaguely remember that our LUB calculation does things like that..
but maybe I remember wrong. If I go by
https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fdocs.groovy-lang.org%2Flatest%2Fhtml%2Fdocumentation%2Fcore-semantics.html%23section-lub&amp;data=02%7C01%7Ceric.milles%40thomsonreuters.com%7C7b12f318629043d33b9d08d808a54e51%7C62ccb8646a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637268853358519815&amp;sdata=NgKVDLj5YZdz43BRn9Du2%2F5yNxoj78BWVWRzAEXSdlk%3D&amp;reserved=0
then I am wrong. Of course I am well aware, that such a type is difficult to 
realize. And of course the question is how to do the actual method call in 
bytecode. If I cannot cast z to B or B, then I cannot create the required 
invokevirtual. This means this must use an invokedynamic, but a different one 
than we normally use.

What I am trying to say is that we can handle this case as I imagine it, but 
the required implementation work is not small.

> https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fissu
> es.apache.org%2Fjira%2Fbrowse%2FGROOVY-9344&amp;data=02%7C01%7Ceric.mi
> lles%40thomsonreuters.com%7C7b12f318629043d33b9d08d808a54e51%7C62ccb86
> 46a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637268853358519815&amp;sdata=qHXA1
> 4ljlSoFfGd3Y6FskDqkUkqF4fnOt1T7BTwbnQ8%3D&amp;reserved=0

This is

> class A {}
> class B {}
>
> @groovy.transform.CompileStatic
> def cs() {
>     def var
>     var = new A()
>     def c = {
>         var = new B() // Cannot cast object 'B@4e234c52' with class 'B' to 
> class 'A'
>     }
>     c()
>     var.toString()
> }
>
> assert cs() != null

which looks to me like a different problem, var is declared with "def", so 
assigning a B should be valid. Only if the static type does no comply with the 
assigned type it should cause an error. There should be also no cast inserted.

> https://nam02.safelinks.protection.outlook.com/?url=https%3A%2F%2Fissu
> es.apache.org%2Fjira%2Fbrowse%2FGROOVY-5874&amp;data=02%7C01%7Ceric.mi
> lles%40thomsonreuters.com%7C7b12f318629043d33b9d08d808a54e51%7C62ccb86
> 46a1a4b5d8e1c397dec1a8258%7C0%7C0%7C637268853358519815&amp;sdata=oLyIU
> IbjMuxQCbLXT5RKs0vGxg%2BRH17syiM4dHyWlgg%3D&amp;reserved=0

This is

> @CompileStatic
>     static main(args)
>     {
>         def list = [1, 2, 3]
>
>         def sum = 0
>         list.each { int i -> sum += i }
>         println(sum)
>
>         sum = list.inject(0, { int x, int y -> x + y })
>         println(sum)
>     }

what I would imagine here is that in def sum, the flow type for sum is int and 
for list it is List<Integer>. That means sum+=i should change the flow type 
internally to Integer or keep int. The result of the inject should be Integer 
and it should always be allowed to assign it to sum, even if not.

bye Jochen

Reply via email to