Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Skip JSpecify @nonnull constructor-param error when assignment failed
When the mapping resolver could not find an assignment (e.g. incompatible
source/target types), the JSpecify @nonnull constructor-param check still
fired, producing a misleading duplicate error on top of the primary
"can't find mapping" diagnostic. Guard the check on assignment != null so
users see the root-cause error alone.
  • Loading branch information
filiphr committed Apr 13, 2026
commit 2b9822301253dd81498701856798b35df8476795
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,11 @@ public PropertyMapping build() {
// constructor parameter without a default value. A null check would leave the variable
// at null, violating the @NonNull contract, and passing the value through is equally
// invalid — defaultValue / defaultExpression are the supported remedies.
if ( targetWriteAccessorType == AccessorType.PARAMETER && !hasDefaultValueOrDefaultExpression() ) {
// Skip when assignment resolution already failed: the user sees the primary
// "can't find mapping" error and a duplicate would only obscure the root cause.
if ( assignment != null
&& targetWriteAccessorType == AccessorType.PARAMETER
&& !hasDefaultValueOrDefaultExpression() ) {
NullabilityUtils.Nullability sourceNullability = getSourceJSpecifyNullability();
NullabilityUtils.Nullability targetNullability = NullabilityUtils.getSetterNullability(
targetWriteAccessor.getElement(), this::targetDeclaringTypeIsNullMarked
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.nullcheck.jspecify;

import org.mapstruct.Mapper;
import org.mapstruct.Mapping;

/**
* Erroneous mapper: the nullable source property cannot be mapped to the target constructor
* parameter's type at all (String -> AddressBean, with no mapping method declared). The
* "cannot find mapping" error must be the only diagnostic; the JSpecify
* "@NonNull constructor parameter" error must not also fire when assignment resolution failed.
*/
@Mapper
public interface ErroneousJSpecifyUnforgeableMapper {

@Mapping(target = "payload", source = "nullableValue")
UnmappableConstructorTargetBean map(SourceBean source);
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,20 @@ public void nonNullDirectParameterToNonNullConstructorParamCompiles() {
public void nullableDirectParameterToNonNullConstructorParamShouldFail() {
}

@ProcessorTest
@WithClasses({ AddressBean.class, UnmappableConstructorTargetBean.class, ErroneousJSpecifyUnforgeableMapper.class })
@ExpectedCompilationOutcome(value = CompilationResult.FAILED,
diagnostics = {
@Diagnostic(type = ErroneousJSpecifyUnforgeableMapper.class,
kind = javax.tools.Diagnostic.Kind.ERROR,
message = "Can't map property \"String nullableValue\" to \"AddressBean payload\". " +
"Consider to declare/implement a mapping method: \"AddressBean map(String value)\".")
})
public void failedAssignmentDoesNotAlsoTriggerNullableToNonNullConstructorParamError() {
// The JSpecify @NonNull constructor-param error must not fire when the assignment
// itself could not be resolved; otherwise users see two errors for one underlying issue.
}

@ProcessorTest
@WithClasses(JSpecifyConstructorDefaultValueMapper.class)
public void defaultValueSuppressesNullableToNonNullConstructorParamError() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright MapStruct Authors.
*
* Licensed under the Apache License version 2.0, available at http://www.apache.org/licenses/LICENSE-2.0
*/
package org.mapstruct.ap.test.nullcheck.jspecify;

import org.jspecify.annotations.NonNull;

public class UnmappableConstructorTargetBean {

private final AddressBean payload;

public UnmappableConstructorTargetBean(@NonNull AddressBean payload) {
this.payload = payload;
}

public AddressBean getPayload() {
return payload;
}
}