Uploaded image for project: 'Groovy'
  1. Groovy
  2. GROOVY-8946

@CompileStatic ignores declared type and forces a cast when inside a closure

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Closed
    • Major
    • Resolution: Fixed
    • 2.4.16, 3.0.0-alpha-3, 2.5.5
    • 4.0.0-beta-1, 3.0.20
    • Static compilation
    • None
    • 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

    Description

      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).

      @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()
      

      When I try to run that, I get:

      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)
      

      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
        final Reference json = new Reference((Object)JSON.parse("[{\"a\":1},{\"a\":2}]"));
        
      • closure inner `json` reference
        private /* synthetic */ Reference json;
        
      • marked line
        json.set((Object)ScriptBytecodeAdapter.castToType(DefaultGroovyMethods.getAt(json.get(), token), (Class)JSONElement.class));
        

      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:

      final Object json2 = DefaultGroovyMethods.getAt((Object)json.get(), "a");
      

      Attachments

        Activity

          People

            emilles Eric Milles
            kikoalemao Frederico Costa Galvão
            Votes:
            0 Vote for this issue
            Watchers:
            3 Start watching this issue

            Dates

              Created:
              Updated:
              Resolved: