diff --git a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties
index 473b05c675ee..ddc5f8021ca1 100644
--- a/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties
+++ b/java/java-analysis-api/resources/messages/JavaAnalysisBundle.properties
@@ -132,7 +132,14 @@ annotate.overridden.methods.parameters.nonnull=Annotate overriding method parame
annotate.overridden.methods.parameters.nullable=Annotate overriding method parameters as nullable
anonymous.ref.loc.can.be.replaced.with.0=Anonymous #ref can be replaced with {0}
anonymous.ref.loc.can.be.replaced.with.lambda=Anonymous #ref can be replaced with lambda
-assigning.a.collection.of.nullable.elements=Assigning a collection of nullable elements into a collection of non-null elements
+assigning.a.collection.of.nullable.elements=\
+
\
+ Assigning a class with nullable type arguments when a class with not-null type arguments is expected{0}\
+
+assigning.a.collection.of.notnull.elements=\
+ \
+ Assigning a class with not-null type arguments when a class with nullable type arguments is expected{0}\
+
returning.a.class.with.nullable.arguments=\
\
Returning a class with nullable type arguments when a class with not-null type arguments is expected{0}\
diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java
index b5b1a07da245..98e264c07e38 100644
--- a/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java
+++ b/java/java-analysis-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspectionBase.java
@@ -87,6 +87,8 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
public boolean REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER = true;
@SuppressWarnings("WeakerAccess") public boolean REPORT_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER = true;
+ @SuppressWarnings("WeakerAccess") public boolean REPORT_CONFLICT_IN_ASSIGNMENTS = true;
+
private static final Logger LOG = Logger.getInstance(NullableStuffInspectionBase.class);
@Override
@@ -102,6 +104,7 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
"REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER".equals(name) && "true".equals(value) ||
"REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS".equals(name) && "false".equals(value) ||
"REPORT_NOT_ANNOTATED_INSTANTIATION_NOT_NULL_TYPE".equals(name) && "false".equals(value) ||
+ "REPORT_CONFLICT_IN_ASSIGNMENTS".equals(name) && "true".equals(value) ||
"REPORT_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER".equals(name) && "true".equals(value)) {
node.removeContent(child);
}
@@ -210,6 +213,12 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
checkConstructorParameters(field, annotated, anno, annoToRemove, holder);
}
+ PsiExpression initializer = field.getInitializer();
+ PsiElement identifyingElement = field.getIdentifyingElement();
+ if (REPORT_CONFLICT_IN_ASSIGNMENTS && initializer != null && identifyingElement != null) {
+ checkNestedGenericClasses(identifyingElement, field.getType(), initializer.getType(),
+ ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
+ }
}
@Override
@@ -483,13 +492,30 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
@Override
public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) {
- checkCollectionNullityOnAssignment(expression.getOperationSign(), expression.getLExpression().getType(), expression.getRExpression());
+ PsiExpression rExpression = expression.getRExpression();
+ if (rExpression == null) return;
+ if (REPORT_CONFLICT_IN_ASSIGNMENTS) {
+ checkNestedGenericClasses(expression.getOperationSign(),
+ expression.getLExpression().getType(),
+ rExpression.getType(),
+ ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
+ }
+ else {
+ checkCollectionNullityOnAssignment(expression.getOperationSign(), expression.getLExpression().getType(), expression.getRExpression());
+ }
}
@Override
public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
PsiIdentifier identifier = variable.getNameIdentifier();
- if (identifier != null) {
+ if (identifier == null) return;
+ PsiExpression initializer = variable.getInitializer();
+ if (initializer == null) return;
+ if (REPORT_CONFLICT_IN_ASSIGNMENTS) {
+ checkNestedGenericClasses(identifier, variable.getType(), initializer.getType(),
+ ConflictNestedTypeProblem.ASSIGNMENT_NESTED_TYPE_PROBLEM);
+ }
+ else {
checkCollectionNullityOnAssignment(identifier, variable.getType(), variable.getInitializer());
}
}
@@ -498,8 +524,9 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
public void visitReturnStatement(@NotNull PsiReturnStatement statement) {
PsiExpression returnValue = statement.getReturnValue();
if (returnValue == null) return;
-
- checkGenericClassOnReturn(PsiTypesUtil.getMethodReturnType(statement), returnValue);
+ checkNestedGenericClasses(returnValue,
+ PsiTypesUtil.getMethodReturnType(statement), returnValue.getType(),
+ ConflictNestedTypeProblem.RETURN_NESTED_TYPE_PROBLEM);
}
@Override
@@ -530,17 +557,19 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
}
}
- private void checkGenericClassOnReturn(@Nullable PsiType expectedType,
- @NotNull PsiExpression returnValue) {
- PsiType returnType = returnValue.getType();
+ private void checkNestedGenericClasses(@NotNull PsiElement errorElement,
+ @Nullable PsiType expectedType,
+ @Nullable PsiType actualType,
+ @NotNull ConflictNestedTypeProblem problem) {
+ if(expectedType == null || actualType == null) return;
JavaTypeNullabilityUtil.NullabilityConflictContext
- context = JavaTypeNullabilityUtil.getNullabilityConflictInAssignment(expectedType, returnType,
+ context = JavaTypeNullabilityUtil.getNullabilityConflictInAssignment(expectedType, actualType,
REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS);
if (context.nullabilityConflict() == JavaTypeNullabilityUtil.NullabilityConflict.UNKNOWN) return;
String messageKey = context.nullabilityConflict() == JavaTypeNullabilityUtil.NullabilityConflict.NOT_NULL_TO_NULL ?
- "returning.a.class.with.notnull.arguments" : "returning.a.class.with.nullable.arguments";
+ problem.notNullToNullProblem() : problem.nullToNotNullProblem();
- reportProblem(holder, returnValue, LocalQuickFix.EMPTY_ARRAY,
+ reportProblem(holder, errorElement, LocalQuickFix.EMPTY_ARRAY,
messageKey, new Object[]{""},
messageKey, new Object[]{NullableStuffInspectionUtil.getNullabilityConflictPresentation(context)});
}
@@ -1347,4 +1376,37 @@ public class NullableStuffInspectionBase extends AbstractBaseJavaLocalInspection
return JavaAnalysisBundle.message("inspection.annotate.overridden.method.nullable.quickfix.name");
}
}
+
+
+ private enum ConflictNestedTypeProblem{
+ RETURN_NESTED_TYPE_PROBLEM("returning.a.class.with.notnull.arguments", "returning.a.class.with.nullable.arguments"),
+ ASSIGNMENT_NESTED_TYPE_PROBLEM("assigning.a.collection.of.notnull.elements", "assigning.a.collection.of.nullable.elements"),
+ ;
+
+ @NotNull
+ @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE)
+ private final String notNullToNullProblemMessage;
+
+ @NotNull
+ @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE)
+ private final String nullToNotNullProblemMessage;
+
+ ConflictNestedTypeProblem(@NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String notNullToNullProblemMessage,
+ @NotNull @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE) String nullToNotNullProblemMessage) {
+ this.notNullToNullProblemMessage = notNullToNullProblemMessage;
+ this.nullToNotNullProblemMessage = nullToNotNullProblemMessage;
+ }
+
+ @NotNull
+ @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE)
+ String notNullToNullProblem() {
+ return notNullToNullProblemMessage;
+ }
+
+ @NotNull
+ @PropertyKey(resourceBundle = JavaAnalysisBundle.BUNDLE)
+ String nullToNotNullProblem() {
+ return nullToNotNullProblemMessage;
+ }
+ }
}
diff --git a/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java b/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java
index 095ce25e4018..1ec54c99aae7 100644
--- a/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java
+++ b/java/java-impl/src/com/intellij/codeInspection/nullable/NullableStuffInspection.java
@@ -48,6 +48,8 @@ public class NullableStuffInspection extends NullableStuffInspectionBase {
checkbox("REPORT_NOT_ANNOTATED_GETTER", JavaBundle.message("inspection.nullable.problems.not.annotated.getters.for.annotated.fields")),
checkbox("REPORT_NULLS_PASSED_TO_NOT_NULL_PARAMETER", JavaBundle.message("inspection.nullable.problems.notnull.parameters.with.null.literal.option")),
checkbox("REPORT_NULLABILITY_ANNOTATION_ON_LOCALS", JavaBundle.message("inspection.nullable.problems.nullability.on.locals")),
+ checkbox("REPORT_CONFLICT_IN_ASSIGNMENTS", JavaBundle.message(
+ "inspection.assignment.conflicts")),
checkbox("REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS", JavaBundle.message(
"inspection.nullable.problems.notnull.to.nullable.assignment.conflicts")),
checkbox("REPORT_REDUNDANT_NULLABILITY_ANNOTATION_IN_THE_SCOPE_OF_ANNOTATED_CONTAINER", JavaBundle.message(
diff --git a/java/java-tests/testData/inspection/nullableProblems/AssignmentIncompatibilitiesWithGeneric.java b/java/java-tests/testData/inspection/nullableProblems/AssignmentIncompatibilitiesWithGeneric.java
new file mode 100644
index 000000000000..aa9717da4e34
--- /dev/null
+++ b/java/java-tests/testData/inspection/nullableProblems/AssignmentIncompatibilitiesWithGeneric.java
@@ -0,0 +1,54 @@
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Locale;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+@NullMarked
+class TestListWrapperAssignment {
+
+
+ ListArrayWrapper<@Nullable String> listByArrayWithNullField = ListArrayWrapper.createArray(new @Nullable String[] {"a", null, "c"});
+
+ ListArrayWrapper listWithNullField = ListArrayWrapper.createVarargs("a", null, "c");
+
+ public static void main(String[] args) {
+ ListArrayWrapper.createVarargs("a", "c").getList().forEach(s -> System.out.println(s.toUpperCase(Locale.ROOT)));
+
+ ListArrayWrapper listArrayWrapper = ListArrayWrapper.createVarargs("a", "c");
+ listArrayWrapper.getList().forEach(s -> System.out.println(s.toUpperCase(Locale.ROOT)));
+
+ ListArrayWrapper<@Nullable String> listArrayWrapperNullable = ListArrayWrapper.createVarargs("a", "c");
+ listArrayWrapperNullable.getList().forEach(s -> System.out.println(s.toUpperCase(Locale.ROOT)));
+
+ ListArrayWrapper listWithNull = ListArrayWrapper.createVarargs("a", null, "c");
+
+ ListArrayWrapper<@Nullable String> listByArrayWithNull = ListArrayWrapper.createArray(new @Nullable String[] {"a", null, "c"});
+ ListArrayWrapper varargs = ListArrayWrapper.createVarargs("1");
+ ListArrayWrapper<@Nullable String> listByArrayWithoutNull = varargs;
+ ListArrayWrapper listByArrayWithNullCorrectInspection1 = ListArrayWrapper.createArray(new @Nullable String[] {"a", null, "c"});
+ ListArrayWrapper<@Nullable String> listByArrayWithNullCorrectInspection2 = ListArrayWrapper.createArray(new String[] {"a", null, "c"});
+ }
+
+ static class ListArrayWrapper {
+ private final List list;
+
+ @SafeVarargs
+ static ListArrayWrapper createVarargs(E... elements) {
+ return new ListArrayWrapper<>(new ArrayList<>(Arrays.asList(elements)));
+ }
+
+ static ListArrayWrapper createArray(E[] array) {
+ return new ListArrayWrapper<>(new ArrayList<>(Arrays.asList(array)));
+ }
+
+ private ListArrayWrapper(List list) {
+ this.list = list;
+ }
+
+ List getList() {
+ return list;
+ }
+ }
+}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors.java b/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors.java
index 3f3145b68006..ab19ea2e791a 100644
--- a/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors.java
+++ b/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors.java
@@ -28,6 +28,6 @@ class Main {
AtomicReferenceArray arr = new AtomicReferenceArray<>(10);
AtomicReferenceArray arr2 = new AtomicReferenceArray<>(new String[10]); // technically wrong and should be highlighted, as array contains nulls
@Nullable String[] data = new String[10];
- AtomicReferenceArray arr3 = new AtomicReferenceArray<>(data); // technically wrong and should be highlighted, as array contains nulls
+ AtomicReferenceArray arr3 = new AtomicReferenceArray<>(data);
}
}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors_after.java b/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors_after.java
index 7302949099c3..fd048a9211a5 100644
--- a/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors_after.java
+++ b/java/java-tests/testData/inspection/nullableProblems/IncompatibleConstructors_after.java
@@ -28,6 +28,6 @@ class Main {
AtomicReferenceArray<@Nullable String> arr = new AtomicReferenceArray<>(10);
AtomicReferenceArray arr2 = new AtomicReferenceArray<>(new String[10]); // technically wrong and should be highlighted, as array contains nulls
@Nullable String[] data = new String[10];
- AtomicReferenceArray arr3 = new AtomicReferenceArray<>(data); // technically wrong and should be highlighted, as array contains nulls
+ AtomicReferenceArray arr3 = new AtomicReferenceArray<>(data);
}
}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/nullableProblems/PassingNullableCollectionWhereNotNullIsExpected.java b/java/java-tests/testData/inspection/nullableProblems/PassingNullableCollectionWhereNotNullIsExpected.java
index d3129e9964bd..6de55c97050a 100644
--- a/java/java-tests/testData/inspection/nullableProblems/PassingNullableCollectionWhereNotNullIsExpected.java
+++ b/java/java-tests/testData/inspection/nullableProblems/PassingNullableCollectionWhereNotNullIsExpected.java
@@ -6,15 +6,15 @@ class JC {
void testList() {
List<@Nullable String> nullableList = new ArrayList<>();
- print(nullableList);
+ print(nullableList);
- List<@NotNull String> list2 = nullableList;
+ List<@NotNull String> list2 = nullableList;
List<@NotNull String> list3;
- list2 = nullableList;
+ list2 = nullableList;
List super @NotNull String> list4 = nullableList;
- List extends @NotNull String> list5 = nullableList;
+ List extends @NotNull String> list5 = nullableList;
}
private static void print(List<@NotNull String> list) {
@@ -28,8 +28,8 @@ class JC {
List<@NotNull String> testReturnValue() {
List<@Nullable String> list = new ArrayList<>();
- Supplier> supplier = () -> list;
- Supplier> supplierRef = this::getNullableList;
+ Supplier> supplier = () -> list;
+ Supplier> supplierRef = this::getNullableList;
Supplier> supplier3 = () -> { return list;};
@@ -41,6 +41,6 @@ class JC {
class Test {
public void test() {
List nullableList = new ArrayList<>();
- List extends @NotNull CharSequence> list2 = nullableList;
+ List extends @NotNull CharSequence> list2 = nullableList;
}
}
\ No newline at end of file
diff --git a/java/java-tests/testData/inspection/nullableProblems/PassingNullableMapWhereNotNullIsExpected.java b/java/java-tests/testData/inspection/nullableProblems/PassingNullableMapWhereNotNullIsExpected.java
index 71166267356b..c2789219bc48 100644
--- a/java/java-tests/testData/inspection/nullableProblems/PassingNullableMapWhereNotNullIsExpected.java
+++ b/java/java-tests/testData/inspection/nullableProblems/PassingNullableMapWhereNotNullIsExpected.java
@@ -4,11 +4,11 @@ import java.util.*;
class JC {
void testMap() {
- Map<@NotNull String, @NotNull String> m1 = new HashMap<@Nullable String, String>();
- m1 = new HashMap();
+ Map<@NotNull String, @NotNull String> m1 = new HashMap<@Nullable String, String>();
+ m1 = new HashMap();
m1 = new HashMap();
- Map<@NotNull String, ? extends @NotNull String> m2 = new HashMap<@Nullable String, String>();
+ Map<@NotNull String, ? extends @NotNull String> m2 = new HashMap<@Nullable String, String>();
}
}
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/JSpecifyFilteredAnnotationTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/JSpecifyFilteredAnnotationTest.java
index 411cb324e6cf..3541efad25c5 100644
--- a/java/java-tests/testSrc/com/intellij/java/codeInspection/JSpecifyFilteredAnnotationTest.java
+++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/JSpecifyFilteredAnnotationTest.java
@@ -113,11 +113,17 @@ public class JSpecifyFilteredAnnotationTest extends LightJavaCodeInsightFixtureT
new Pair<>("MultiBoundTypeVariableUnionNullToSelf.java", 62), // see: IDEA-377697
- new Pair<>("WildcardCapturesToBoundOfTypeParameterNotToTypeVariableItself.java", 24), // see: IDEA-377699
+ new Pair<>("WildcardCapturesToBoundOfTypeParameterNotToTypeVariableItself.java", 24) ,// see: IDEA-377699
new Pair<>("SelfType.java", 34), // see: IDEA-377707 (also see the commented case in warning matchers)
new Pair<>("SelfType.java", 43), // see: IDEA-377707 (also see the commented case in warning matchers)
- new Pair<>("OutOfBoundsTypeVariable.java", 21) // see: IDEA-377707 (also see the commented case in warning matchers)
+ new Pair<>("OutOfBoundsTypeVariable.java", 21), // see: IDEA-377707 (also see the commented case in warning matchers)
+ new Pair<>("TypeParameterBounds.java", 40), // see: IDEA-377707
+
+ new Pair<>("AugmentedInferenceAgreesWithBaseInference.java", 33), // see: IDEA-377683
+ new Pair<>("NullnessUnspecifiedTypeParameter.java", 33), // see: IDEA-377683
+ new Pair<>("TypeVariableMinusNullVsTypeVariable.java", 28), // see: IDEA-377683
+ new Pair<>("TypeVariableMinusNullVsTypeVariable.java", 30) // see: IDEA-377683
)
),
new SkipIndividuallyFilter( //cases to investigate later (with unspecified annotation and complicated to understand). (line number starts from 0)
@@ -179,8 +185,7 @@ public class JSpecifyFilteredAnnotationTest extends LightJavaCodeInsightFixtureT
new Pair<>("SuperNullableForNonNullableTypeParameter.java", 27) // see: IDEA-379303
)
),
- new CallWithParameterWithNestedGenericsFilter(), // see: IDEA-377682
- new VariableWithNestedGenericsFilter() // see: IDEA-377683
+ new CallWithParameterWithNestedGenericsFilter() // see: IDEA-377682
);
private static final LightProjectDescriptor PROJECT_DESCRIPTOR = new DefaultLightProjectDescriptor() {
@@ -576,6 +581,7 @@ public class JSpecifyFilteredAnnotationTest extends LightJavaCodeInsightFixtureT
case "inspection.nullable.problems.Nullable.method.overrides.NotNull",
"inspection.nullable.problems.NotNull.parameter.overrides.Nullable",
"assigning.a.collection.of.nullable.elements",
+ "assigning.a.collection.of.notnull.elements",
"returning.a.class.with.nullable.arguments",
"returning.a.class.with.notnull.arguments"
//, "non.null.type.argument.is.expected" //todo see IDEA-377707
diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java
index 563efccecc5a..ff9e819657d2 100644
--- a/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java
+++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/NullableStuffInspectionTest.java
@@ -516,6 +516,13 @@ public class NullableStuffInspectionTest extends LightJavaCodeInsightFixtureTest
doTest();
}
+ public void testAssignmentIncompatibilitiesWithGeneric() {
+ myInspection.REPORT_NOT_NULL_TO_NULLABLE_CONFLICTS_IN_ASSIGNMENTS = true;
+ addJSpecifyNullMarked(myFixture);
+ setupTypeUseAnnotations("org.jspecify.annotations", myFixture);
+ doTest();
+ }
+
public void testArrayUnderContainerAnnotation() {
myInspection.REPORT_NOT_ANNOTATED_INSTANTIATION_NOT_NULL_TYPE = true;
addJSpecifyNullMarked(myFixture);
diff --git a/java/openapi/resources/messages/JavaBundle.properties b/java/openapi/resources/messages/JavaBundle.properties
index 56b134be6e79..74fac482fa04 100644
--- a/java/openapi/resources/messages/JavaBundle.properties
+++ b/java/openapi/resources/messages/JavaBundle.properties
@@ -564,6 +564,7 @@ inspection.nullable.problems.not.annotated.getters.for.annotated.fields=Report n
inspection.nullable.problems.notnull.overrides.option=Report @NotNull ¶meters overriding non-annotated
inspection.nullable.problems.notnull.parameters.with.null.literal.option=Report @NotNull parameters with null-literal argument usages
inspection.nullable.problems.nullability.on.locals=Report nullability annotations on local variables when they are non-applicable
+inspection.assignment.conflicts=Report nullability conflicts for nested types in assignment and variable declarations
inspection.nullable.problems.notnull.to.nullable.assignment.conflicts=Report assignment of a not-null type argument to a nullable type argument
inspection.nullable.problems.redundant.nullability.inside.container=Report redundant nullability annotation in the scope of annotated container
inspection.optional.get.without.is.present.message={0}.#ref() without ''isPresent()'' check