/*******************************************************************************
* Copyright © 2011, 2013 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
*
*******************************************************************************/
package org.eclipse.edt.compiler.internal.core.lookup;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import org.eclipse.edt.compiler.core.ast.AbstractASTExpressionVisitor;
import org.eclipse.edt.compiler.core.ast.ArrayAccess;
import org.eclipse.edt.compiler.core.ast.CallStatement;
import org.eclipse.edt.compiler.core.ast.DefaultASTVisitor;
import org.eclipse.edt.compiler.core.ast.Expression;
import org.eclipse.edt.compiler.core.ast.FieldAccess;
import org.eclipse.edt.compiler.core.ast.FunctionInvocation;
import org.eclipse.edt.compiler.core.ast.LiteralExpression;
import org.eclipse.edt.compiler.core.ast.Name;
import org.eclipse.edt.compiler.core.ast.Node;
import org.eclipse.edt.compiler.core.ast.ParenthesizedExpression;
import org.eclipse.edt.compiler.core.ast.QualifiedName;
import org.eclipse.edt.compiler.core.ast.SetValuesExpression;
import org.eclipse.edt.compiler.core.ast.SimpleName;
import org.eclipse.edt.compiler.core.ast.SubstringAccess;
import org.eclipse.edt.compiler.core.ast.SuperExpression;
import org.eclipse.edt.compiler.core.ast.TernaryExpression;
import org.eclipse.edt.compiler.core.ast.ThisExpression;
import org.eclipse.edt.compiler.internal.core.builder.IProblemRequestor;
import org.eclipse.edt.compiler.internal.core.validation.statement.LValueValidator;
import org.eclipse.edt.compiler.internal.core.validation.statement.RValueValidator;
import org.eclipse.edt.compiler.internal.core.validation.type.TypeValidator;
import org.eclipse.edt.compiler.internal.util.BindingUtil;
import org.eclipse.edt.mof.egl.ArrayType;
import org.eclipse.edt.mof.egl.Delegate;
import org.eclipse.edt.mof.egl.FunctionMember;
import org.eclipse.edt.mof.egl.FunctionParameter;
import org.eclipse.edt.mof.egl.Member;
import org.eclipse.edt.mof.egl.NamedElement;
import org.eclipse.edt.mof.egl.ParameterKind;
import org.eclipse.edt.mof.egl.Type;
import org.eclipse.edt.mof.egl.utils.TypeUtils;
public class FunctionArgumentValidator extends DefaultASTVisitor {
private IProblemRequestor problemRequestor;
private NamedElement functionBinding;
private String canonicalFunctionName;
private Iterator<FunctionParameter> parameterIter;
private int numArgs;
private Expression qualifier;
private ICompilerOptions compilerOptions;
public FunctionArgumentValidator(Delegate delegateBinding, IProblemRequestor problemRequestor, ICompilerOptions compilerOptions) {
this.functionBinding = delegateBinding;
this.parameterIter = delegateBinding.getParameters().iterator();
this.problemRequestor = problemRequestor;
this.compilerOptions = compilerOptions;
}
public FunctionArgumentValidator(FunctionMember functionBinding, IProblemRequestor problemRequestor, ICompilerOptions compilerOptions) {
this.functionBinding = functionBinding;
this.parameterIter = functionBinding.getParameters().iterator();
this.problemRequestor = problemRequestor;
this.compilerOptions = compilerOptions;
}
@Override
public boolean visit(FunctionInvocation functionInvocation) {
this.qualifier = functionInvocation;
functionInvocation.getTarget().accept(new DefaultASTVisitor() {
@Override
public boolean visit(SimpleName simpleName) {
canonicalFunctionName = simpleName.getCanonicalName();
return false;
}
@Override
public boolean visit(QualifiedName qualifiedName) {
String canonicalName = qualifiedName.getCanonicalName();
canonicalFunctionName = canonicalName.substring(canonicalName.lastIndexOf('.')+1);
return false;
}
});
if(canonicalFunctionName == null) {
canonicalFunctionName = functionInvocation.getTarget().getCanonicalString();
}
for(Iterator iter = functionInvocation.getArguments().iterator(); iter.hasNext();) {
checkArg((Expression) iter.next());
}
return false;
}
@Override
public void endVisit(FunctionInvocation functionInvocation) {
checkCorrectNumberArguments(functionInvocation.getTarget());
}
@Override
public boolean visit(CallStatement callStatement) {
this.qualifier = callStatement.getInvocationTarget();
canonicalFunctionName = callStatement.getInvocationTarget().getCanonicalString();
if (!callStatement.hasArguments()) {
return false;
}
for(Node expr : callStatement.getArguments()) {
checkArg((Expression)expr);
}
return false;
}
@Override
public void endVisit(CallStatement callStatement) {
checkCorrectNumberArguments(callStatement.getInvocationTarget());
}
private void checkCorrectNumberArguments(Expression errorNode) {
int parmCount = getFunctionParameterCount();
if (parmCount != numArgs) {
problemRequestor.acceptProblem(
errorNode,
IProblemRequestor.ROUTINE_MUST_HAVE_X_ARGS,
new String[] {
canonicalFunctionName,
Integer.toString(parmCount)
}
);
}
}
private int getFunctionParameterCount(){
if(functionBinding instanceof Delegate){
return ((Delegate)functionBinding).getParameters().size();
}
if(functionBinding instanceof FunctionMember){
return ((FunctionMember)functionBinding).getParameters().size();
}
return -1;
}
public boolean checkArg(Expression argExpr) {
numArgs += 1;
if(!parameterIter.hasNext()) {
return false;
}
FunctionParameter parameterBinding = parameterIter.next();
Type parameterType = parameterBinding.getType();
// Set values exprs have no type or member so do this check before the next check below
if (!checkArgumentNotSetValuesExpression(argExpr)) {
return false;
}
if (argExpr.resolveType() == null && argExpr.resolveMember() == null) {
return false;
}
// An argument can be a ternary, which really has 2 (or more) args to validate.
Map<Expression, Type> argMap = new HashMap<Expression, Type>();
TypeValidator.collectExprsForTypeCompatibility(argExpr, argMap);
if (!checkSubstringNotUsedAsArgument(parameterBinding, argMap)) {
return false;
}
if (!checkArgumentUsedCorrectlyWithInAndOut(argMap, parameterBinding, parameterType)) {
return false;
}
switch (parameterBinding.getParameterKind()) {
case PARM_IN:
checkArgForInParameter(argMap, parameterBinding, parameterType, numArgs);
break;
case PARM_OUT:
checkArgForOutParameter(argMap, parameterBinding, parameterType, numArgs);
break;
case PARM_INOUT:
checkArgForInOutParameter(argMap, parameterBinding, parameterType, numArgs);
break;
}
return false;
}
private boolean checkArgumentNotSetValuesExpression(final Expression argExpr) {
final boolean[] result = new boolean[] {true};
argExpr.accept(new DefaultASTVisitor() {
@Override
public boolean visit(ParenthesizedExpression parenthesizedExpression) {
return true;
}
@Override
public boolean visit(TernaryExpression ternaryExpression) {
ternaryExpression.getSecondExpr().accept(this);
ternaryExpression.getThirdExpr().accept(this);
return false;
}
@Override
public boolean visit(SetValuesExpression setValuesExpression) {
problemRequestor.acceptProblem(
setValuesExpression,
IProblemRequestor.SET_VALUES_BLOCK_NOT_VALID_AS_FUNC_ARG);
result[0] = false;
return false;
}
});
return result[0];
}
private boolean checkSubstringNotUsedAsArgument(FunctionParameter parm, Map<Expression, Type> argMap) {
boolean result = true;
if (parm != null && parm.getParameterKind() != ParameterKind.PARM_IN) {
for (Expression argExpr : argMap.keySet()) {
if (argExpr instanceof SubstringAccess) {
problemRequestor.acceptProblem(argExpr,
IProblemRequestor.SUBSTRING_IMMUTABLE,
new String[] {});
result = false;
}
}
}
return result;
}
private abstract static class NonLiteralAndNonNameExpressionVisitor extends AbstractASTExpressionVisitor {
@Override
public void endVisit(ParenthesizedExpression parenthesizedExpression) {
parenthesizedExpression.getExpression().accept(this);
}
@Override
public void endVisitName(Name name) {}
@Override
public void endVisit(ArrayAccess arrayAccess) {}
@Override
public void endVisit(SubstringAccess substringAccess) {}
@Override
public void endVisit(FieldAccess fieldAccess) {}
@Override
public void endVisit(SuperExpression superExpression) {}
@Override
public void endVisit(ThisExpression thisExpression) {}
@Override
public void endVisitLiteral(LiteralExpression literal) {}
@Override
public void endVisitExpression(Expression expression) {
handleExpressionThatIsNotNameOrLiteral(expression);
}
abstract void handleExpressionThatIsNotNameOrLiteral(Expression expression);
}
private boolean checkArgumentUsedCorrectlyWithInAndOut(Map<Expression, Type> argMap, final FunctionParameter parmBinding, Type parmType) {
boolean result = true;
for (Map.Entry<Expression, Type> entry : argMap.entrySet()) {
if (entry.getValue() == null) {
continue;
}
Expression argExpr = entry.getKey();
final boolean[] expressionIsLiteralOrName = new boolean[] {true};
final boolean[] foundError = new boolean[] {false};
argExpr.accept(new NonLiteralAndNonNameExpressionVisitor() {
@Override
void handleExpressionThatIsNotNameOrLiteral(Expression expression) {
if(parmBinding.getParameterKind() != ParameterKind.PARM_IN) {
problemRequestor.acceptProblem(
expression,
IProblemRequestor.FUNCTION_ARG_REQUIRES_IN_PARAMETER,
new String[] {
expression.getCanonicalString(),
functionBinding.getCaseSensitiveName()
});
foundError[0] = true;
}
expressionIsLiteralOrName[0] = false;
}
});
if (foundError[0]) {
result = false;
}
else if (expressionIsLiteralOrName[0] && parmBinding.getParameterKind() != ParameterKind.PARM_IN) {
if(!checkArgNotConstantOrLiteral(argExpr, parmBinding)) {
result = false;
}
}
}
return result;
}
private boolean checkArgNotConstantOrLiteral(Expression argExpr, FunctionParameter parmBinding) {
final int problemKind = parmBinding.getParameterKind() == ParameterKind.PARM_INOUT
? IProblemRequestor.FUNCTION_ARG_LITERAL_NOT_VALID_WITH_INOUT_PARAMETER
: IProblemRequestor.FUNCTION_ARG_LITERAL_NOT_VALID_WITH_OUT_PARAMETER;
Name constName = LValueValidator.findConstName(argExpr);
Member constMember = constName == null ? null : constName.resolveMember();
if (constMember != null) {
boolean canPassConst = false;
if (parmBinding.getParameterKind() == ParameterKind.PARM_INOUT && parmBinding.isConst()) {
canPassConst = true;
}
else {
// Value types means every part of the field (including accesses) are constant. For reference types it's just the field declaration that's constant.
if (constName != argExpr && !(constMember.getType() != null && TypeUtils.isValueType(constMember.getType()))) {
canPassConst = true;
}
}
if (!canPassConst) {
problemRequestor.acceptProblem(
argExpr,
problemKind,
new String[] {
argExpr.getCanonicalString(),
functionBinding.getCaseSensitiveName()
});
}
return false;
}
final boolean[] foundError = new boolean[] {false};
argExpr.accept(new AbstractASTExpressionVisitor() {
@Override
public void endVisitName(Name name) {}
@Override
public void endVisit(ArrayAccess arrayAccess) {}
@Override
public void endVisit(FieldAccess fieldAccess) {}
@Override
public void endVisit(SubstringAccess substringAccess) {}
@Override
public void endVisit(SuperExpression superExpression) {}
@Override
public void endVisit(ThisExpression thisExpression) {};
@Override
public void endVisitExpression(Expression expression) {
problemRequestor.acceptProblem(
expression,
problemKind,
new String[] {
expression.getCanonicalString(),
functionBinding.getCaseSensitiveName()
});
foundError[0] = true;
}
});
return !foundError[0];
}
private boolean checkArgForInOrOutParameter(Map<Expression, Type> argMap, FunctionParameter funcParmBinding, Type parmType, int argNum) {
boolean result = true;
for (Map.Entry<Expression, Type> entry : argMap.entrySet()) {
Expression argExpr = entry.getKey();
Type argType = entry.getValue();
parmType = BindingUtil.resolveGenericType(parmType, qualifier);
if (!BindingUtil.isMoveCompatible(parmType, funcParmBinding, argType, argExpr)) {
// Generic type parms are defined as EAny (see EList.appendElement). Therefore the binding does not have the nullable flag set.
// When passing in 'null', We have to use the qualifier's type to check if the elements are nullable.
if (TypeUtils.Type_NULLTYPE.equals(argType) && !funcParmBinding.isNullable() && BindingUtil.isUnresolvedGenericType(funcParmBinding.getType())) {
// The qualifier's type will tell us if it's nullable.
Type qualType = BindingUtil.getTypeForGenericQualifier(qualifier);
if (qualType instanceof ArrayType && ((ArrayType)qualType).elementsNullable()) {
continue;
}
}
problemRequestor.acceptProblem(
argExpr,
IProblemRequestor.FUNCTION_ARG_NOT_ASSIGNMENT_COMPATIBLE_WITH_PARM,
new String[] {
argExpr.getCanonicalString(),
funcParmBinding.getCaseSensitiveName(),
canonicalFunctionName,
// arg can be a function, which has no type
BindingUtil.getShortTypeString(argExpr, argType),
BindingUtil.getShortTypeString(parmType)
});
result = false;
}
}
return result;
}
private boolean checkArgForInParameter(Map<Expression, Type> argMap, FunctionParameter funcParmBinding, Type parmType, int argNum) {
boolean result = true;
for (Map.Entry<Expression, Type> entry : argMap.entrySet()) {
Expression argExpr = entry.getKey();
Member argDBinding = argExpr.resolveMember();
if (argDBinding != null) {
if (!new RValueValidator(problemRequestor, compilerOptions, argDBinding, argExpr).validate()) {
result = false;
}
}
validateNotSuper(argExpr);
}
if (!result) {
return false;
}
return checkArgForInOrOutParameter(argMap, funcParmBinding, parmType, argNum);
}
private boolean checkArgForOutParameter(Map<Expression, Type> argMap, final FunctionParameter funcParmBinding, Type parmType, int argNum) {
boolean result = true;
for (Map.Entry<Expression, Type> entry : argMap.entrySet()) {
Expression argExpr = entry.getKey();
Member argMember = argExpr.resolveMember();
if(argMember != null) {
if(!new LValueValidator(problemRequestor, compilerOptions, argMember, argExpr, new LValueValidator.DefaultLValueValidationRules() {
@Override
public boolean canAssignToFunctionParmConst() {
return funcParmBinding.isConst();
}
@Override
public boolean canAssignToConstantVariables() {
return funcParmBinding.isConst();
}
}).validate()) {
result = false;
continue;
}
}
validateNotThis(argExpr);
validateNotSuper(argExpr);
}
if (!result) {
return false;
}
return checkArgForInOrOutParameter(argMap, funcParmBinding, parmType, argNum);
}
private boolean checkArgForInOutParameter(Map<Expression, Type> argMap, final FunctionParameter funcParmBinding, Type parmType, int argNum) {
boolean result = true;
for (Map.Entry<Expression, Type> entry : argMap.entrySet()) {
Expression argExpr = entry.getKey();
Type argType = entry.getValue();
Member argMember = argExpr.resolveMember();
if (argMember != null) {
if(!new RValueValidator(problemRequestor, compilerOptions, argMember, argExpr).validate()) {
result = false;
continue;
}
if(!new LValueValidator(problemRequestor, compilerOptions, argMember, argExpr, new LValueValidator.DefaultLValueValidationRules() {
@Override
public boolean canAssignToFunctionReferences() {
return true;
}
@Override
public boolean canAssignToConstantVariables() {
return true;
}
@Override
public boolean canAssignToFunctionParmConst() {
return funcParmBinding.isConst();
}
}).validate()) {
result = false;
continue;
}
}
boolean argCompatible = argMember == null || argMember.isNullable() == funcParmBinding.isNullable();
if (argCompatible) {
if (argType != null) {
argCompatible = BindingUtil.isReferenceCompatible(parmType, argType);
}
else if (argMember != null) {
argCompatible = TypeUtils.areCompatible(parmType.getClassifier(), argMember);
}
}
if (!argCompatible) {
problemRequestor.acceptProblem(
argExpr,
IProblemRequestor.FUNCTION_ARG_NOT_REFERENCE_COMPATIBLE_WITH_PARM,
new String[] {
argExpr.getCanonicalString(),
funcParmBinding.getCaseSensitiveName(),
canonicalFunctionName,
// Use getTypeName() so that nullability is included in the message, since nullability must match ("string is not compatible with string" would look odd)
BindingUtil.getTypeName(argMember, argType),
BindingUtil.getTypeName(funcParmBinding)
});
result = false;
continue;
}
validateNotThis(argExpr);
validateNotSuper(argExpr);
}
return result;
}
private void validateNotThis(Expression expr) {
DefaultASTVisitor visitor = new DefaultASTVisitor() {
@Override
public boolean visit(ThisExpression thisExpression) {
problemRequestor.acceptProblem(
thisExpression,
IProblemRequestor.FUNCTION_ARG_CANNOT_BE_THIS,
new String[] {});
return false;
}
};
expr.accept(visitor);
}
private void validateNotSuper(Expression expr) {
DefaultASTVisitor visitor = new DefaultASTVisitor() {
@Override
public boolean visit(SuperExpression superExpression) {
problemRequestor.acceptProblem(
superExpression,
IProblemRequestor.FUNCTION_ARG_CANNOT_BE_SUPER,
new String[] {});
return false;
}
};
expr.accept(visitor);
}
}