IDEA-130652 Check contract return type against method signature

This commit is contained in:
peter
2014-10-01 20:09:10 +02:00
parent 374a7d1e75
commit eb332c2127
3 changed files with 38 additions and 31 deletions
@@ -78,13 +78,15 @@ class ContractInferenceInterpreter {
List<MethodContract> inferContracts() {
final boolean notNull = NullableNotNullManager.isNotNull(myMethod);
PsiTypeElement typeElement = myMethod.getReturnTypeElement();
final PsiType returnType = typeElement == null ? null : typeElement.getType();
return ContainerUtil.filter(doInferContracts(), new Condition<MethodContract>() {
@Override
public boolean value(MethodContract contract) {
if (notNull && contract.returnValue == NOT_NULL_VALUE) {
return false;
}
return true;
return ContractInspection.isReturnTypeCompatible(returnType, contract.returnValue);
}
});
}
@@ -98,31 +100,12 @@ class ContractInferenceInterpreter {
if (statements[0] instanceof PsiReturnStatement) {
List<MethodContract> result = handleDelegation(((PsiReturnStatement)statements[0]).getReturnValue(), false);
if (result != null) {
PsiTypeElement typeElement = myMethod.getReturnTypeElement();
final boolean returningObject = typeElement == null || !(typeElement.getType() instanceof PsiClassType);
return ContainerUtil.findAll(result, new Condition<MethodContract>() {
@Override
public boolean value(MethodContract contract) {
if ((contract.returnValue == NULL_VALUE || contract.returnValue == NOT_NULL_VALUE) && returningObject) {
return false;
}
if ((contract.returnValue == TRUE_VALUE || contract.returnValue == FALSE_VALUE) && !returningObject) {
return false;
}
return true;
}
});
return result;
}
}
else if (statements[0] instanceof PsiExpressionStatement && ((PsiExpressionStatement)statements[0]).getExpression() instanceof PsiMethodCallExpression) {
List<MethodContract> result = handleDelegation(((PsiExpressionStatement)statements[0]).getExpression(), false);
if (result != null) return ContainerUtil.findAll(result, new Condition<MethodContract>() {
@Override
public boolean value(MethodContract contract) {
return contract.returnValue == THROW_EXCEPTION || !textMatches(myMethod.getReturnTypeElement(), PsiKeyword.VOID);
}
});
if (result != null) return result;
}
}
@@ -385,15 +368,7 @@ class ContractInferenceInterpreter {
result.addAll(toContracts(states, THROW_EXCEPTION));
}
else if (statement instanceof PsiReturnStatement) {
List<MethodContract> contracts = visitExpression(states, ((PsiReturnStatement)statement).getReturnValue());
for (MethodContract contract : contracts) {
if ((contract.returnValue == TRUE_VALUE || contract.returnValue == FALSE_VALUE) &&
!textMatches(myMethod.getReturnTypeElement(), PsiKeyword.BOOLEAN)) {
continue;
}
result.add(contract);
}
result.addAll(visitExpression(states, ((PsiReturnStatement)statement).getReturnValue()));
}
else if (statement instanceof PsiAssertStatement) {
List<MethodContract> conditionResults = visitExpression(states, ((PsiAssertStatement)statement).getAssertCondition());
@@ -91,7 +91,30 @@ public class ContractInspection extends BaseJavaBatchLocalInspectionTool {
if (contract.arguments.length != paramCount) {
return "Method takes " + paramCount + " parameters, while contract clause number " + (i + 1) + " expects " + contract.arguments.length;
}
PsiType returnType = method.getReturnType();
if (returnType != null && !isReturnTypeCompatible(returnType, contract.returnValue)) {
return "Method returns " + returnType.getPresentableText() + " but the contract specifies " + contract.returnValue;
}
}
return null;
}
static boolean isReturnTypeCompatible(@Nullable PsiType returnType, @NotNull MethodContract.ValueConstraint returnValue) {
if (returnValue == MethodContract.ValueConstraint.ANY_VALUE || returnValue == MethodContract.ValueConstraint.THROW_EXCEPTION) {
return true;
}
if (PsiType.VOID.equals(returnType)) return false;
if (PsiType.BOOLEAN.equals(returnType)) {
return returnValue == MethodContract.ValueConstraint.TRUE_VALUE ||
returnValue == MethodContract.ValueConstraint.FALSE_VALUE;
}
if (returnType instanceof PsiClassType) {
return returnValue == MethodContract.ValueConstraint.NULL_VALUE ||
returnValue == MethodContract.ValueConstraint.NOT_NULL_VALUE;
}
return true;
}
}
@@ -11,4 +11,13 @@ class Foo {
@Contract(pure=<warning descr="Pure methods must return something, void is not allowed as a return type">true</warning>)
void voidPureMethod() {}
@Contract(<warning descr="Method returns void but the contract specifies null">"->null"</warning>)
public native void throwMe();
@Contract(<warning descr="Method returns boolean but the contract specifies null">"->null"</warning>)
public native boolean wrongReturnType();
@Contract(<warning descr="Method returns String but the contract specifies true">"->true"</warning>)
public native String wrongReturnType2();
}