slcom/slangc/model/MethodModel.sauce

647 lines
21 KiB
Plaintext

package slangc.model;
import slang.data.List;
import slangc.api.Reporter;
import slangc.bytecode.MethodSignature;
import slangc.bytecode.TypeSignature;
import slangc.model.clauses.Arguments;
import slangc.model.expressions.SuperConstructorCallExpression;
import slangc.model.expressions.ThisConstructorCallExpression;
import slangc.model.statements.BlockStatement;
import slangc.model.statements.ExpressionStatement;
import slangc.parser.Branch;
import slangc.parser.ErrorType;
import slangc.parser.Node;
import slangc.parser.NodeType;
public class MethodModel extends MemberModel implements StatementOwner {
BlockStatement body = null;
ParameterSet parameters = null;
TypeModel returnType = null;
TypeModel[] exceptions = null;
List<LocalStorageModel> locals = null;
MethodModel prototype = null;
/** Constructor for synthetic methods which copy an existing method signature. */
public MethodModel(TypeModel owner, String name, MethodModel copySignatureFrom) {
super(owner, name, null);
// TODO: It might be worth making a deep copy of the parameters so they're actually usable by synthetic code
parameters = copySignatureFrom.parameters;
returnType = copySignatureFrom.returnType;
exceptions = copySignatureFrom.exceptions;
setAttributes(copySignatureFrom.getAttributes());
this.prototype = copySignatureFrom;
}
public MethodModel(TypeModel owner, String name, Branch source) {
super(owner, name, source);
parameters = new ParameterSet(this);
if (!isStaticInitialisation()) {
Branch p = source.getNodeType() == NodeType.METHOD_DECLARATION ?
(Branch) source.getSubnode(source.countSubnodes() - 4)
: (Branch) source.getSubnode(source.countSubnodes() - 3);
Branch l = (Branch) p.getSubnode(1);
int goodparams = 0;
for (int i = 0; i < l.countSubnodes(); i++) {
Node n = l.getSubnode(i);
if (n.getNodeType() != NodeType.COMMA) {
goodparams++;
}
}
for (int i = 0; i < l.countSubnodes(); i++) {
/* Parameters are indexed in bytecode by their stack position (relative to
* the method call frame info), which gives the last parameter index -1,
* the second-last index -2 and so on (since the last parameter will be
* pushed just before the method call frame starts).
*/
int negindex = (0 - goodparams) + parameters.countParameters();
Node n = l.getSubnode(i);
if (n.getNodeType() != NodeType.COMMA) {
parameters.addParameter(new ParameterModel(this, negindex /*parameters.countParameters()*/, (Branch) n));
}
}
exceptions = unpackExceptions(((Branch)(source.getSubnode(source.countSubnodes() - 2))));
}
if (isConstructor()) {
//Log.line("Owner is " + owner.fullName());
returnType = owner;
} else if (isStaticInitialisation()) {
returnType = null; // TODO: Better "void" handling
} else {
Branch t = (Branch) source.getSubnode(2);
if (t.getNodeType() == NodeType.NO_RETURN_TYPE) {
returnType = null; // TODO: Better "void" handling
} else {
returnType = ((UserTypeModel)owner).resolveTypeReference(t.getSubnode(0));
}
}
if (hasBody()) {
body = new BlockStatement(this, (Branch) ((Branch)(source.getSubnode(source.countSubnodes() - 1))).getSubnode(0));
}
}
private TypeModel[] unpackExceptions(Branch b) {
if (b.getNodeType() == NodeType.NO_THROWS) {
return null;
} else {
Branch list = (Branch)b.getSubnode(0);
if (list.countSubnodes() == 0) {
return null;
}
int count = 0;
for (int i = 0; i < list.countSubnodes(); i++) {
if (list.getSubnode(i).getNodeType() != NodeType.COMMA) {
count++;
}
}
TypeModel[] result = new TypeModel[count];
count = 0;
for (int i = 0; i < list.countSubnodes(); i++) {
if (list.getSubnode(i).getNodeType() != NodeType.COMMA) {
result[count] = getOwner().resolveTypeReference(list.getSubnode(i));
if (result[count] == null) {
list.getSubnode(i).annotate(ErrorType.INTERNAL_ERROR, "Failed to resolve type reference");
//throw new Error("TODO BAD");
}
count++;
}
}
return result;
}
}
public int countExceptions() {
if (exceptions == null) {
return 0;
} else {
return exceptions.length;
}
}
public TypeModel getException(int i) {
return exceptions[i];
}
@Override
public MemberCategory getCategory() {
return MemberCategory.METHOD;
}
public boolean isConstructor() {
return getName().equals("this");/*return getSource().getNodeType() == NodeType.CONSTRUCTOR_DECLARATION;*/
}
public ParameterSet getParameters() {
return parameters;
}
/**
* If this is an instance constructor, checks if it's body begins with a call to another/super constructor and
* marks that constructor's location as approved.
* @return true if this is an instance constructor starting with an explicit (and now marked "approved")
* call to another/super constructor, otherwise false.
*/
public boolean checkExplicitBaseConstructorCall(boolean includingThisConstructorCall) {
if (isConstructor() && hasBody()) {
if (getBody() instanceof BlockStatement) {
BlockStatement b = (BlockStatement) getBody();
if (b.countInnerStatements() > 0) {
StatementModel first = b.getInnerStatement(0);
if (first instanceof ExpressionStatement) {
ExpressionModel expr = ((ExpressionStatement)first).getExpression();
if (expr instanceof ThisConstructorCallExpression) {
((ThisConstructorCallExpression)expr).setApprovedLocation(true);
if (includingThisConstructorCall) {
return true;
}
} else if (expr instanceof SuperConstructorCallExpression) {
((SuperConstructorCallExpression)expr).setApprovedLocation(true);
return true;
}
}
}
}
}
return false;
}
public boolean isStaticInitialisation() {
return getSource() != null && getSource().getNodeType() == NodeType.STATIC_CONSTRUCTOR_DECLARATION;
}
public boolean hasBody() {
if (body != null) {
return true;
} else if (isSynthetic()) {
return body != null;
} else {
return getSource().getSubnode(getSource().countSubnodes() - 1).getNodeType() == NodeType.METHOD_BODY;
}
}
public BlockStatement getBody() {
return body;
}
public boolean hasReturnType() {
return returnType != null;
}
public TypeModel getReturnType() {
return returnType;
}
@Override
public void dump(Reporter reporter, String indent, String incr) {
// TODO Auto-generated method stub
super.dump(reporter, indent, incr);
if (hasReturnType()) {
reporter.note("DUMP", indent + incr + "> Return type is " + getReturnType());
} else {
reporter.note("DUMP", indent + incr + "[no/void return type]");
}
if (parameters.countParameters() == 0) {
reporter.note("DUMP", indent + incr + "[no parameters]");
} else {
reporter.note("DUMP", indent + incr + "> " + parameters.countParameters() + " parameters:");
}
for (int i = 0; i < parameters.countParameters(); i++) {
parameters.getParameter(i).dump(reporter, indent + incr + incr, incr);
}
if (hasBody()) {
reporter.note("DUMP", indent + incr + "> Method body: ...");
//body.dump(reporter, indent + incr + incr, incr);
} else {
reporter.note("DUMP", indent + incr + "[no method body]");
}
}
@Override
public boolean isStatic() {
return super.isStatic() /*|| isConstructor()*/ || isStaticInitialisation();
}
//@Override
public TypeModel resolveType(Node typeReference) {
return ((UserTypeModel)getOwner()).resolveTypeReference(typeReference);
}
//@Override
public MethodModel getMethodOrField() {
return this;
}
//@Override
public InnerTypeScope getTypeScope() {
return getOwner();
}
//@Override
public Named lookupSimpleName(String name) {
if (parameters != null && parameters.hasParameter(name)) {
return parameters.getParameter(name);
}
return getOwner().lookupSimpleName(name);
}
public MethodModel[] lookupSimpleMethod(String name) {
return getOwner().lookupSimpleMethod(name);
}
public String signatureString() {
String result = getName();
result += "(";
if (parameters != null) {
for (int i = 0; i < parameters.countParameters(); i++) {
if (i != 0) {
result += ",";
}
ParameterModel m = parameters.getParameter(i);
result += m.getStorageType().fullName();
}
}
result += ")";
if (returnType != null) {
result += ":" + returnType.fullName();
}
return result;
}
@Override
public String toString() {
return getOwner().toString() + "." + signatureString();
}
public boolean overrides(MethodModel other) {
//System.err.println("Checking if " + this + " overrides " + other + "...");
// Perform obvious checks first - if names don't match we don't override
if (!getName().equals(other.getName())) {
//System.err.println("Doesn't override - names don't match");
return false;
}
// If other type isn't an ancestor of this type we don't override
if (!getOwner().inherits(other.getOwner())) {
//System.err.println("Doesn't override - doesn't fit hierarchy");
return false;
}
// In the special case of constructors, they will be considered to logically override any inherited constructors
if (isConstructor()) {
//System.err.println("Does kind-of override - is constructor");
return true;
}
// If names/types can lead to a potential override, we need to check parameters/return types
if (!canOverrideParameters(other.parameters)) {
//System.err.println("Doesn't override - parameters don't match");
return false;
}
if (!canOverrideReturn(other.returnType)) {
//System.err.println("Doesn't override - return types are incompatible");
return false;
}
//System.err.println("Signatures match so it does override");
return true;
}
public boolean canOverrideParameters(ParameterSet other) {
if (parameters.countParameters() != other.countParameters()) {
//System.err.println("Patemeter counts don't match");
return false;
}
for (int i = 0; i < parameters.countParameters(); i++) {
ParameterModel thisp = parameters.getParameter(i);
ParameterModel otherp = other.getParameter(i);
if (thisp.getStorageType() != otherp.getStorageType()) {
//System.err.println("Storage types " + thisp.getStorageType() + " and " + otherp.getStorageType() + " don't match");
return false;
/*if (!(thisp.getStorageType() instanceof UserTypeModel) || !(otherp.getStorageType() instanceof UserTypeModel)) {
return false;
}*/
/*if (!thisp.getStorageType().isAssignableFrom(otherp.getStorageType())) {
return false;
}*/
}
}
// If nothing mismatched we can override
return true;
}
public boolean canOverrideReturn(TypeModel otherReturnType) {
if (returnType == otherReturnType) {
return true;
} else if (returnType != null && otherReturnType != null) {
return otherReturnType.isAssignableFrom(returnType);
} else {
return false;
}
}
@Override
public int resolveExpressions() {
if (hasBody()) {
return getBody().resolveExpressions();
} else {
return 0;
}
}
public boolean isApplicableToArguments(Arguments arguments, boolean allowVarargs) {
ExpressionModel[] exprs = arguments.getSubexpressions();
if ((!allowVarargs || !parameters.isVarargs()) && exprs.length != parameters.countParameters()) {
return false;
} else if (allowVarargs && parameters.isVarargs() && exprs.length < parameters.countNonVarargParameters()) {
return false;
}
for (int i = 0; i < exprs.length; i++) {
ExpressionResult r = exprs[i].getResult();
if (r.resolvesToValueOrNull()) {
if (r.getKind() == ExpressionResult.Kind.NULL) {
if (!parameters.getParameterType(i, allowVarargs).isObjectType()) {
return false;
}
} else {
if (!parameters.getParameterType(i, allowVarargs).isAssignableFrom(r.getValueType())) {
return false;
}
}
} else {
return false;
}
}
/* If nothing was a mismatch, it should be good to go! */
return true;
}
public boolean isLeftAssignableFromRight(ParameterSet left, ParameterSet right, boolean allowVarargs) {
if (left.countParameters() != right.countParameters()) {
return false;
}
for (int i = 0; i < left.countParameters(); i++) {
if (!left.getParameterType(i, allowVarargs).isAssignableFrom(right.getParameterType(i, allowVarargs))) {
return false;
}
}
return true;
}
public int compareApplicabilityOfArguments(MethodModel other, Arguments arguments, boolean allowVarargs) {
if (isLeftAssignableFromRight(this.parameters, other.parameters, allowVarargs)) {
return -1;
} else if (isLeftAssignableFromRight(other.parameters, this.parameters, allowVarargs)) {
return 1;
} else {
return 0;
}
}
public static int compareApplicabilityOfArguments(MethodModel[] lhs, MethodModel[] rhs, Arguments arguments, boolean allowVarargs) {
if (lhs.length == 0 && rhs.length > 0) {
return 1;
} else if (rhs.length == 0 && lhs.length > 0) {
return -1;
}
for (int i = 0; i < lhs.length; i++) {
for (int j = 0; i < rhs.length; j++) {
int comp = lhs[i].compareApplicabilityOfArguments(rhs[j], arguments, allowVarargs);
if (comp != 0) {
return comp;
}
}
}
return 0;
}
public static MethodModel[] addArrays(MethodModel[] a, MethodModel[] b) {
MethodModel[] c = new MethodModel[a.length + b.length];
for (int i = 0; i < c.length; i++) {
c[i] = i < a.length ? a[i] : b[i - a.length];
}
return c;
}
public static MethodModel[] arrayWithout(MethodModel[] a, MethodModel b) {
MethodModel[] c = new MethodModel[0];
for (int i = 0; i < a.length; i++) {
if (a[i] != b) {
c = addArrays(c, new MethodModel[] {a[i]});
}
}
return c;
}
public static boolean arrayContains(MethodModel[] a, MethodModel b) {
for (int i = 0; i < a.length; i++) {
if (a[i] == b) {
return true;
}
}
return false;
}
/**
* Returns a new array of MethodModel instances where any duplicate entries or those
* made irrelevant by others in the list which override them are removed.
*
* <p/>
* This shouldn't normally be used directly, but is used internally by findApplicableMethods/bestApplicableMethod.
* @param availableMethods
* @return
*/
public static MethodModel[] findRelevantMethods(MethodModel[] availableMethods, boolean checkOverrides) {
MethodModel[] result = new MethodModel[0];
for (int i = 0; i < availableMethods.length; i++) {
MethodModel thisMethod = availableMethods[i];
boolean willAdd = true;
for (int j = 0; j < availableMethods.length; j++) {
if (checkOverrides && i != j) {
MethodModel otherMethod = availableMethods[j];
if (otherMethod.overrides(thisMethod)) {
//System.err.println("Method " + thisMethod + " is overridden by " + otherMethod + " so will be ignored");
willAdd = false;
}
}
}
// Avoid adding any already-existing results so this method can also remove duplicates
if (willAdd && !arrayContains(result, availableMethods[i])) {
//System.err.println("Method " + thisMethod + " will be considered");
result = addArrays(result, new MethodModel[] {availableMethods[i]});
}
}
return result;
}
/**
* Narrows down a list of available methods/constructors (i.e. which have been looked up by name) to
* those which are applicable to the given arguments. This also avoids adding any methods which are
* overridden by a method already on the list.
*
* <p/>
* This shouldn't normally be used directly, instead bestApplicableMethods should be used to get a
* reduced list.
*
* @param availableMethods
* @param arguments
* @return
*/
public static MethodModel[] findApplicableMethods(MethodModel[] availableMethods, Arguments arguments, boolean allowVarargs) {
// Begin by removing any duplicate methods
// (note, overridden methods should already be excluded UNLESS they also occur in scope
// actually, that probably never happens anyway. Doesn't seem to be an issue?)
availableMethods = findRelevantMethods(availableMethods, false);
MethodModel[] result = new MethodModel[0];
for (int i = 0; i < availableMethods.length; i++) {
//System.err.println("Checking if " + availableMethods[i] + " matches " + arguments);
if (availableMethods[i].isApplicableToArguments(arguments, allowVarargs)) {
//System.err.println("Match.");
result = addArrays(result, new MethodModel[] {availableMethods[i]});
//System.err.println("Result length: " + result.length);
} else {
//System.err.println("No match.");
}
}
return result;
}
/**
* Returns an array of the "most applicable" methods (based on the arguments) from an array of
* available choices.
*
* <p/>
* This method first calls findApplicableMethods to limit to those which are applicable.
* If no methods are applicable, an empty array is returned. If more than one are applicable, then
* this method will try to select the "most applicable" from those, ideally returning exactly
* one method but including any ambiguous ones if necessary.
*
* @param availableMethods
* @param arguments
* @return
*/
public static MethodModel[] bestApplicableMethods(MethodModel[] availableMethods, Arguments arguments, boolean allowVarargs) {
MethodModel[] applicable = findApplicableMethods(availableMethods, arguments, allowVarargs);
/* If there's only one applicable method (or none) then we don't have to do any more work to
* choose between them.
*/
if (applicable.length < 2) {
return applicable;
}
MethodModel[] mostApplicable = new MethodModel[0];
for (int i = 0; i < applicable.length; i++) {
if (mostApplicable.length == 0) {
mostApplicable = new MethodModel[] {applicable[i]};
} else {
MethodModel[] newMostApplicable = new MethodModel[0];
for (int j = 0; j < mostApplicable.length; j++) {
int comp = applicable[i].compareApplicabilityOfArguments(mostApplicable[j], arguments, allowVarargs);
if (comp < 0) {
newMostApplicable = addArrays(newMostApplicable, new MethodModel[] {mostApplicable[j]});
} else if (comp > 0) {
newMostApplicable = addArrays(newMostApplicable, new MethodModel[] {applicable[i]});
} else {
newMostApplicable = addArrays(newMostApplicable, new MethodModel[] {mostApplicable[j], applicable[i]});
}
}
mostApplicable = newMostApplicable;
}
/*
MethodModel[] newArray = new MethodModel[] {applicable[i]};
int slangc = compareApplicabilityOfArguments(mostApplicable, newArray, arguments);
if (slangc < 0) {
mostApplicable = newArray;
} else if (slangc > 0) {
// the existing ones are more applicable, no further action is required
} else {
// they are equally applicable, so we add the two lists together
mostApplicable = addArrays(mostApplicable, newArray);
}
*/
}
return mostApplicable;
}
public MethodSignature getMethodSignature() {
TypeSignature[] argsigs = new TypeSignature[parameters.countParameters()];
for (int i = 0; i < argsigs.length; i++) {
argsigs[i] = parameters.getParameter(i).getStorageType().getTypeSignature();
}
MethodSignature.Kind kind;
if (isStaticInitialisation()) {
kind = MethodSignature.Kind.STATIC_INIT;
} else if (isConstructor()) {
kind = MethodSignature.Kind.CONSTRUCTOR;
} else if (getOwner().getTypeType() == TypeType.INTERFACE || getOwner().getTypeType() == TypeType.INTERFACE_TEMPLATE) {
kind = isStatic() ? MethodSignature.Kind.STATIC_METHOD : MethodSignature.Kind.INTERFACE_METHOD;
} else {
kind = isStatic() ? MethodSignature.Kind.STATIC_METHOD : MethodSignature.Kind.INSTANCE_METHOD;
}
return new MethodSignature(getOwner().getTypeSignature(), kind, returnType == null ? TypeSignature.VOID : returnType.getTypeSignature(), getName(), argsigs);
}
public int countParameters() {
return parameters.countParameters();
}
public ParameterModel getParameter(int indexFromLeft) {
return parameters.getParameter(indexFromLeft);
}
private List<LocalStorageModel> getLocals() {
if (locals == null) {
locals = new List<LocalStorageModel>();
for (int i = 0; i < countParameters(); i++) {
locals.append(parameters.getParameter(i));
}
}
return locals;
}
public int countLocals() {
return getLocals().count();
}
void localAdded(LocalVariableSlot l) {
getLocals().append(l);
}
@Override
public int getFlags() {
int f = super.getFlags();
if (isConstructor() || isStaticInitialisation()) {
f |= Flags.MASK_CONSTRUCTOR;
}
if (isSynthetic()) {
f |= Flags.MASK_SYNTHETIC;
}
return f;
}
public MethodModel getPrototype() {
return prototype;
}
}