Skip to content

Conversation

@ay0ks
Copy link

@ay0ks ay0ks commented Sep 11, 2025

Fixes #3935

@ay0ks

This comment was marked as outdated.

@ay0ks
Copy link
Author

ay0ks commented Sep 12, 2025

I was able to build the project (missed ant dist the first time) and can confirm that the issue is resolved. Example code provided in #3935 (comment) compiles and runs with little changes.

It would be better to make generated default value providers protected to be able to be used inside the customized builder constructor.

@janrieke
Copy link
Contributor

janrieke commented Sep 12, 2025

Thanks for your contribution. I'll have a look at the PR next week. Until then, please:

  • add your name to the AUTHORS file,
  • verify that Eclipse is not affected, and
  • add a test case or extend an existing ("transform" folder).

Copilot AI review requested due to automatic review settings December 3, 2025 09:42
Copilot finished reviewing on behalf of ay0ks December 3, 2025 09:45
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This pull request fixes issue #3935 by improving the constructorExists method in HandleSuperBuilder.java to properly detect manually-written constructors that accept builder parameters with customized generic type parameters.

Key Changes

  • Refactored constructorExists to use AST-based type checking instead of string matching
  • Now supports builders with more than 2 type parameters (e.g., Builder<T, C, B>) as long as the last two are wildcards
  • Improved code clarity by using explicit AST node type checks

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 5 comments.

File Description
src/core/lombok/javac/handlers/HandleSuperBuilder.java Refactored constructorExists method to properly handle customized generic builders with additional type parameters beyond the standard 2
AUTHORS Added contributor credit for Dymitr "ay0ks"

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

JCExpression argLast = paramArgs.get(paramArgs.size() - 1);
JCExpression argLast2 = paramArgs.get(paramArgs.size() - 2);
if (argLast instanceof JCWildcard && argLast2 instanceof JCWildcard) {
return true;
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new logic only checks if the last two type arguments are wildcards, but it doesn't verify that they are unbounded wildcards (i.e., ? without bounds like ? extends T or ? super T). The old implementation checked for the exact pattern <?, ?>. If a user defines a constructor with bounded wildcards like Builder<? extends Something, ? super SomethingElse>, this might incorrectly match.

Consider checking the wildcard kind to ensure they are unbounded:

if (argLast instanceof JCWildcard && argLast2 instanceof JCWildcard) {
    JCWildcard wc1 = (JCWildcard) argLast;
    JCWildcard wc2 = (JCWildcard) argLast2;
    if (wc1.kind == TypeBoundKind.UNBOUND && wc2.kind == TypeBoundKind.UNBOUND) {
        return true;
    }
}
Suggested change
return true;
JCWildcard wc1 = (JCWildcard) argLast;
JCWildcard wc2 = (JCWildcard) argLast2;
if (wc1.kind == BoundKind.UNBOUND && wc2.kind == BoundKind.UNBOUND) {
return true;
}

Copilot uses AI. Check for mistakes.
JCExpression paramType = md.params.get(0).vartype;
if (paramType instanceof JCTypeApply) {
JCTypeApply paramType_ = (JCTypeApply) paramType;
if (paramType_.clazz instanceof JCIdent && ((JCIdent) paramType_.clazz).name.contentEquals(builderClassName)) {
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new implementation only handles the case where paramType_.clazz is a JCIdent (simple name), but the old code explicitly handled fully-qualified, partially-qualified, or unqualified names by extracting the last part after the last dot. This means the new code won't match constructors with qualified builder class names like protected Parent(com.example.Parent.ParentBuilder<?, ?> b).

Consider also handling JCFieldAccess for qualified names:

if (paramType_.clazz instanceof JCIdent && ((JCIdent) paramType_.clazz).name.contentEquals(builderClassName)) {
    // existing logic
} else if (paramType_.clazz instanceof JCFieldAccess) {
    String qualifiedName = paramType_.clazz.toString();
    String simpleName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
    if (simpleName.equals(builderClassName)) {
        // check wildcards
    }
}
Suggested change
if (paramType_.clazz instanceof JCIdent && ((JCIdent) paramType_.clazz).name.contentEquals(builderClassName)) {
boolean matchesBuilderClass = false;
if (paramType_.clazz instanceof JCIdent) {
matchesBuilderClass = ((JCIdent) paramType_.clazz).name.contentEquals(builderClassName);
} else if (paramType_.clazz instanceof JCFieldAccess) {
String qualifiedName = paramType_.clazz.toString();
String simpleName = qualifiedName.substring(qualifiedName.lastIndexOf('.') + 1);
matchesBuilderClass = simpleName.equals(builderClassName);
}
if (matchesBuilderClass) {

Copilot uses AI. Check for mistakes.
}

/**
* Checks if there is a manual constructor in the given type with a single parameter (builder).
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JavaDoc comment should be updated to reflect that this method now handles builders with customized generic parameters (more than the standard 2 type parameters), not just the standard Builder<?, ?> pattern.

Suggested update:

/**
 * Checks if there is a manual constructor in the given type with a single parameter (builder).
 * Supports builders with custom type parameters, as long as the last two type parameters are wildcards.
 */
Suggested change
* Checks if there is a manual constructor in the given type with a single parameter (builder).
* Checks if there is a manual constructor in the given type with a single parameter (builder).
* Supports builders with custom type parameters, as long as the last two type parameters are wildcards.

Copilot uses AI. Check for mistakes.
}
private boolean constructorExists(JavacNode type, String builderClassName) {
if (type != null && type.get() instanceof JCClassDecl) {
for (JCTree def : ((JCClassDecl) type.get()).defs) {
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Access to unsupported JDK-internal API 'com.sun.tools.javac.tree.JCTree'. (Use com.sun.source @SInCE 1.6)
Access to unsupported JDK-internal API 'com.sun.tools.javac.tree.JCTree'. (Use javax.tools and javax.lang.model @SInCE 1.6)

Copilot uses AI. Check for mistakes.
if (paramType instanceof JCTypeApply) {
JCTypeApply paramType_ = (JCTypeApply) paramType;
if (paramType_.clazz instanceof JCIdent && ((JCIdent) paramType_.clazz).name.contentEquals(builderClassName)) {
List<JCExpression> paramArgs = paramType_.arguments;
Copy link

Copilot AI Dec 3, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Access to unsupported JDK-internal API 'com.sun.tools.javac.util.List'. (Use javax.tools and javax.lang.model @SInCE 1.6)

Suggested change
List<JCExpression> paramArgs = paramType_.arguments;
java.util.List<JCExpression> paramArgs = new ArrayList<>(paramType_.arguments);

Copilot uses AI. Check for mistakes.
If I'm not mistaken, `builder()` should accept the generics at line 106.
I haven't touched this for a really long time, so excuse me.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG] @SuperBuilder constructor presence check is not working

2 participants