diff --git a/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java b/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java index c608b0d2a53..0d0dd126e2a 100644 --- a/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java +++ b/checker/src/main/java/org/checkerframework/checker/signature/SignatureAnnotatedTypeFactory.java @@ -84,6 +84,10 @@ public class SignatureAnnotatedTypeFactory extends BaseAnnotatedTypeFactory { protected final AnnotationMirror PRIMITIVE_TYPE = AnnotationBuilder.fromClass(elements, PrimitiveType.class); + /** The {@literal @}{@link Identifier} annotation. */ + protected final AnnotationMirror IDENTIFIER = + AnnotationBuilder.fromClass(elements, Identifier.class); + /** The {@link String#replace(char, char)} method. */ private final ExecutableElement replaceCharChar = TreeUtils.getMethod("java.lang.String", "replace", processingEnv, "char", "char"); @@ -209,9 +213,20 @@ public SignatureTreeAnnotator(AnnotatedTypeFactory atypeFactory) { @Override public Void visitBinary(BinaryTree tree, AnnotatedTypeMirror type) { + if (TreeUtils.isStringConcatenation(tree)) { - // This could be made more precise. - type.replaceAnnotation(SIGNATURE_UNKNOWN); + AnnotatedTypeMirror lType = getAnnotatedType(tree.getLeftOperand()); + AnnotatedTypeMirror rType = getAnnotatedType(tree.getRightOperand()); + + // An identifier can end, but not start, with digits + if (lType.getPrimaryAnnotation(Identifier.class) != null + && (rType.getPrimaryAnnotation(Identifier.class) != null + || TypesUtils.isIntegralNumericOrBoxed(rType.getUnderlyingType()))) { + type.replaceAnnotation(IDENTIFIER); + } else { + // This could be made more precise. + type.replaceAnnotation(SIGNATURE_UNKNOWN); + } } return null; // super.visitBinary(tree, type); } @@ -231,10 +246,10 @@ public Void visitCompoundAssignment(CompoundAssignmentTree tree, AnnotatedTypeMi * *
* {@literal @}InternalForm String internalForm = binaryName.replace('.', '/');
- * {@literal @}BinaryName String binaryName = internalForm.replace('/', '.');
+ * {@literal @}DotSeparatedIdentifiers String dsi = internalForm.replace('/', '.');
*
*
- * Class.getName and Class.getCanonicalName(): Cwhen called on a primitive type ,the return a
+ * Class.getName and Class.getCanonicalName(): when called on a primitive type, they return a
* {@link PrimitiveType}. When called on a non-array, non-nested, non-primitive type, they
* return a {@link BinaryName}:
*
@@ -276,7 +291,7 @@ public Void visitMethodInvocation(MethodInvocationTree tree, AnnotatedTypeMirror
type.replaceAnnotation(INTERNAL_FORM);
} else if ((oldChar == '/' && newChar == '.')
&& receiverType.getPrimaryAnnotation(InternalForm.class) != null) {
- type.replaceAnnotation(BINARY_NAME);
+ type.replaceAnnotation(DOT_SEPARATED_IDENTIFIERS);
}
} else {
boolean isClassGetName = TreeUtils.isMethodInvocation(tree, classGetName, processingEnv);
diff --git a/checker/tests/signature/Concatenation.java b/checker/tests/signature/Concatenation.java
new file mode 100644
index 00000000000..a8e673b1a6e
--- /dev/null
+++ b/checker/tests/signature/Concatenation.java
@@ -0,0 +1,8 @@
+import org.checkerframework.checker.signature.qual.*;
+
+public class Concatenation {
+
+ @Identifier String m(@Identifier String s, int i) {
+ return s + i;
+ }
+}
diff --git a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java
index 4a3a4af2cf1..094ded83315 100644
--- a/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java
+++ b/framework/src/main/java/org/checkerframework/common/value/ValueAnnotatedTypeFactory.java
@@ -641,7 +641,7 @@ public Void visitNewArray(NewArrayTree tree, AnnotatedTypeMirror mirror) {
/*package-private*/ AnnotationMirror convertSpecialIntRangeToStandardIntRange(
AnnotationMirror anm, TypeKind primitiveKind) {
long max = Long.MAX_VALUE;
- if (TypesUtils.isIntegralPrimitive(primitiveKind)) {
+ if (TypeKindUtils.isIntegral(primitiveKind)) {
Range maxRange = Range.create(primitiveKind);
max = maxRange.to;
}
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java
index 12d70478844..c53b6d44d98 100644
--- a/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypeKindUtils.java
@@ -32,6 +32,25 @@ public static boolean isIntegral(TypeKind typeKind) {
}
}
+ /**
+ * Return true if the argument is one of INT, SHORT, BYTE, LONG.
+ *
+ * @param typeKind the TypeKind to inspect
+ * @return true if typeKind is a primitive integral type kind, excluding CHAR which does not print
+ * as an integer
+ */
+ public static boolean isIntegralNumeric(TypeKind typeKind) {
+ switch (typeKind) {
+ case INT:
+ case SHORT:
+ case BYTE:
+ case LONG:
+ return true;
+ default:
+ return false;
+ }
+ }
+
/**
* Return true if the argument is one of FLOAT, DOUBLE.
*
diff --git a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java
index ccdc560e362..bd91c404e60 100644
--- a/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java
+++ b/javacutil/src/main/java/org/checkerframework/javacutil/TypesUtils.java
@@ -542,37 +542,40 @@ public static boolean isNumericBoxed(TypeMirror type) {
* @return whether the argument is an integral primitive type
*/
public static boolean isIntegralPrimitive(TypeMirror type) {
- return isIntegralPrimitive(type.getKind());
+ return TypeKindUtils.isIntegral(type.getKind());
+ }
+
+ /**
+ * Return true if the argument TypeMirror is a (possibly boxed) integral type.
+ *
+ * @param type the type to inspect
+ * @return true if type is an integral type
+ */
+ public static boolean isIntegralPrimitiveOrBoxed(TypeMirror type) {
+ TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type);
+ return kind != null && TypeKindUtils.isIntegral(kind);
}
/**
* Returns true iff the argument is an integral primitive type.
*
- * @param typeKind a type kind
+ * @param type a type
* @return whether the argument is an integral primitive type
*/
- public static boolean isIntegralPrimitive(TypeKind typeKind) {
- switch (typeKind) {
- case BYTE:
- case CHAR:
- case INT:
- case LONG:
- case SHORT:
- return true;
- default:
- return false;
- }
+ public static boolean isIntegralNumericPrimitive(TypeMirror type) {
+ return TypeKindUtils.isIntegralNumeric(type.getKind());
}
/**
- * Return true if the argument TypeMirror is a (possibly boxed) integral type.
+ * Return true if the argument TypeMirror is a (possibly boxed) integral type, excluding char and
+ * Character which do not print as numbers.
*
* @param type the type to inspect
- * @return true if type is an integral type
+ * @return true if type is an integral numeric type
*/
- public static boolean isIntegralPrimitiveOrBoxed(TypeMirror type) {
+ public static boolean isIntegralNumericOrBoxed(TypeMirror type) {
TypeKind kind = TypeKindUtils.primitiveOrBoxedToTypeKind(type);
- return kind != null && TypeKindUtils.isIntegral(kind);
+ return kind != null && TypeKindUtils.isIntegralNumeric(kind);
}
/**