-
-
Notifications
You must be signed in to change notification settings - Fork 1k
Fix #4030 remove double getters in null checks #4064
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -37,7 +37,7 @@ | |
|
|
||
| /** | ||
| * A builder that is used for creating an assignment to a collection. | ||
| * | ||
| * <p> | ||
| * The created assignments to the following null checks: | ||
| * <ul> | ||
| * <li>source-null-check - For this the {@link SetterWrapperForCollectionsAndMapsWithNullCheck} is used when a | ||
|
|
@@ -49,7 +49,7 @@ | |
| * <li>local-var-null-check - Done in {@link ExistingInstanceSetterWrapperForCollectionsAndMaps}, and | ||
| * {@link SetterWrapperForCollectionsAndMapsWithNullCheck}</li> | ||
| * </ul> | ||
| * | ||
| * <p> | ||
| * A local-var-null-check is needed in the following cases: | ||
| * | ||
| * <ul> | ||
|
|
@@ -106,7 +106,6 @@ public CollectionAssignmentBuilder targetAccessorType(AccessorType targetAccesso | |
|
|
||
| /** | ||
| * @param assignment the assignment that needs to be invoked | ||
| * | ||
| * @return this builder for chaining | ||
| */ | ||
| public CollectionAssignmentBuilder assignment(Assignment assignment) { | ||
|
|
@@ -116,20 +115,19 @@ public CollectionAssignmentBuilder assignment(Assignment assignment) { | |
|
|
||
| /** | ||
| * @param sourceRHS the source right hand side for getting the property for mapping | ||
| * | ||
| * @return this builder for chaining | ||
| */ | ||
| public CollectionAssignmentBuilder rightHandSide(SourceRHS sourceRHS) { | ||
| this.sourceRHS = sourceRHS; | ||
| return this; | ||
| } | ||
|
|
||
| public CollectionAssignmentBuilder nullValueCheckStrategy( NullValueCheckStrategyGem nvcs ) { | ||
| public CollectionAssignmentBuilder nullValueCheckStrategy(NullValueCheckStrategyGem nvcs) { | ||
| this.nvcs = nvcs; | ||
| return this; | ||
| } | ||
|
|
||
| public CollectionAssignmentBuilder nullValuePropertyMappingStrategy( NullValuePropertyMappingStrategyGem nvpms ) { | ||
| public CollectionAssignmentBuilder nullValuePropertyMappingStrategy(NullValuePropertyMappingStrategyGem nvpms) { | ||
| this.nvpms = nvpms; | ||
| return this; | ||
| } | ||
|
|
@@ -178,19 +176,12 @@ else if ( method.isUpdateMethod() && !targetImmutable ) { | |
| ctx.getTypeFactory(), | ||
| targetAccessorType.isFieldAssignment() | ||
| ); | ||
| if ( result.getSourceType().isPrimitive() ) { | ||
| result.setSourceLocalVarName( null ); | ||
| } | ||
|
Comment on lines
+179
to
+181
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why are primitives excluded? |
||
| } | ||
| else if ( method.isUpdateMethod() && nvpms == IGNORE ) { | ||
|
|
||
| result = new SetterWrapperForCollectionsAndMapsWithNullCheck( | ||
| result, | ||
| method.getThrownTypes(), | ||
| targetType, | ||
| ctx.getTypeFactory(), | ||
| targetAccessorType.isFieldAssignment() | ||
| ); | ||
| } | ||
| else if ( setterWrapperNeedsSourceNullCheck( result ) | ||
| && canBeMappedOrDirectlyAssigned( result ) ) { | ||
| else if ( method.isUpdateMethod() && nvpms == IGNORE | ||
| || setterWrapperNeedsSourceNullCheck( result ) && canBeMappedOrDirectlyAssigned( result ) ) { | ||
|
|
||
| result = new SetterWrapperForCollectionsAndMapsWithNullCheck( | ||
| result, | ||
|
|
@@ -199,6 +190,7 @@ && canBeMappedOrDirectlyAssigned( result ) ) { | |
| ctx.getTypeFactory(), | ||
| targetAccessorType.isFieldAssignment() | ||
| ); | ||
| result.setSourceLocalVarName( null ); | ||
| } | ||
| else if ( canBeMappedOrDirectlyAssigned( result ) ) { | ||
| //TODO init default value | ||
|
|
@@ -210,14 +202,17 @@ else if ( canBeMappedOrDirectlyAssigned( result ) ) { | |
| targetType, | ||
| targetAccessorType.isFieldAssignment() | ||
| ); | ||
| result.setSourceLocalVarName( null ); | ||
| } | ||
| else if ( hasNoArgsConstructor() ) { | ||
| result = new NewInstanceSetterWrapperForCollectionsAndMaps( | ||
| result, | ||
| method.getThrownTypes(), | ||
| targetType, | ||
| ctx.getTypeFactory(), | ||
| targetAccessorType.isFieldAssignment() ); | ||
| targetAccessorType.isFieldAssignment() | ||
| ); | ||
| result.setSourceLocalVarName( null ); | ||
| } | ||
| else { | ||
| ctx.getMessager().printMessage( | ||
|
|
@@ -244,15 +239,16 @@ else if ( hasNoArgsConstructor() ) { | |
| nvpms, | ||
| targetAccessorType.isFieldAssignment() | ||
| ); | ||
| result.setSourceLocalVarName( null ); | ||
| } | ||
|
|
||
| return result; | ||
| } | ||
|
|
||
| private boolean canBeMappedOrDirectlyAssigned(Assignment result) { | ||
| return result.getType() != AssignmentType.DIRECT | ||
| || hasCopyConstructor() | ||
| || targetType.isEnumSet(); | ||
| || hasCopyConstructor() | ||
| || targetType.isEnumSet(); | ||
| } | ||
|
|
||
| /** | ||
|
|
@@ -294,8 +290,8 @@ private boolean checkConstructorForPredicate(Predicate<Element> predicate) { | |
| } | ||
| else { | ||
| Element sourceElement = targetType.getImplementationType() != null | ||
| ? targetType.getImplementationType().getTypeElement() | ||
| : targetType.getTypeElement(); | ||
| ? targetType.getImplementationType().getTypeElement() | ||
| : targetType.getTypeElement(); | ||
| if ( sourceElement != null ) { | ||
| for ( Element element : sourceElement.getEnclosedElements() ) { | ||
| if ( element.getKind() == ElementKind.CONSTRUCTOR | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -35,6 +35,7 @@ | |
| import org.mapstruct.ap.internal.model.common.FormattingParameters; | ||
| import org.mapstruct.ap.internal.model.common.ModelElement; | ||
| import org.mapstruct.ap.internal.model.common.Parameter; | ||
| import org.mapstruct.ap.internal.model.common.ParameterBinding; | ||
| import org.mapstruct.ap.internal.model.common.PresenceCheck; | ||
| import org.mapstruct.ap.internal.model.common.SourceRHS; | ||
| import org.mapstruct.ap.internal.model.common.Type; | ||
|
|
@@ -660,6 +661,11 @@ private boolean hasDefaultValueOrDefaultExpression() { | |
| private Assignment assignToPlainViaAdder( Assignment rightHandSide) { | ||
|
|
||
| Assignment result = rightHandSide; | ||
| if ( sourceReference == null || !sourceReference.isNested() ) { | ||
| // For non-nested properties, reset the local var name: the adder wrapper handles | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor: the comment says "For non-nested properties", but the guard also covers |
||
| // the source reference inline without needing a local variable. | ||
| result.setSourceLocalVarName( null ); | ||
| } | ||
|
|
||
| String adderIteratorName = sourcePropertyName == null ? targetPropertyName : sourcePropertyName; | ||
| if ( result.getSourceType().isIterableType() ) { | ||
|
|
@@ -767,6 +773,9 @@ else if ( !sourceReference.isNested() ) { | |
| sourceReference, | ||
| sourceRHS | ||
| ) ); | ||
| if ( presenceCheckAllowsLocalVar( sourceRHS.getSourcePresenceCheckerReference() ) ) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is where the local var gets set for simple (non-nested) properties. Because |
||
| sourceRHS.setSourceLocalVarName( sourceRHS.createUniqueVarName( propertyEntry.getName() ) ); | ||
| } | ||
| return sourceRHS; | ||
| } | ||
| // nested property given as dot path | ||
|
|
@@ -821,6 +830,14 @@ else if ( !sourceReference.isNested() ) { | |
| } | ||
| } | ||
|
|
||
| private boolean presenceCheckAllowsLocalVar(PresenceCheck presenceCheck) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔴 Root cause of the unconditional-getter regression. This guard only excludes The principle here is right ("only hoist when the presence check consumes the source RHS value"), but it's enforced incompletely. Suggest returning Minor: this method encodes the subtlest decision in the PR — worth a short comment explaining the intent. |
||
| if ( presenceCheck instanceof MethodReferencePresenceCheck ) { | ||
| return ((MethodReferencePresenceCheck) presenceCheck).getMethodReference().getParameterBindings() | ||
| .stream().anyMatch( ParameterBinding::isForSourceRhs ); | ||
| } | ||
| return true; | ||
| } | ||
|
|
||
|
Comment on lines
+833
to
+840
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is checked here? |
||
| private PresenceCheck getSourcePresenceCheckerRef(SourceReference sourceReference, | ||
| SourceRHS sourceRHS) { | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the mutable-collection update path (
ExistingInstanceSetterWrapperForCollectionsAndMaps), the local var is only reset for primitive source types. So aList<String>fed by ahasX()presence check keeps the hoisted local var and is read unconditionally (this is the exact branch the_913update mappers hit). If the fix inpresenceCheckAllowsLocalVarlands, this primitive-only reset may be redundant — but as it stands today it doesn't cover the regression.