org.jetbrains.plugins.groovy.codeInspection.type.GroovyTypeCheckVisitor Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of groovy-psi Show documentation
Show all versions of groovy-psi Show documentation
A packaging of the IntelliJ Community Edition groovy-psi library.
This is release number 1 of trunk branch 142.
The newest version!
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.groovy.codeInspection.type;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemHighlightType;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.util.Condition;
import com.intellij.psi.*;
import com.intellij.psi.impl.PsiSubstitutorImpl;
import com.intellij.psi.tree.IElementType;
import com.intellij.psi.util.TypeConversionUtil;
import com.intellij.util.Function;
import com.intellij.util.containers.ContainerUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.PropertyKey;
import org.jetbrains.plugins.groovy.GroovyBundle;
import org.jetbrains.plugins.groovy.annotator.GrHighlightUtil;
import org.jetbrains.plugins.groovy.codeInspection.BaseInspectionVisitor;
import org.jetbrains.plugins.groovy.codeInspection.GroovyInspectionBundle;
import org.jetbrains.plugins.groovy.codeInspection.assignment.*;
import org.jetbrains.plugins.groovy.config.GroovyConfigUtils;
import org.jetbrains.plugins.groovy.extensions.GroovyNamedArgumentProvider;
import org.jetbrains.plugins.groovy.extensions.NamedArgumentDescriptor;
import org.jetbrains.plugins.groovy.lang.lexer.GroovyTokenTypes;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElement;
import org.jetbrains.plugins.groovy.lang.psi.GroovyPsiElementFactory;
import org.jetbrains.plugins.groovy.lang.psi.api.GroovyResolveResult;
import org.jetbrains.plugins.groovy.lang.psi.api.auxiliary.GrListOrMap;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrConstructorInvocation;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrField;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariable;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.GrVariableDeclaration;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrArgumentList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.arguments.GrNamedArgument;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.blocks.GrClosableBlock;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrReturnStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.branch.GrThrowStatement;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.clauses.GrForInClause;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.*;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.literals.GrString;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrIndexProperty;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.expressions.path.GrMethodCallExpression;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameter;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.params.GrParameterList;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrBuilderMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrEnumConstant;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrGdkMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.statements.typedef.members.GrMethod;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrCodeReferenceElement;
import org.jetbrains.plugins.groovy.lang.psi.api.types.GrTypeElement;
import org.jetbrains.plugins.groovy.lang.psi.impl.GrClosureType;
import org.jetbrains.plugins.groovy.lang.psi.impl.PsiImplUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.signatures.GrClosureSignatureUtil;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.ConversionResult;
import org.jetbrains.plugins.groovy.lang.psi.impl.statements.expressions.TypesUtil;
import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.ClosureParameterEnhancer;
import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.ClosureParamsEnhancer;
import org.jetbrains.plugins.groovy.lang.psi.typeEnhancers.GrTypeConverter.ApplicableTo;
import org.jetbrains.plugins.groovy.lang.psi.util.*;
import org.jetbrains.plugins.groovy.lang.resolve.ResolveUtil;
import java.util.List;
import java.util.Map;
import static com.intellij.psi.util.PsiUtil.extractIterableTypeParameter;
import static org.jetbrains.plugins.groovy.codeInspection.type.GroovyTypeCheckVisitorHelper.*;
public class GroovyTypeCheckVisitor extends BaseInspectionVisitor {
private static final Logger LOG = Logger.getInstance(GroovyAssignabilityCheckInspection.class);
private boolean checkCallApplicability(@Nullable PsiType type, boolean checkUnknownArgs, @NotNull CallInfo info) {
PsiType[] argumentTypes = info.getArgumentTypes();
GrExpression invoked = info.getInvokedExpression();
if (invoked == null) return true;
if (type instanceof GrClosureType) {
if (argumentTypes == null) return true;
GrClosureSignatureUtil.ApplicabilityResult result =
PsiUtil.isApplicableConcrete(argumentTypes, (GrClosureType)type, info.getCall());
switch (result) {
case inapplicable:
registerCannotApplyError(invoked.getText(), info);
return false;
case canBeApplicable:
if (checkUnknownArgs) {
highlightUnknownArgs(info);
}
return !checkUnknownArgs;
default:
return true;
}
}
else if (type != null) {
final GroovyResolveResult[] calls = ResolveUtil.getMethodCandidates(type, "call", invoked, argumentTypes);
for (GroovyResolveResult result : calls) {
PsiElement resolved = result.getElement();
if (resolved instanceof PsiMethod && !result.isInvokedOnProperty()) {
if (!checkMethodApplicability(result, checkUnknownArgs, info)) return false;
}
else if (resolved instanceof PsiField) {
if (!checkCallApplicability(((PsiField)resolved).getType(), checkUnknownArgs && calls.length == 1, info)) return false;
}
}
if (calls.length == 0 && !(invoked instanceof GrString)) {
registerCannotApplyError(invoked.getText(), info);
}
return true;
}
return true;
}
private boolean checkCannotInferArgumentTypes(@NotNull CallInfo info) {
if (info.getArgumentTypes() != null) {
return true;
}
else {
highlightUnknownArgs(info);
return false;
}
}
private boolean checkConstructorApplicability(@NotNull GroovyResolveResult constructorResolveResult,
@NotNull CallInfo info,
boolean checkUnknownArgs) {
final PsiElement element = constructorResolveResult.getElement();
LOG.assertTrue(element instanceof PsiMethod && ((PsiMethod)element).isConstructor(), element);
final PsiMethod constructor = (PsiMethod)element;
final GrArgumentList argList = info.getArgumentList();
if (argList != null) {
final GrExpression[] exprArgs = argList.getExpressionArguments();
if (exprArgs.length == 0 && !PsiUtil.isConstructorHasRequiredParameters(constructor)) return true;
}
PsiType[] types = info.getArgumentTypes();
PsiClass containingClass = constructor.getContainingClass();
if (types != null && containingClass != null) {
final PsiType[] newTypes = GrInnerClassConstructorUtil.addEnclosingArgIfNeeded(types, info.getCall(), containingClass);
if (newTypes.length != types.length) {
return checkMethodApplicability(constructorResolveResult, checkUnknownArgs, new DelegatingCallInfo(info) {
@Nullable
@Override
public PsiType[] getArgumentTypes() {
return newTypes;
}
});
}
}
return checkMethodApplicability(constructorResolveResult, checkUnknownArgs, info);
}
private void processConstructorCall(@NotNull ConstructorCallInfo info) {
if (hasErrorElements(info.getArgumentList())) return;
if (!checkCannotInferArgumentTypes(info)) return;
final GroovyResolveResult constructorResolveResult = info.advancedResolve();
final PsiElement constructor = constructorResolveResult.getElement();
if (constructor != null) {
if (!checkConstructorApplicability(constructorResolveResult, info, true)) return;
}
else {
final GroovyResolveResult[] results = info.multiResolve();
if (results.length > 0) {
for (GroovyResolveResult result : results) {
PsiElement resolved = result.getElement();
if (resolved instanceof PsiMethod) {
if (!checkConstructorApplicability(result, info, false)) return;
}
}
registerError(
info.getElementToHighlight(),
GroovyBundle.message("constructor.call.is.ambiguous"),
null,
ProblemHighlightType.GENERIC_ERROR
);
}
else {
final GrExpression[] expressionArguments = info.getExpressionArguments();
final boolean hasClosureArgs = info.getClosureArguments().length > 0;
final boolean hasNamedArgs = info.getNamedArguments().length > 0;
if (hasClosureArgs ||
hasNamedArgs && expressionArguments.length > 0 ||
!hasNamedArgs && expressionArguments.length > 0 && !isOnlyOneMapParam(expressionArguments)) {
final GroovyResolveResult[] resolveResults = info.multiResolveClass();
if (resolveResults.length == 1) {
final PsiElement element = resolveResults[0].getElement();
if (element instanceof PsiClass) {
registerError(
info.getElementToHighlight(),
GroovyBundle.message("cannot.apply.default.constructor", ((PsiClass)element).getName()),
null,
ProblemHighlightType.GENERIC_ERROR
);
return;
}
}
}
}
}
checkNamedArgumentsType(info);
}
private boolean checkForImplicitEnumAssigning(@Nullable PsiType expectedType,
@NotNull GrExpression expression,
@NotNull GroovyPsiElement elementToHighlight) {
if (!(expectedType instanceof PsiClassType)) return false;
if (!GroovyConfigUtils.getInstance().isVersionAtLeast(elementToHighlight, GroovyConfigUtils.GROOVY1_8)) return false;
final PsiClass resolved = ((PsiClassType)expectedType).resolve();
if (resolved == null || !resolved.isEnum()) return false;
final PsiType type = expression.getType();
if (type == null) return false;
if (!type.equalsToText(GroovyCommonClassNames.GROOVY_LANG_GSTRING) &&
!type.equalsToText(CommonClassNames.JAVA_LANG_STRING)) {
return false;
}
final Object result = GroovyConstantExpressionEvaluator.evaluate(expression);
if (result == null || !(result instanceof String)) {
registerError(
elementToHighlight,
ProblemHighlightType.WEAK_WARNING,
GroovyBundle.message("cannot.assign.string.to.enum.0", expectedType.getPresentableText())
);
}
else {
final PsiField field = resolved.findFieldByName((String)result, true);
if (!(field instanceof PsiEnumConstant)) {
registerError(
elementToHighlight,
ProblemHighlightType.GENERIC_ERROR_OR_WARNING,
GroovyBundle.message("cannot.find.enum.constant.0.in.enum.1", result, expectedType.getPresentableText())
);
}
}
return true;
}
private void checkIndexProperty(@NotNull CallInfo info) {
if (hasErrorElements(info.getArgumentList())) return;
if (!checkCannotInferArgumentTypes(info)) return;
final PsiType type = info.getQualifierInstanceType();
final PsiType[] types = info.getArgumentTypes();
if (checkSimpleArrayAccess(info, type, types)) return;
final GroovyResolveResult[] results = info.multiResolve();
final GroovyResolveResult resolveResult = info.advancedResolve();
if (resolveResult.getElement() != null) {
PsiElement resolved = resolveResult.getElement();
if (resolved instanceof PsiMethod && !resolveResult.isInvokedOnProperty()) {
checkMethodApplicability(resolveResult, true, info);
}
else if (resolved instanceof GrField) {
checkCallApplicability(((GrField)resolved).getTypeGroovy(), true, info);
}
else if (resolved instanceof PsiField) {
checkCallApplicability(((PsiField)resolved).getType(), true, info);
}
}
else if (results.length > 0) {
for (GroovyResolveResult result : results) {
PsiElement resolved = result.getElement();
if (resolved instanceof PsiMethod && !result.isInvokedOnProperty()) {
if (!checkMethodApplicability(result, false, info)) return;
}
else if (resolved instanceof GrField) {
if (!checkCallApplicability(((GrField)resolved).getTypeGroovy(), false, info)) return;
}
else if (resolved instanceof PsiField) {
if (!checkCallApplicability(((PsiField)resolved).getType(), false, info)) return;
}
}
registerError(
info.getElementToHighlight(),
ProblemHighlightType.GENERIC_ERROR,
GroovyBundle.message("method.call.is.ambiguous")
);
}
else {
final String typesString = buildArgTypesList(types);
registerError(
info.getElementToHighlight(),
ProblemHighlightType.GENERIC_ERROR,
GroovyBundle.message("cannot.find.operator.overload.method", typesString)
);
}
}
private boolean checkMethodApplicability(@NotNull final GroovyResolveResult methodResolveResult,
boolean checkUnknownArgs,
@NotNull final CallInfo info) {
final PsiElement element = methodResolveResult.getElement();
if (!(element instanceof PsiMethod)) return true;
if (element instanceof GrBuilderMethod) return true;
final PsiMethod method = (PsiMethod)element;
if ("call".equals(method.getName()) && info.getInvokedExpression() instanceof GrReferenceExpression) {
final GrExpression qualifierExpression = ((GrReferenceExpression)info.getInvokedExpression()).getQualifierExpression();
if (qualifierExpression != null) {
final PsiType type = qualifierExpression.getType();
if (type instanceof GrClosureType) {
GrClosureSignatureUtil.ApplicabilityResult result =
PsiUtil.isApplicableConcrete(info.getArgumentTypes(), (GrClosureType)type, info.getInvokedExpression());
switch (result) {
case inapplicable:
highlightInapplicableMethodUsage(methodResolveResult, info, method);
return false;
case canBeApplicable://q(1,2)
if (checkUnknownArgs) {
highlightUnknownArgs(info);
}
return !checkUnknownArgs;
default:
return true;
}
}
}
}
if (method instanceof GrGdkMethod && info.getInvokedExpression() instanceof GrReferenceExpression) {
final GrReferenceExpression invoked = (GrReferenceExpression)info.getInvokedExpression();
final GrExpression qualifier = PsiImplUtil.getRuntimeQualifier(invoked);
if (qualifier == null && method.getName().equals("call")) {
GroovyPsiElementFactory factory = GroovyPsiElementFactory.getInstance(invoked.getProject());
final GrReferenceExpression callRef = factory.createReferenceExpressionFromText("qualifier.call", invoked);
callRef.setQualifier(invoked);
return checkMethodApplicability(methodResolveResult, checkUnknownArgs, new DelegatingCallInfo(info) {
@Nullable
@Override
public GrExpression getInvokedExpression() {
return callRef;
}
@NotNull
@Override
public GroovyResolveResult advancedResolve() {
return methodResolveResult;
}
@NotNull
@Override
public GroovyResolveResult[] multiResolve() {
return new GroovyResolveResult[]{methodResolveResult};
}
@Nullable
@Override
public PsiType getQualifierInstanceType() {
return info.getInvokedExpression().getType();
}
});
}
final PsiMethod staticMethod = ((GrGdkMethod)method).getStaticMethod();
final PsiType qualifierType = info.getQualifierInstanceType();
//check methods processed by @Category(ClassWhichProcessMethod) annotation
if (qualifierType != null &&
!GdkMethodUtil.isCategoryMethod(staticMethod, qualifierType, qualifier, methodResolveResult.getSubstitutor()) &&
!checkCategoryQualifier(invoked, qualifier, staticMethod, methodResolveResult.getSubstitutor())) {
registerError(
info.getHighlightElementForCategoryQualifier(),
ProblemHighlightType.GENERIC_ERROR,
GroovyInspectionBundle.message(
"category.method.0.cannot.be.applied.to.1",
method.getName(),
qualifierType.getCanonicalText()
)
);
return false;
}
}
if (info.getArgumentTypes() == null) return true;
GrClosureSignatureUtil.ApplicabilityResult applicable =
PsiUtil.isApplicableConcrete(info.getArgumentTypes(), method, methodResolveResult.getSubstitutor(), info.getCall(), false);
switch (applicable) {
case inapplicable:
highlightInapplicableMethodUsage(methodResolveResult, info, method);
return false;
case canBeApplicable:
if (checkUnknownArgs) {
highlightUnknownArgs(info);
}
return !checkUnknownArgs;
default:
return true;
}
}
private void checkMethodCall(@NotNull CallInfo info) {
if (hasErrorElements(info.getArgumentList())) return;
if (info.getInvokedExpression() instanceof GrReferenceExpression) {
final GrReferenceExpression referenceExpression = (GrReferenceExpression)info.getInvokedExpression();
GroovyResolveResult resolveResult = info.advancedResolve();
GroovyResolveResult[] results = info.multiResolve();
PsiElement resolved = resolveResult.getElement();
if (resolved == null) {
GrExpression qualifier = referenceExpression.getQualifierExpression();
if (qualifier == null && GrHighlightUtil.isDeclarationAssignment(referenceExpression)) return;
}
if (!checkCannotInferArgumentTypes(info)) return;
if (resolved != null) {
if (resolved instanceof PsiMethod && !resolveResult.isInvokedOnProperty()) {
checkMethodApplicability(resolveResult, true, info);
}
else {
checkCallApplicability(referenceExpression.getType(), true, info);
}
}
else if (results.length > 0) {
for (GroovyResolveResult result : results) {
PsiElement current = result.getElement();
if (current instanceof PsiMethod && !result.isInvokedOnProperty()) {
if (!checkMethodApplicability(result, false, info)) return;
}
else {
if (!checkCallApplicability(referenceExpression.getType(), false, info)) return;
}
}
registerError(info.getElementToHighlight(), GroovyBundle.message("method.call.is.ambiguous"));
}
}
else if (info.getInvokedExpression() != null) { //it checks in visitRefExpr(...)
final PsiType type = info.getInvokedExpression().getType();
checkCallApplicability(type, true, info);
}
checkNamedArgumentsType(info);
}
private void checkNamedArgumentsType(@NotNull CallInfo info) {
GroovyPsiElement rawCall = info.getCall();
if (!(rawCall instanceof GrCall)) return;
GrCall call = (GrCall)rawCall;
GrNamedArgument[] namedArguments = PsiUtil.getFirstMapNamedArguments(call);
if (namedArguments.length == 0) return;
Map map = GroovyNamedArgumentProvider.getNamedArgumentsFromAllProviders(call, null, false);
if (map == null) return;
for (GrNamedArgument namedArgument : namedArguments) {
String labelName = namedArgument.getLabelName();
NamedArgumentDescriptor descriptor = map.get(labelName);
if (descriptor == null) continue;
GrExpression namedArgumentExpression = namedArgument.getExpression();
if (namedArgumentExpression == null) continue;
if (getTupleInitializer(namedArgumentExpression) != null) continue;
if (PsiUtil.isRawClassMemberAccess(namedArgumentExpression)) continue;
PsiType expressionType = TypesUtil.boxPrimitiveType(namedArgumentExpression.getType(), call.getManager(), call.getResolveScope());
if (expressionType == null) continue;
if (!descriptor.checkType(expressionType, call)) {
registerError(
namedArgumentExpression,
ProblemHighlightType.GENERIC_ERROR,
"Type of argument '" + labelName + "' can not be '" + expressionType.getPresentableText() + "'"
);
}
}
}
private void checkOperator(@NotNull CallInfo info) {
if (hasErrorElements(info.getCall())) return;
if (isSpockTimesOperator(info.getCall())) return;
GroovyResolveResult[] results = info.multiResolve();
GroovyResolveResult resolveResult = info.advancedResolve();
if (isOperatorWithSimpleTypes(info.getCall(), resolveResult)) return;
if (!checkCannotInferArgumentTypes(info)) return;
if (resolveResult.getElement() != null) {
checkMethodApplicability(resolveResult, true, info);
}
else if (results.length > 0) {
for (GroovyResolveResult result : results) {
if (!checkMethodApplicability(result, false, info)) return;
}
registerError(
info.getElementToHighlight(),
ProblemHighlightType.GENERIC_ERROR,
GroovyBundle.message("method.call.is.ambiguous")
);
}
}
private void highlightInapplicableMethodUsage(@NotNull GroovyResolveResult methodResolveResult,
@NotNull CallInfo info,
@NotNull PsiMethod method) {
final PsiClass containingClass =
method instanceof GrGdkMethod ? ((GrGdkMethod)method).getStaticMethod().getContainingClass() : method.getContainingClass();
PsiType[] argumentTypes = info.getArgumentTypes();
if (containingClass == null) {
registerCannotApplyError(method.getName(), info);
return;
}
final String typesString = buildArgTypesList(argumentTypes);
final PsiElementFactory factory = JavaPsiFacade.getElementFactory(method.getProject());
final PsiClassType containingType = factory.createType(containingClass, methodResolveResult.getSubstitutor());
final String canonicalText = containingType.getInternalCanonicalText();
String message =
method.isConstructor() ? GroovyBundle.message("cannot.apply.constructor", method.getName(), canonicalText, typesString)
: GroovyBundle.message("cannot.apply.method1", method.getName(), canonicalText, typesString);
registerError(
info.getElementToHighlight(),
message,
genCastFixes(GrClosureSignatureUtil.createSignature(methodResolveResult), argumentTypes, info.getArgumentList()),
ProblemHighlightType.GENERIC_ERROR
);
}
private void highlightUnknownArgs(@NotNull CallInfo info) {
registerError(
info.getElementToHighlight(),
GroovyBundle.message("cannot.infer.argument.types"),
LocalQuickFix.EMPTY_ARRAY,
ProblemHighlightType.WEAK_WARNING
);
}
private void processAssignment(@NotNull PsiType expectedType, @NotNull GrExpression expression, @NotNull PsiElement toHighlight) {
processAssignment(expectedType, expression, toHighlight, "cannot.assign", toHighlight, ApplicableTo.ASSIGNMENT);
}
private void processAssignment(@NotNull PsiType expectedType,
@NotNull GrExpression expression,
@NotNull PsiElement toHighlight,
@NotNull @PropertyKey(resourceBundle = GroovyBundle.BUNDLE) String messageKey,
@NotNull PsiElement context,
@NotNull ApplicableTo position) {
{ // check if current assignment is constructor call
final GrListOrMap initializer = getTupleInitializer(expression);
if (initializer != null) {
processConstructorCall(new GrListOrMapInfo(initializer));
return;
}
}
if (PsiUtil.isRawClassMemberAccess(expression)) return;
if (checkForImplicitEnumAssigning(expectedType, expression, expression)) return;
final PsiType actualType = expression.getType();
if (actualType == null) return;
final ConversionResult result = TypesUtil.canAssign(expectedType, actualType, context, position);
if (result == ConversionResult.OK) return;
final List fixes = ContainerUtil.newArrayList();
{
fixes.add(new GrCastFix(expectedType, expression));
final String varName = getLValueVarName(toHighlight);
if (varName != null) {
fixes.add(new GrChangeVariableType(actualType, varName));
}
}
final String message = GroovyBundle.message(messageKey, actualType.getPresentableText(), expectedType.getPresentableText());
registerError(
toHighlight,
message,
fixes.toArray(new LocalQuickFix[fixes.size()]),
result == ConversionResult.ERROR ? ProblemHighlightType.GENERIC_ERROR : ProblemHighlightType.GENERIC_ERROR_OR_WARNING
);
}
private void processAssignment(@NotNull PsiType lType,
@Nullable PsiType rType,
@NotNull GroovyPsiElement context,
@NotNull PsiElement elementToHighlight) {
if (rType == null) return;
final ConversionResult result = TypesUtil.canAssign(lType, rType, context, ApplicableTo.ASSIGNMENT);
processResult(result, elementToHighlight, "cannot.assign", lType, rType);
}
protected void processAssignmentWithinMultipleAssignment(@NotNull GrExpression lhs,
@NotNull GrExpression rhs,
@NotNull GrExpression context) {
final PsiType targetType = lhs.getType();
final PsiType actualType = rhs.getType();
if (targetType == null || actualType == null) return;
final ConversionResult result = TypesUtil.canAssignWithinMultipleAssignment(targetType, actualType, context);
if (result == ConversionResult.OK) return;
registerError(
rhs,
GroovyBundle.message("cannot.assign", actualType.getPresentableText(), targetType.getPresentableText()),
LocalQuickFix.EMPTY_ARRAY,
result == ConversionResult.ERROR ? ProblemHighlightType.GENERIC_ERROR : ProblemHighlightType.GENERIC_ERROR_OR_WARNING
);
}
protected void processTupleAssignment(@NotNull GrTupleExpression tupleExpression,
@NotNull GrExpression initializer) {
GrExpression[] lValues = tupleExpression.getExpressions();
if (initializer instanceof GrListOrMap) {
GrExpression[] initializers = ((GrListOrMap)initializer).getInitializers();
for (int i = 0; i < lValues.length; i++) {
GrExpression lValue = lValues[i];
if (initializers.length <= i) break;
GrExpression rValue = initializers[i];
processAssignmentWithinMultipleAssignment(lValue, rValue, tupleExpression);
}
}
else {
PsiType type = initializer.getType();
PsiType rType = extractIterableTypeParameter(type, false);
for (GrExpression lValue : lValues) {
PsiType lType = lValue.getNominalType();
// For assignments with spread dot
if (PsiImplUtil.isSpreadAssignment(lValue)) {
final PsiType argType = extractIterableTypeParameter(lType, false);
if (argType != null && rType != null) {
processAssignment(argType, rType, tupleExpression, getExpressionPartToHighlight(lValue));
}
return;
}
if (lValue instanceof GrReferenceExpression && ((GrReferenceExpression)lValue).resolve() instanceof GrReferenceExpression) {
//lvalue is not-declared variable
return;
}
if (lType != null && rType != null) {
processAssignment(lType, rType, tupleExpression, getExpressionPartToHighlight(lValue));
}
}
}
}
private void processResult(@NotNull ConversionResult result,
@NotNull PsiElement elementToHighlight,
@NotNull @PropertyKey(resourceBundle = GroovyBundle.BUNDLE) String messageKey,
@NotNull PsiType lType,
@NotNull PsiType rType) {
if (result == ConversionResult.OK) return;
registerError(
elementToHighlight,
GroovyBundle.message(messageKey, rType.getPresentableText(), lType.getPresentableText()),
LocalQuickFix.EMPTY_ARRAY,
result == ConversionResult.ERROR ? ProblemHighlightType.GENERIC_ERROR : ProblemHighlightType.GENERIC_ERROR_OR_WARNING
);
}
protected void processReturnValue(@NotNull GrExpression expression,
@NotNull PsiElement context,
@NotNull PsiElement elementToHighlight) {
if (getTupleInitializer(expression) != null) return;
final PsiType returnType = PsiImplUtil.inferReturnType(expression);
if (returnType == null || returnType == PsiType.VOID) return;
processAssignment(returnType, expression, elementToHighlight, "cannot.return.type", context, ApplicableTo.RETURN_VALUE);
}
private void registerCannotApplyError(@NotNull String invokedText, @NotNull CallInfo info) {
if (info.getArgumentTypes() == null) return;
final String typesString = buildArgTypesList(info.getArgumentTypes());
registerError(
info.getElementToHighlight(),
ProblemHighlightType.GENERIC_ERROR,
GroovyBundle.message("cannot.apply.method.or.closure", invokedText, typesString)
);
}
@Override
protected void registerError(@NotNull PsiElement location,
@NotNull String description,
@Nullable LocalQuickFix[] fixes,
ProblemHighlightType highlightType) {
if (PsiUtil.isCompileStatic(location)) {
// filter all errors here, error will be highlighted by annotator
if (highlightType != ProblemHighlightType.GENERIC_ERROR) {
super.registerError(location, description, fixes, highlightType);
}
}
else {
if (highlightType == ProblemHighlightType.GENERIC_ERROR) {
// if this visitor works within non-static context we will highlight all errors as warnings
super.registerError(location, description, fixes, ProblemHighlightType.GENERIC_ERROR_OR_WARNING);
}
else {
// if this visitor works within static context errors will be highlighted as errors by annotator, warnings will be highlighted as warnings here
super.registerError(location, description, fixes, highlightType);
}
}
}
@Override
public void visitEnumConstant(GrEnumConstant enumConstant) {
super.visitEnumConstant(enumConstant);
GrEnumConstantInfo info = new GrEnumConstantInfo(enumConstant);
processConstructorCall(info);
checkNamedArgumentsType(info);
}
@Override
public void visitReturnStatement(GrReturnStatement returnStatement) {
super.visitReturnStatement(returnStatement);
final GrExpression value = returnStatement.getReturnValue();
if (value != null) {
processReturnValue(value, returnStatement, returnStatement.getReturnWord());
}
}
@Override
public void visitThrowStatement(GrThrowStatement throwStatement) {
super.visitThrowStatement(throwStatement);
final GrExpression exception = throwStatement.getException();
if (exception == null) return;
final PsiElement throwWord = throwStatement.getFirstChild();
processAssignment(
PsiType.getJavaLangThrowable(
throwStatement.getManager(),
throwStatement.getResolveScope()
),
exception,
throwWord
);
}
@Override
public void visitExpression(GrExpression expression) {
super.visitExpression(expression);
if (isImplicitReturnStatement(expression)) {
processReturnValue(expression, expression, expression);
}
}
@Override
public void visitMethodCallExpression(GrMethodCallExpression methodCallExpression) {
super.visitMethodCallExpression(methodCallExpression);
checkMethodCall(new GrMethodCallInfo(methodCallExpression));
}
@Override
public void visitNewExpression(GrNewExpression newExpression) {
super.visitNewExpression(newExpression);
if (newExpression.getArrayCount() > 0) return;
GrCodeReferenceElement refElement = newExpression.getReferenceElement();
if (refElement == null) return;
GrNewExpressionInfo info = new GrNewExpressionInfo(newExpression);
processConstructorCall(info);
}
@Override
public void visitApplicationStatement(GrApplicationStatement applicationStatement) {
super.visitApplicationStatement(applicationStatement);
checkMethodCall(new GrMethodCallInfo(applicationStatement));
}
@Override
public void visitAssignmentExpression(GrAssignmentExpression assignment) {
super.visitAssignmentExpression(assignment);
final GrExpression lValue = assignment.getLValue();
if (lValue instanceof GrIndexProperty) return;
if (!PsiUtil.mightBeLValue(lValue)) return;
final IElementType opToken = assignment.getOperationTokenType();
if (opToken != GroovyTokenTypes.mASSIGN) return;
final GrExpression rValue = assignment.getRValue();
if (rValue == null) return;
if (lValue instanceof GrReferenceExpression && ((GrReferenceExpression)lValue).resolve() instanceof GrReferenceExpression) {
//lvalue is not-declared variable
return;
}
if (lValue instanceof GrTupleExpression) {
processTupleAssignment(((GrTupleExpression)lValue), rValue);
}
else {
PsiType lValueNominalType = lValue.getNominalType();
final PsiType targetType = PsiImplUtil.isSpreadAssignment(lValue) ? extractIterableTypeParameter(lValueNominalType, false)
: lValueNominalType;
if (targetType != null) {
processAssignment(targetType, rValue, lValue, "cannot.assign", assignment, ApplicableTo.ASSIGNMENT);
}
}
}
@Override
public void visitBinaryExpression(GrBinaryExpression binary) {
super.visitBinaryExpression(binary);
checkOperator(new GrBinaryExprInfo(binary));
}
@Override
public void visitCastExpression(GrTypeCastExpression expression) {
super.visitCastExpression(expression);
final GrExpression operand = expression.getOperand();
if (operand == null) return;
final PsiType actualType = operand.getType();
if (actualType == null) return;
if (expression.getCastTypeElement() == null) return;
final PsiType expectedType = expression.getCastTypeElement().getType();
final ConversionResult result = TypesUtil.canCast(expectedType, actualType, expression);
if (result == ConversionResult.OK) return;
final ProblemHighlightType highlightType = result == ConversionResult.ERROR
? ProblemHighlightType.GENERIC_ERROR
: ProblemHighlightType.GENERIC_ERROR_OR_WARNING;
final String message = GroovyBundle.message(
"cannot.cast",
actualType.getPresentableText(),
expectedType.getPresentableText()
);
registerError(
expression,
highlightType,
message
);
}
@Override
public void visitIndexProperty(GrIndexProperty expression) {
super.visitIndexProperty(expression);
checkIndexProperty(new GrIndexPropertyInfo(expression));
}
/**
* Handles method default values.
*/
@Override
public void visitMethod(GrMethod method) {
super.visitMethod(method);
final PsiTypeParameter[] parameters = method.getTypeParameters();
final Map map = ContainerUtil.newHashMap();
for (PsiTypeParameter parameter : parameters) {
final PsiClassType[] types = parameter.getSuperTypes();
final PsiType bound = PsiIntersectionType.createIntersection(types);
final PsiWildcardType wildcardType = PsiWildcardType.createExtends(method.getManager(), bound);
map.put(parameter, wildcardType);
}
final PsiSubstitutor substitutor = PsiSubstitutorImpl.createSubstitutor(map);
for (GrParameter parameter : method.getParameterList().getParameters()) {
final GrExpression initializer = parameter.getInitializerGroovy();
if (initializer == null) continue;
final PsiType targetType = parameter.getType();
processAssignment(
substitutor.substitute(targetType),
initializer,
parameter.getNameIdentifierGroovy(),
"cannot.assign",
method,
ApplicableTo.ASSIGNMENT
);
}
}
@Override
public void visitConstructorInvocation(GrConstructorInvocation invocation) {
super.visitConstructorInvocation(invocation);
GrConstructorInvocationInfo info = new GrConstructorInvocationInfo(invocation);
processConstructorCall(info);
checkNamedArgumentsType(info);
}
@Override
public void visitParameterList(final GrParameterList parameterList) {
super.visitParameterList(parameterList);
PsiElement parent = parameterList.getParent();
if (!(parent instanceof GrClosableBlock)) return;
GrParameter[] parameters = parameterList.getParameters();
if (parameters.length > 0) {
List signatures = ClosureParamsEnhancer.findFittingSignatures((GrClosableBlock)parent);
final List paramTypes = ContainerUtil.map(parameters, new Function() {
@Override
public PsiType fun(GrParameter parameter) {
return parameter.getType();
}
});
if (signatures.size() > 1) {
final PsiType[] fittingSignature = ContainerUtil.find(signatures, new Condition() {
@Override
public boolean value(PsiType[] types) {
for (int i = 0; i < types.length; i++) {
if (!typesAreEqual(types[i], paramTypes.get(i), parameterList)) {
return false;
}
}
return true;
}
});
if (fittingSignature == null) {
registerError(
parameterList,
GroovyInspectionBundle.message("no.applicable.signature.found"),
null,
ProblemHighlightType.GENERIC_ERROR
);
}
}
else if (signatures.size() == 1) {
PsiType[] types = signatures.get(0);
for (int i = 0; i < types.length; i++) {
GrTypeElement typeElement = parameters[i].getTypeElementGroovy();
if (typeElement == null) continue;
PsiType expected = types[i];
PsiType actual = paramTypes.get(i);
if (!typesAreEqual(expected, actual, parameterList)) {
registerError(
typeElement,
GroovyInspectionBundle.message("expected.type.0", expected.getPresentableText()),
null,
ProblemHighlightType.GENERIC_ERROR
);
}
}
}
}
}
@Override
public void visitForInClause(GrForInClause forInClause) {
super.visitForInClause(forInClause);
final GrVariable variable = forInClause.getDeclaredVariable();
final GrExpression iterated = forInClause.getIteratedExpression();
if (variable == null || iterated == null) return;
final PsiType iteratedType = ClosureParameterEnhancer.findTypeForIteration(iterated, forInClause);
if (iteratedType == null) return;
final PsiType targetType = variable.getType();
processAssignment(targetType, iteratedType, forInClause, variable.getNameIdentifierGroovy());
}
@Override
public void visitVariable(GrVariable variable) {
super.visitVariable(variable);
final PsiType varType = variable.getType();
final PsiElement parent = variable.getParent();
if (variable instanceof GrParameter && ((GrParameter)variable).getDeclarationScope() instanceof GrMethod ||
parent instanceof GrForInClause) {
return;
}
else if (parent instanceof GrVariableDeclaration && ((GrVariableDeclaration)parent).isTuple()) {
//check tuple assignment: def (int x, int y) = foo()
final GrVariableDeclaration tuple = (GrVariableDeclaration)parent;
final GrExpression initializer = tuple.getTupleInitializer();
if (initializer == null) return;
if (!(initializer instanceof GrListOrMap)) {
PsiType type = initializer.getType();
if (type == null) return;
PsiType valueType = extractIterableTypeParameter(type, false);
processAssignment(varType, valueType, tuple, variable.getNameIdentifierGroovy());
return;
}
}
GrExpression initializer = variable.getInitializerGroovy();
if (initializer == null) return;
processAssignment(varType, initializer, variable.getNameIdentifierGroovy(), "cannot.assign", variable, ApplicableTo.ASSIGNMENT);
}
@Override
protected void registerError(@NotNull PsiElement location,
ProblemHighlightType highlightType,
Object... args) {
registerError(location, (String)args[0], LocalQuickFix.EMPTY_ARRAY, highlightType);
}
}