diff --git a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java index 498e956ca595..0aaf0d401c5c 100644 --- a/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java +++ b/java/java-analysis-impl/src/com/intellij/codeInspection/dataFlow/HardcodedContracts.java @@ -21,8 +21,7 @@ import org.jetbrains.annotations.Nullable; import java.util.*; import static com.intellij.codeInspection.dataFlow.ContractReturnValue.*; -import static com.intellij.codeInspection.dataFlow.MethodContract.singleConditionContract; -import static com.intellij.codeInspection.dataFlow.MethodContract.trivialContract; +import static com.intellij.codeInspection.dataFlow.MethodContract.*; import static com.intellij.codeInspection.dataFlow.StandardMethodContract.ValueConstraint.*; import static com.intellij.codeInspection.dataFlow.StandardMethodContract.createConstraintArray; import static com.intellij.psi.CommonClassNames.*; @@ -184,16 +183,31 @@ public final class HardcodedContracts { .register(enumValues(), ContractProvider.of(StandardMethodContract.fromText("->new"))) .register(staticCall("java.lang.System", "arraycopy"), expression -> getArraycopyContract()) .register(anyOf( - instanceCall("java.util.Date", "before", "after"), - instanceCall("java.time.LocalDate", "isBefore", "isAfter"), - instanceCall("java.time.LocalDateTime", "isBefore", "isAfter"), - instanceCall("java.time.LocalTime", "isBefore", "isAfter"), - instanceCall("java.time.ZonedDateTime", "isBefore", "isAfter"), - instanceCall("java.time.Year", "isBefore", "isAfter"), - instanceCall("java.time.YearMonth", "isBefore", "isAfter") - ), ContractProvider.of( - singleConditionContract(ContractValue.qualifier(), RelationType.EQ, ContractValue.argument(0), returnFalse()) - )) + instanceCall(JAVA_TIME_LOCAL_DATE, "isAfter"), + instanceCall(JAVA_TIME_LOCAL_TIME, "isAfter"), + instanceCall(JAVA_TIME_OFFSET_TIME, "isAfter"), + instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "isAfter"), + instanceCall(JAVA_TIME_ZONED_DATE_TIME, "isAfter"), + instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "isAfter"), + instanceCall("java.util.Date", "after"), + instanceCall("java.time.Year", "isAfter"), + instanceCall("java.time.YearMonth", "isAfter")), + ContractProvider.of( + singleConditionContract(ContractValue.qualifier(), RelationType.GT, ContractValue.argument(0), returnBoolean(true)), + trivialContract(returnBoolean(false)))) + .register(anyOf( + instanceCall(JAVA_TIME_LOCAL_DATE, "isBefore"), + instanceCall(JAVA_TIME_LOCAL_TIME, "isBefore"), + instanceCall(JAVA_TIME_OFFSET_TIME, "isBefore"), + instanceCall(JAVA_TIME_OFFSET_DATE_TIME, "isBefore"), + instanceCall(JAVA_TIME_ZONED_DATE_TIME, "isBefore"), + instanceCall(JAVA_TIME_LOCAL_DATE_TIME, "isBefore"), + instanceCall("java.util.Date", "before"), + instanceCall("java.time.Year", "isBefore"), + instanceCall("java.time.YearMonth", "isBefore")), + ContractProvider.of( + singleConditionContract(ContractValue.qualifier(), RelationType.LT, ContractValue.argument(0), returnBoolean(true)), + trivialContract(returnBoolean(false)))) //for propagation CONSUMED_STREAM .register(ConsumedStreamUtils.getSkipStreamMatchers(), ContractProvider.of(trivialContract(returnThis()))) .register(staticCall(JAVA_LANG_CHARACTER, "isSurrogate").parameterCount(1), diff --git a/java/java-tests/testData/inspection/dataFlow/fixture/DateTimeComparing.java b/java/java-tests/testData/inspection/dataFlow/fixture/DateTimeComparing.java new file mode 100644 index 000000000000..c584d42bf546 --- /dev/null +++ b/java/java-tests/testData/inspection/dataFlow/fixture/DateTimeComparing.java @@ -0,0 +1,30 @@ +import java.time.LocalDateTime; +import java.time.LocalTime; + +public class DateTimeComparing { + public void testLocalDateTime() { + LocalDateTime now1 = LocalDateTime.now(); + LocalDateTime now2 = LocalDateTime.now(); + if (now1.isAfter(now2)) { + if (now1.isBefore(now2)) { + + } + if (now2.isBefore(now1)) { + } + } + } + + public void testLocalTime() { + LocalTime now1 = LocalTime.now(); + LocalTime now2 = LocalTime.now(); + if (now1.isAfter(now2)) { + + if (now1.isBefore(now2)) { + + } + if (now2.isBefore(now1)) { + + } + } + } +} diff --git a/java/java-tests/testSrc/com/intellij/java/codeInspection/HardcodedContractsTest.java b/java/java-tests/testSrc/com/intellij/java/codeInspection/HardcodedContractsTest.java index 08e25274a508..abad2c609fd6 100644 --- a/java/java-tests/testSrc/com/intellij/java/codeInspection/HardcodedContractsTest.java +++ b/java/java-tests/testSrc/com/intellij/java/codeInspection/HardcodedContractsTest.java @@ -184,4 +184,29 @@ public class HardcodedContractsTest extends DataFlowInspectionTestCase { } public void testCharacterMethods() { checkHighlighting(); } + + @SuppressWarnings("MethodOverloadsMethodOfSuperclass") + public void testDateTimeComparing() { + myFixture.addClass(""" + package java.time.chrono; + public interface ChronoLocalDateTime { }"""); + myFixture.addClass(""" + package java.time; + import java.time.temporal.TemporalUnit; + public final class LocalTime { + public static LocalTime now() { return new LocalTime(); } + public boolean isBefore(LocalTime localTime) { return false; } + public boolean isAfter(LocalTime localTime) { return false; } + }"""); + myFixture.addClass(""" + package java.time; + import java.time.chrono.ChronoLocalDateTime; + import java.time.temporal.TemporalUnit; + public final class LocalDateTime implements ChronoLocalDateTime { + public static LocalDateTime now() { return new LocalDateTime(); } + public boolean isBefore(ChronoLocalDateTime localDateTime2) { return false; } + public boolean isAfter(ChronoLocalDateTime localDateTime2) { return false; } + }"""); + checkHighlighting(); + } }