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 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. * *

* 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. * *

* 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. * *

* 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 getLocals() { if (locals == null) { locals = new List(); 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; } }