package polyglot.ext.jl.ast;
import polyglot.ast.*;
import polyglot.types.*;
import polyglot.util.*;
import polyglot.visit.*;
import java.util.*;
import polyglot.main.*;
/**
* A <code>Call</code> is an immutable representation of a Java
* method call. It consists of a method name and a list of arguments.
* It may also have either a Type upon which the method is being
* called or an expression upon which the method is being called.
*/
public class Call_c extends Expr_c implements Call
{
protected Receiver target;
protected String name;
protected List arguments;
protected MethodInstance mi;
protected boolean targetImplicit;
public Call_c(Position pos, Receiver target, String name,
List arguments) {
super(pos);
this.target = target;
this.name = name;
this.arguments = TypedList.copyAndCheck(arguments, Expr.class, true);
this.targetImplicit = (target == null);
}
/** Get the precedence of the call. */
public Precedence precedence() {
return Precedence.LITERAL;
}
/** Get the target object or type of the call. */
public Receiver target() {
return this.target;
}
/** Set the target object or type of the call. */
public Call target(Receiver target) {
Call_c n = (Call_c) copy();
n.target = target;
return n;
}
/** Get the name of the call. */
public String name() {
return this.name;
}
/** Set the name of the call. */
public Call name(String name) {
Call_c n = (Call_c) copy();
n.name = name;
return n;
}
public ProcedureInstance procedureInstance() {
return methodInstance();
}
/** Get the method instance of the call. */
public MethodInstance methodInstance() {
return this.mi;
}
/** Set the method instance of the call. */
public Call methodInstance(MethodInstance mi) {
Call_c n = (Call_c) copy();
n.mi = mi;
return n;
}
public boolean isTargetImplicit() {
return this.targetImplicit;
}
public Call targetImplicit(boolean targetImplicit) {
if (targetImplicit == this.targetImplicit) {
return this;
}
Call_c n = (Call_c) copy();
n.targetImplicit = targetImplicit;
return n;
}
/** Get the actual arguments of the call. */
public List arguments() {
return this.arguments;
}
/** Set the actual arguments of the call. */
public ProcedureCall arguments(List arguments) {
Call_c n = (Call_c) copy();
n.arguments = TypedList.copyAndCheck(arguments, Expr.class, true);
return n;
}
/** Reconstruct the call. */
protected Call_c reconstruct(Receiver target, List arguments) {
if (target != this.target || ! CollectionUtil.equals(arguments,
this.arguments)) {
Call_c n = (Call_c) copy();
n.target = target;
n.arguments = TypedList.copyAndCheck(arguments, Expr.class, true);
return n;
}
return this;
}
/** Visit the children of the call. */
public Node visitChildren(NodeVisitor v) {
Receiver target = (Receiver) visitChild(this.target, v);
List arguments = visitList(this.arguments, v);
return reconstruct(target, arguments);
}
public Node buildTypes(TypeBuilder tb) throws SemanticException {
Call_c n = (Call_c) super.buildTypes(tb);
TypeSystem ts = tb.typeSystem();
List l = new ArrayList(arguments.size());
for (int i = 0; i < arguments.size(); i++) {
l.add(ts.unknownType(position()));
}
MethodInstance mi = ts.methodInstance(position(), ts.Object(),
Flags.NONE,
ts.unknownType(position()),
name, l,
Collections.EMPTY_LIST);
return n.methodInstance(mi);
}
/**
* Typecheck the Call when the target is null. This method finds
* an appropriate target, and then type checks accordingly.
*
* @param argTypes list of <code>Type</code>s of the arguments
*/
protected Node typeCheckNullTarget(TypeChecker tc, List argTypes) throws SemanticException {
TypeSystem ts = tc.typeSystem();
NodeFactory nf = tc.nodeFactory();
Context c = tc.context();
// the target is null, and thus implicit
// let's find the target, using the context, and
// set the target appropriately, and then type check
// the result
MethodInstance mi = c.findMethod(this.name, argTypes);
Receiver r;
if (mi.flags().isStatic()) {
r = nf.CanonicalTypeNode(position(), mi.container()).type(mi.container());
} else {
// The method is non-static, so we must prepend with "this", but we
// need to determine if the "this" should be qualified. Get the
// enclosing class which brought the method into scope. This is
// different from mi.container(). mi.container() returns a super type
// of the class we want.
ClassType scope = c.findMethodScope(name);
if (! ts.equals(scope, c.currentClass())) {
r = nf.This(position(),
nf.CanonicalTypeNode(position(), scope)).type(scope);
}
else {
r = nf.This(position()).type(scope);
}
}
// we call typeCheck on the reciever too.
r = (Receiver)r.typeCheck(tc);
return this.targetImplicit(true).target(r).del().typeCheck(tc);
}
/** Type check the call. */
public Node typeCheck(TypeChecker tc) throws SemanticException {
TypeSystem ts = tc.typeSystem();
Context c = tc.context();
List argTypes = new ArrayList(this.arguments.size());
for (Iterator i = this.arguments.iterator(); i.hasNext(); ) {
Expr e = (Expr) i.next();
argTypes.add(e.type());
}
if (this.target == null) {
return this.typeCheckNullTarget(tc, argTypes);
}
ReferenceType targetType = this.findTargetType();
MethodInstance mi = ts.findMethod(targetType,
this.name,
argTypes,
c.currentClass());
/* This call is in a static context if and only if
* the target (possibly implicit) is a type node.
*/
boolean staticContext = (this.target instanceof TypeNode);
if (staticContext && !mi.flags().isStatic()) {
throw new SemanticException("Cannot call non-static method " + this.name
+ " of " + targetType + " in static "
+ "context.", this.position());
}
// If the target is super, but the method is abstract, then complain.
if (this.target instanceof Special &&
((Special)this.target).kind() == Special.SUPER &&
mi.flags().isAbstract()) {
throw new SemanticException("Cannot call an abstract method " +
"of the super class", this.position());
}
// If we found a method, the call must type check, so no need to check
// the arguments here.
Call_c call = (Call_c)this.methodInstance(mi).type(mi.returnType());
call.checkConsistency(c);
return call;
}
//ak333: made public so it could be accessed by CppCallDel
public ReferenceType findTargetType() throws SemanticException {
Type t = target.type();
if (t.isReference()) {
return t.toReference();
} else {
// trying to invoke a method on a non-reference type.
// let's pull out an appropriate error message.
if (target instanceof Expr) {
throw new SemanticException("Cannot invoke method \"" + name + "\" on "
+ "an expression of non-reference type "
+ t + ".", target.position());
}
else if (target instanceof TypeNode) {
throw new SemanticException("Cannot invoke static method \"" + name
+ "\" on non-reference type " + t + ".",
target.position());
}
throw new SemanticException("Receiver of method invocation must be a "
+ "reference type.",
target.position());
}
}
public Type childExpectedType(Expr child, AscriptionVisitor av)
{
if (child == target) {
return mi.container();
}
Iterator i = this.arguments.iterator();
Iterator j = mi.formalTypes().iterator();
while (i.hasNext() && j.hasNext()) {
Expr e = (Expr) i.next();
Type t = (Type) j.next();
if (e == child) {
return t;
}
}
return child.type();
}
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append(targetImplicit ? "" : target.toString() + ".");
sb.append(name);
sb.append("(");
int count = 0;
for (Iterator i = arguments.iterator(); i.hasNext(); ) {
if (count++ > 2) {
sb.append("...");
break;
}
Expr n = (Expr) i.next();
sb.append(n.toString());
if (i.hasNext()) {
sb.append(", ");
}
}
sb.append(")");
return sb.toString();
}
/** Write the expression to an output file. */
public void prettyPrint(CodeWriter w, PrettyPrinter tr) {
if (!targetImplicit) {
if (target instanceof Expr) {
printSubExpr((Expr) target, w, tr);
w.write(".");
}
else if (target != null) {
print(target, w, tr);
w.write(".");
}
}
w.write(name + "(");
w.begin(0);
for(Iterator i = arguments.iterator(); i.hasNext();) {
Expr e = (Expr) i.next();
print(e, w, tr);
if (i.hasNext()) {
w.write(",");
w.allowBreak(0, " ");
}
}
w.end();
w.write(")");
}
/** Dumps the AST. */
public void dump(CodeWriter w) {
super.dump(w);
if ( mi != null ) {
w.allowBreak(4, " ");
w.begin(0);
w.write("(instance " + mi + ")");
w.end();
}
w.allowBreak(4, " ");
w.begin(0);
w.write("(name " + name + ")");
w.end();
w.allowBreak(4, " ");
w.begin(0);
w.write("(arguments " + arguments + ")");
w.end();
}
public Term entry() {
if (target instanceof Expr) {
return ((Expr) target).entry();
}
return listEntry(arguments, this);
}
public List acceptCFG(CFGBuilder v, List succs) {
if (target instanceof Expr) {
Expr t = (Expr) target;
v.visitCFG(t, listEntry(arguments, this));
}
v.visitCFGList(arguments, this);
return succs;
}
/** Check exceptions thrown by the call. */
public Node exceptionCheck(ExceptionChecker ec) throws SemanticException {
if (mi == null) {
throw new InternalCompilerError(position(),
"Null method instance after type "
+ "check.");
}
return super.exceptionCheck(ec);
}
public List throwTypes(TypeSystem ts) {
List l = new LinkedList();
l.addAll(mi.throwTypes());
l.addAll(ts.uncheckedExceptions());
if (target instanceof Expr && ! (target instanceof Special)) {
l.add(ts.NullPointerException());
}
return l;
}
// check that the implicit target setting is correct.
protected void checkConsistency(Context c) throws SemanticException {
if (targetImplicit) {
// the target is implicit. Check that the
// method found in the target type is the
// same as the method found in the context.
// as exception will be thrown if no appropriate method
// exists.
MethodInstance ctxtMI = c.findMethod(name, mi.formalTypes());
// cannot perform this check due to the context's findMethod returning a
// different method instance than the typeSystem in some situations
// if (!c.typeSystem().equals(ctxtMI, mi)) {
// throw new InternalCompilerError("Method call " + this + " has an " +
// "implicit target, but the name " + name + " resolves to " +
// ctxtMI + " in " + ctxtMI.container() + " instead of " + mi+ " in " + mi.container(), position());
// }
}
}
}