groovy-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Frederico Costa Galvão (JIRA) <>
Subject [jira] [Commented] (GROOVY-8946) @CompileStatic ignores declared type and forces a cast when inside a closure
Date Sat, 05 Jan 2019 18:28:00 GMT


Frederico Costa Galvão commented on GROOVY-8946:

Thanks for your time, [~blackdrag]!

I'm slightly familiar with how a reference work in that sense, mainly from inspecting groovy
bytecode to understand its inner workings. Thanks anyway for clarifying it!
 The fact that both the outer and inner reference declarations retained the Object type (or
rather no type at all) is a reinforcement to my point, AFAIK.

My partial familiarity with flow typing (both from Java/Groovy's and Typescript's inner workings),
makes me believe that it's exactly because of how the intersection type should work that I
suspected this was a bug: even if JSON.parse has a declared return value of JSONElement (which
it does), which makes the inferred value type JSONElement, the fact that both <the declared
type> and <the late type from DefaultGroovyMethod.getAt> are both Object makes it
so that <JSONElement || Object> can only be <Object> in a nominal typing system,
unless groovyc could see through the raw typing of DGM.getAt and infer that it would return
another JSONElement (but it'd be wrong anyway, as it could be a scalar/primitive).
 This is not the first time I've seen groovyc ignore the explicitly declared type and take
control with flow typing, but I don't have any other scenario to provide at the moment.

I'm looking forward to [~daniel_sun]'s take on this.

> @CompileStatic ignores declared type and forces a cast when inside a closure
> ----------------------------------------------------------------------------
>                 Key: GROOVY-8946
>                 URL:
>             Project: Groovy
>          Issue Type: Bug
>          Components: Static compilation
>    Affects Versions: 2.4.16, 3.0.0-alpha-3, 2.5.5
>         Environment: Linux wmtz00088 4.4.0-141-generic #167-Ubuntu SMP Wed Dec 5 10:40:15
UTC 2018 x86_64 x86_64 x86_64 GNU/Linux
>            Reporter: Frederico Costa Galvão
>            Priority: Major
> Using a typed method (which happens to be JSON.parse from grails.converters) but handling
stuff as `Object` still makes the compiler generate a cast to the infered value type, ignoring
the declared type.
> I wanted to reproduce this as close as possible, so my snippet grabs stuff from grails.
However, this should be easily reproducible with a simple typed method instead of using the
grails stuff.
> For readability's sake, the original code (greatly simplified bellow) tries to navigate
through a json object with a deep path like 'a.b.c', in which the values at each step could
be multidimensional or not (and implicit `collect`s happens when it is).
> {code}
> @GrabResolver(name = 'r1', root = '')
> @Grapes([
>     @Grab("org.grails.plugins:converters:3.3.+"),
>     @Grab(group='org.grails', module='grails-encoder', version='3.3.+'),
>     @Grab(group='org.grails', module='grails-web', version='3.3.+'),
>     @Grab(group='commons-io', module='commons-io', version='2.3'),
> ])
> @GrabExclude('org.codehaus.groovy:groovy-xml')
> import grails.converters.JSON
> import groovy.transform.CompileStatic
> @CompileStatic
> class What {
>     static void _do() {
>         Object json = JSON.parse('[{"a":1},{"a":2}]')
>         Object json2 = json['a']
>         final boolean res = 'a'.tokenize('.').every { final String token ->
>             json = json[token]//MARK, ERROR
>             return json
>         }
>         assert json == [1, 2]
>     }
> }
> What._do()
> {code}
> When I try to run that, I get:
> {noformat}
> org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object '[1,
2]' with class 'java.util.ArrayList' to class 'org.grails.web.json.JSONElement' due to: groovy.lang.GroovyRuntimeException:
Could not find matching constructor for: org.grails.web.json.JSONElement(Integer, Integer)
> {noformat}
> And the bytecode for the relevant code is (Luyten is the decompiler of choice, but I've
seen the same with groovyConsole's inner AST browser):
>  - outside `json` reference
> {code:java}
> final Reference json = new Reference((Object)JSON.parse("[{\"a\":1},{\"a\":2}]"));
> {code}
>  - closure inner `json` reference
> {code:java}
> private /* synthetic */ Reference json;
> {code}
>  - marked line
> {code:java}
> json.set((Object)ScriptBytecodeAdapter.castToType(DefaultGroovyMethods.getAt(json.get(),
token), (Class)JSONElement.class));
> {code}
> I have tried that with:
>  groovy: 2.4.[11..16], 2.5.5, 3.0.0-alpha-3
> Things that make it run/compile properly:
>  - casting `JSON.parse(...)` to `(Object)`
>  - removing `@CompileStatic`
>  - using `JsonSlurper` (which already returns `Object`)
> I understand that type inference from flow is useful and important, but shouldn't it
respect the declared type whenever it differs from def? It's not inside a type guard, nor
did I use a `JSONElement` specific member to rightfully trigger it. I may be wrong and this
could be by design, but I couldn't find something to justify this out-of-sight cast.
> Also, the other/last argument I have in favor of it being a bug is that the same operation,
if done outside the closure, compiles just fine:
> {code:java}
> final Object json2 = DefaultGroovyMethods.getAt((Object)json.get(), "a");
> {code}

This message was sent by Atlassian JIRA

View raw message