[ https://issues.apache.org/jira/browse/GROOVY-8946?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Eric Milles updated GROOVY-8946: -------------------------------- Fix Version/s: 3.0.5 > @CompileStatic ignores declared type and forces a cast when inside a closure > ---------------------------------------------------------------------------- > > Key: GROOVY-8946 > URL: https://issues.apache.org/jira/browse/GROOVY-8946 > 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 > Assignee: Eric Milles > Priority: Major > Fix For: 3.0.5, 4.0.0-beta-1 > > > 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 = 'https://repo.grails.org/grails/core') > @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 (v8.20.1#820001)