You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@groovy.apache.org by "Paul King (Jira)" <ji...@apache.org> on 2023/02/28 15:39:00 UTC

[jira] [Created] (GROOVY-10955) @Builder doesn't work on records

Paul King created GROOVY-10955:
----------------------------------

             Summary: @Builder doesn't work on records
                 Key: GROOVY-10955
                 URL: https://issues.apache.org/jira/browse/GROOVY-10955
             Project: Groovy
          Issue Type: Bug
            Reporter: Paul King


For this code:
{code}
import groovy.transform.builder.*
@Builder
record Developer(Integer id, String first, String last, String email, List<String> skills) { }
Developer.builder().id(2).build()
{code}
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:
{noformat}
org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'Developer$DeveloperBuilder@5ef26266' with class 'Developer$DeveloperBuilder' to class 'Developer'
        ...
	at Developer$DeveloperBuilder.build(ConsoleScript25)
{noformat}
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:
{code}
@Builder(builderStrategy=InitializerStrategy)
record Developer(Integer id, String first, String last, String email, List<String> skills) { }
Developer.createInitializer().id(2).build()
{code}
with this more obscure error:
{noformat}
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)
...
{noformat}
I would probably just used the named args style rather than a builder, e.g.:
{code}
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']]
{code}
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:
{code}
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']]
{code}



--
This message was sent by Atlassian Jira
(v8.20.10#820010)