package com.redhat.ceylon.compiler.java.codegen.recovery;
import java.util.List;
import com.redhat.ceylon.common.Backend;
import com.redhat.ceylon.compiler.typechecker.analyzer.AnalysisError;
import com.redhat.ceylon.compiler.typechecker.analyzer.UsageWarning;
import com.redhat.ceylon.compiler.typechecker.tree.Message;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.compiler.typechecker.tree.Visitor;
import com.redhat.ceylon.model.loader.model.LazyClass;
import com.redhat.ceylon.model.typechecker.model.Class;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Value;
/**
* Visitor for declarations which constructs a {@link TransformationPlan} for
* transforming a {@link Tree.Declaration} based on the errors in the tree.
* Unlike the {@link StatementErrorVisitor} and {@link ErrorVisitor}
* this visitor does not fail fast.
*/
class DeclarationErrorVisitor extends Visitor {
private static final int TYPE_DECLARATION_DOES_NOT_EXIST = 102;
private static final int FORMAL_MEMBER_UNIMPLEMENTED_IN_CLASS_HIERARCHY = 300;
private static final int MEMBER_HAS_WRONG_NUMBER_OF_PARAMETERS = 9100;
private static final int TYPE_OF_PARAMETER_IS_DIFFERENT_TO_CORRESPONDING_PARAMETER = 9200;
private static final int COULD_NOT_DETERMINE_PARAMETER_TYPE_SAME_AS_CORRESPONDING_PARAMETER = 9210;
private static final int REFINED_MEMBER_WRONG_NUM_PL = 9300;
private static final int MISSING_PL_FUNCTION_DECL = 1000;
private static final int PL_AND_CONSTRUCTORS = 1002;
private TransformationPlan plan;
private final ExpressionErrorVisitor expressionVisitor;
private boolean expectingError;
private String errMessage;
private Declaration model;
DeclarationErrorVisitor(ExpressionErrorVisitor expressionVisitor) {
this.expressionVisitor = expressionVisitor;
}
public final HasErrorException getFirstErrorMessage(Tree.Declaration target) {
TransformationPlan plan = getRecoveryPlan(target);
if (plan instanceof Generate) {
return null;
} else {
return new HasErrorException(plan.getNode(), plan.getErrorMessage());
}
}
public final TransformationPlan getRecoveryPlan(Tree.Declaration target) {
model = target.getDeclarationModel();
TransformationPlan oldPlan = plan;
try {
plan = Errors.GENERATE;
target.visit(this);
return plan;
} finally {
plan = oldPlan;
}
}
/**
* Install a new plan iff it's more drastic than the existing plan
* @param plan
*/
private void newplan(TransformationPlan plan) {
if (plan.replaces(this.plan)) {
this.plan = plan;
}
}
@Override
public final void visitAny(Node that) {
planAccordingToErrors(that);
super.visitAny(that);
}
/**
* Update the plan according to the errors on the node
*/
private void planAccordingToErrors(Node that) {
List<Message> errors = that.getErrors();
for(Message message : errors){
if(isError(that, message)) {
TransformationPlan plan;
/*if (message.getCode() == MEMBER_HAS_WRONG_NUMBER_OF_PARAMETERS
&& model.isActual()
&& model.isClassMember()) {
plan = new ThrowerMethod(that, message);
} else if (message.getCode() == TYPE_OF_PARAMETER_IS_DIFFERENT_TO_CORRESPONDING_PARAMETER
&& model.isActual()
&& model.isClassMember()) {
plan = new ThrowerMethod(that, message);
} else if (message.getCode() == COULD_NOT_DETERMINE_PARAMETER_TYPE_SAME_AS_CORRESPONDING_PARAMETER
&& model.isActual()
&& model.isClassMember()) {
plan = new ThrowerMethod(that, message);
} else if ((message.getCode() == REFINED_MEMBER_WRONG_NUM_PL
|| message.getCode() == MISSING_PL_FUNCTION_DECL)
&& model.isActual()
&& model.isClassMember()) {
plan = new ThrowerMethod(that, message);
} else*/ if (message.getCode() == FORMAL_MEMBER_UNIMPLEMENTED_IN_CLASS_HIERARCHY
&& (model instanceof Class
|| (model instanceof Value
&& ((Value)model).getTypeDeclaration().isAnonymous()))) {
plan = new ThrowerMethod(that, message);
}
else if (message.getCode() == PL_AND_CONSTRUCTORS
&& (model instanceof Class
|| (model instanceof Value
&& ((Value)model).getTypeDeclaration().isAnonymous()))) {
plan = new ThrowerCatchallConstructor(that, message);
}
else {
plan = new Drop(that, message);
}
newplan(plan);
}
}
}
/** Is the given message on the given node considered an error */
private boolean isError(Node that, Message message) {
if (errMessage != null
&& message.getMessage().equals(errMessage)) {
return false;
} else {
return !(message instanceof UsageWarning);
}
}
@Override
public void visit(Tree.Annotation that) {
// Unlike declaration bodies or specifiers,
// we *do* care about errors in expressions in annotations: Those are
// considered part of the declaration
HasErrorException error = expressionVisitor.getFirstErrorMessage(that);
if (error != null) {
newplan(new Drop(error.getNode(), error.getErrorMessage()));
}
}
@Override
public void visit(Tree.Body that) {
// don't go there: we don't really care about block errors
}
@Override
public void visit(Tree.SpecifierOrInitializerExpression that) {
// don't go there: we don't really care about expression errors
}
@Override
public void visit(Tree.Type that) {
HasErrorException error = expressionVisitor.getFirstErrorMessage(that);
if (error != null && isError(that, error.getErrorMessage())) {
newplan(new Drop(error.getNode(), error.getErrorMessage()));
return;
}
// type inference is used but the type of
// the inferred expression is unknown due to other errors
if (that.getTypeModel().containsUnknowns()) {
newplan(new Drop(that, new AnalysisError(that, "unknown type", Backend.Java)));
}
}
@Override
public void visit(Tree.InitializerParameter that) {
planAccordingToErrors(that);
// but don't visit children
}
public void visit(Tree.StatementOrArgument that) {
boolean b = this.expectingError;
initExpectingError(that.getCompilerAnnotations());
super.visit(that);
expectingError = b;
}
public void visit(Tree.ParameterDeclaration that) {
boolean b = this.expectingError;
initExpectingError(that.getTypedDeclaration().getCompilerAnnotations());
super.visit(that);
expectingError = b;
}
public void visit(Tree.CompilationUnit that) {
boolean b = this.expectingError;
initExpectingError(that.getCompilerAnnotations());
super.visit(that);
expectingError = b;
}
protected void initExpectingError(List<Tree.CompilerAnnotation> annotations) {
for (Tree.CompilerAnnotation c: annotations) {
if (c.getIdentifier().getText().equals("error")) {
expectingError = true;
Tree.StringLiteral sl = c.getStringLiteral();
if (sl!=null) {
errMessage = sl.getText();
}
}
}
}
public void visit(Tree.ExtendedType that) {
that.getType().visit(this);
// Don't visit the invocation expression unless our superclass is a
// Java class with a private ctor -- that could cause a javac
// error if we tried to generate a ctor for this class.
if (that.getType().getDeclarationModel() instanceof LazyClass
&& !((LazyClass)that.getType().getDeclarationModel()).isCeylon()) {
boolean hasPrivateCtor = false;
List<Declaration> overloads = ((LazyClass)that.getType().getDeclarationModel()).getOverloads();
if (overloads != null) {
for (Declaration ctor : overloads) {
if (!ctor.isShared()) {
hasPrivateCtor = true;
break;
}
}
}
if (hasPrivateCtor) {
that.getInvocationExpression().visit(this);
}
}
}
}