Details
-
Bug
-
Status: Closed
-
Major
-
Resolution: Fixed
-
None
-
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
- links to