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

@Builder doesn't work on records

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Closed
    • Major
    • Resolution: Fixed
    • None
    • 4.0.10
    • None
    • None

    Description

      For this code:

      import groovy.transform.builder.*
      @Builder
      record Developer(Integer id, String first, String last, String email, List<String> skills) { }
      Developer.builder().id(2).build()
      

      The code fails in the build method. It is meant to create a new Developer but instead creates a DeveloperBuilder instance and then throws a cast exception:

      org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'Developer$DeveloperBuilder@5ef26266' with class 'Developer$DeveloperBuilder' to class 'Developer'
              ...
      	at Developer$DeveloperBuilder.build(ConsoleScript25)
      

      I wasn't necessarily expecting it to work. It could be made to work or we could explicitly disable it for records.

      Similarly, this code fails:

      @Builder(builderStrategy=InitializerStrategy)
      record Developer(Integer id, String first, String last, String email, List<String> skills) { }
      Developer.createInitializer().id(2).build()
      

      with this more obscure error:

      java.lang.ArrayIndexOutOfBoundsException: Internal compiler error while compiling ConsoleScript26
      Method: org.codehaus.groovy.ast.MethodNode@7cd420f9[Developer$DeveloperInitializer id(java.lang.Integer) from Developer$DeveloperInitializer]
      Line -1, expecting casting to Developer$DeveloperInitializer<groovy.transform.builder.InitializerStrategy$SET, T1, T2, T3, T4> but operand stack is empty
              ...
      	at org.codehaus.groovy.classgen.asm.OperandStack.doConvertAndCast(OperandStack.java:340)
      	at org.codehaus.groovy.classgen.asm.StatementWriter.writeReturn(StatementWriter.java:593)
      	at org.codehaus.groovy.classgen.AsmClassGenerator.visitReturnStatement(AsmClassGenerator.java:822)
      ...
      

      I would probably just used the named args style rather than a builder, e.g.:

      var dev1 = new Developer(id: 1, first: 'Dan', last: 'Vega', email: 'danvega@gmail.com', skills: ['Java', 'Spring'])
      assert dev1.with{ [id, first, last, email, skills] } ==
      //  [1, 'Dan', 'Vega', 'danvega@gmail.com', ['Java', 'Spring']]
      

      But we should either support or disable one or more of the @Builder strategies.

      Builder can also be written on constructors. That does work for the default strategy but again not for the InitializerStrategy. Here is a working example:

      import groovy.transform.builder.*
      record Developer(Integer id, String first, String last, String email, List<String> skills) {
          @Builder
          Developer(Integer id, String full, String email, List<String> skills) {
              this(id, full.split(' ')[0], full.split(' ')[1], email, skills)
          }
      }
      
      var dev1 = new Developer(id: 1, first: 'Dan', last: 'Vega', email: 'danvega@gmail.com', skills: ['Java', 'Spring'])
      
      assert dev1.with{ [id, first, last, email, skills] } ==
        [1, 'Dan', 'Vega', 'danvega@gmail.com', ['Java', 'Spring']]
      
      var dev2 = Developer.builder().id(2).full('Paul King').email('paulk@apache.org').skills(['Java', 'Groovy']).build()
      
      assert dev2.with{ [id, first, last, email, skills] } ==
        [2, 'Paul', 'King', 'paulk@apache.org', ['Java', 'Groovy']]
      

      Attachments

        Issue Links

          Activity

            People

              paulk Paul King
              paulk Paul King
              Votes:
              0 Vote for this issue
              Watchers:
              2 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: