IDEA-184920 Create external annotation for integral ranges and annotate some JDK methods

This commit is contained in:
Tagir Valeev
2018-01-15 15:11:32 +07:00
parent 397d8f8fff
commit f8ad50f33f
5 changed files with 253 additions and 18 deletions
@@ -4,10 +4,11 @@ package com.intellij.codeInspection.dataFlow.rangeSet;
import com.intellij.codeInsight.AnnotationUtil;
import com.intellij.codeInspection.dataFlow.DfaFactType;
import com.intellij.codeInspection.dataFlow.value.*;
import com.intellij.psi.*;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiModifierListOwner;
import com.intellij.psi.PsiPrimitiveType;
import com.intellij.psi.PsiType;
import com.intellij.util.ThreeState;
import com.siyeh.ig.callMatcher.CallMapper;
import com.siyeh.ig.callMatcher.CallMatcher;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -24,16 +25,6 @@ import static com.intellij.codeInsight.AnnotationUtil.CHECK_TYPE;
* @author Tagir Valeev
*/
public abstract class LongRangeSet {
// TODO: create an external annotation and use it
private static final CallMapper<LongRangeSet> KNOWN_METHOD_RANGES = new CallMapper<LongRangeSet>()
.register(CallMatcher.instanceCall("java.time.LocalDateTime", "getHour"), range(0, 23))
.register(CallMatcher.instanceCall("java.time.LocalDateTime", "getMinute", "getSecond"), range(0, 59))
.register(CallMatcher.staticCall(CommonClassNames.JAVA_LANG_LONG, "numberOfLeadingZeros", "numberOfTrailingZeros", "bitCount"),
range(0, Long.SIZE))
.register(CallMatcher.staticCall(CommonClassNames.JAVA_LANG_INTEGER, "numberOfLeadingZeros", "numberOfTrailingZeros", "bitCount"),
range(0, Integer.SIZE))
.register(CallMatcher.instanceCall(CommonClassNames.JAVA_LANG_ENUM, "ordinal").parameterCount(0), indexRange());
LongRangeSet() {}
/**
@@ -446,10 +437,12 @@ public abstract class LongRangeSet {
@NotNull
public static LongRangeSet fromPsiElement(PsiModifierListOwner owner) {
if (owner == null) return all();
if (owner instanceof PsiMethod) {
LongRangeSet rangeSet = KNOWN_METHOD_RANGES.mapFirst((PsiMethod)owner);
if (rangeSet != null) {
return rangeSet;
PsiAnnotation rangeAnnotation = AnnotationUtil.findAnnotation(owner, "org.jetbrains.annotations.Range");
if(rangeAnnotation != null) {
Long from = AnnotationUtil.getLongAttributeValue(rangeAnnotation, "from");
Long to = AnnotationUtil.getLongAttributeValue(rangeAnnotation, "to");
if(from != null && to != null && to >= from) {
return range(from, to);
}
}
if (AnnotationUtil.isAnnotated(owner, "javax.annotation.Nonnegative", CHECK_TYPE)) {
@@ -7,7 +7,10 @@ import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Comparing;
import com.intellij.psi.*;
import com.intellij.psi.util.*;
import com.intellij.util.*;
import com.intellij.util.ArrayUtil;
import com.intellij.util.Consumer;
import com.intellij.util.Processor;
import com.intellij.util.Processors;
import com.intellij.util.containers.ConcurrentFactoryMap;
import com.intellij.util.containers.ContainerUtil;
import gnu.trove.THashMap;
@@ -487,6 +490,13 @@ public class AnnotationUtil {
return constValue instanceof Boolean ? (Boolean)constValue : null;
}
@Nullable
public static Long getLongAttributeValue(@NotNull PsiAnnotation anno, @Nullable final String attributeName) {
PsiAnnotationMemberValue attrValue = anno.findAttributeValue(attributeName);
Object constValue = JavaPsiFacade.getInstance(anno.getProject()).getConstantEvaluationHelper().computeConstantExpression(attrValue);
return constValue instanceof Number ? ((Number)constValue).longValue() : null;
}
@Nullable
public static String getDeclaredStringAttributeValue(@NotNull PsiAnnotation anno, @Nullable final String attributeName) {
PsiAnnotationMemberValue attrValue = anno.findDeclaredAttributeValue(attributeName);
@@ -61,6 +61,12 @@
<item name="java.lang.Comparable int compareTo(T) 0">
<annotation name="org.jetbrains.annotations.NotNull" />
</item>
<item name='java.lang.Enum int ordinal()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="java.lang.Integer.MAX_VALUE"/>
</annotation>
</item>
<item name='java.lang.Enum T valueOf(java.lang.Class&lt;T&gt;, java.lang.String)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -70,6 +76,24 @@
<item name='java.lang.Enum T valueOf(java.lang.Class&lt;T&gt;, java.lang.String) 1'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.lang.Integer int numberOfLeadingZeros(int)'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="32"/>
</annotation>
</item>
<item name='java.lang.Integer int numberOfTrailingZeros(int)'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="32"/>
</annotation>
</item>
<item name='java.lang.Integer int bitCount(int)'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="32"/>
</annotation>
</item>
<item name='java.lang.Integer java.lang.Integer valueOf(java.lang.String)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
@@ -91,6 +115,24 @@
<item name='java.lang.Long java.lang.Long valueOf(long)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.lang.Long int numberOfLeadingZeros(long)'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="64"/>
</annotation>
</item>
<item name='java.lang.Long int numberOfTrailingZeros(long)'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="64"/>
</annotation>
</item>
<item name='java.lang.Long int bitCount(long)'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="64"/>
</annotation>
</item>
<item name='java.lang.Object java.lang.Class&lt;?&gt; getClass()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
@@ -0,0 +1,163 @@
<root>
<item name='java.time.LocalDate int getDayOfMonth()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="1"/>
<val name="to" val="31"/>
</annotation>
</item>
<item name='java.time.LocalDate int getDayOfYear()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="1"/>
<val name="to" val="366"/>
</annotation>
</item>
<item name='java.time.LocalDate int getMonthValue()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="1"/>
<val name="to" val="12"/>
</annotation>
</item>
<item name='java.time.LocalDateTime int getDayOfMonth()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="1"/>
<val name="to" val="31"/>
</annotation>
</item>
<item name='java.time.LocalDateTime int getDayOfYear()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="1"/>
<val name="to" val="366"/>
</annotation>
</item>
<item name='java.time.LocalDateTime int getHour()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="23"/>
</annotation>
</item>
<item name='java.time.LocalDateTime int getMinute()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="59"/>
</annotation>
</item>
<item name='java.time.LocalDateTime int getMonthValue()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="1"/>
<val name="to" val="12"/>
</annotation>
</item>
<item name='java.time.LocalDateTime int getNano()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="999_999_999"/>
</annotation>
</item>
<item name='java.time.LocalDateTime int getSecond()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="59"/>
</annotation>
</item>
<item name='java.time.LocalDateTime int getYear()'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.time.LocalDateTime java.time.LocalDateTime from(java.time.temporal.TemporalAccessor)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.time.LocalDateTime java.time.LocalDateTime of(int, int, int, int, int)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.time.LocalDateTime java.time.LocalDateTime of(int, int, int, int, int, int)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.time.LocalDateTime java.time.LocalDateTime of(int, int, int, int, int, int, int)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.time.LocalDateTime java.time.LocalDateTime of(int, java.time.Month, int, int, int)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.time.LocalDateTime java.time.LocalDateTime of(int, java.time.Month, int, int, int, int)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.time.LocalDateTime java.time.LocalDateTime of(int, java.time.Month, int, int, int, int, int)'>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.time.LocalDateTime java.time.LocalDateTime parse(java.lang.CharSequence)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.time.LocalDateTime java.time.LocalDateTime parse(java.lang.CharSequence, java.time.format.DateTimeFormatter)'>
<annotation name='org.jetbrains.annotations.NotNull'/>
</item>
<item name='java.time.LocalTime int getHour()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="23"/>
</annotation>
</item>
<item name='java.time.LocalTime int getMinute()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="59"/>
</annotation>
</item>
<item name='java.time.LocalTime int getNano()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="999_999_999"/>
</annotation>
</item>
<item name='java.time.LocalTime int getSecond()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="0"/>
<val name="to" val="59"/>
</annotation>
</item>
<item name='java.time.Month int getValue()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="1"/>
<val name="to" val="12"/>
</annotation>
<annotation name='org.jetbrains.annotations.Contract'>
<val name="pure" val="true"/>
</annotation>
</item>
<item name='java.time.Month int length()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="28"/>
<val name="to" val="31"/>
</annotation>
</item>
<item name='java.time.Month int maxLength()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="29"/>
<val name="to" val="31"/>
</annotation>
</item>
<item name='java.time.Month int minLength()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="28"/>
<val name="to" val="31"/>
</annotation>
</item>
<item name='java.time.Year int length()'>
<annotation name='org.jetbrains.annotations.Range'>
<val name="from" val="365"/>
<val name="to" val="366"/>
</annotation>
</item>
</root>
@@ -0,0 +1,27 @@
/*
* Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
*/
package org.jetbrains.annotations;
import java.lang.annotation.*;
/**
* An annotation which allows to specify for integral type (byte, char, short, int, long) an allowed values range.
* <p>
* This is an experimental annotation which can be changed or removed without additional notice!
*/
@Documented
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.TYPE_USE})
@ApiStatus.Experimental
public @interface Range {
/**
* @return minimal allowed value (inclusive)
*/
long from();
/**
* @return maximal allowed value (inclusive)
*/
long to();
}