From c1adf5d0e7bb45828dcb6aee38d90b9dd70e25cb Mon Sep 17 00:00:00 2001 From: nickl- Date: Tue, 29 May 2018 08:36:28 +0200 Subject: [PATCH] Varargs method implementation. * added ELLIPSIS to the grammar and let FormalParameter receive isVarArgs token. * implemented Types.isSignatureVarargsAssignable for method signature comparison. * make type parameter an array of Type if isVarArgs * implement method invocation and appropriate type casting for isVarArgs --- src/main/java/bsh/BSHFormalParameter.java | 10 +- src/main/java/bsh/BSHFormalParameters.java | 2 + src/main/java/bsh/BSHMethodDeclaration.java | 10 +- src/main/java/bsh/BlockNameSpace.java | 4 +- src/main/java/bsh/BshMethod.java | 149 ++++++++++++------- src/main/java/bsh/NameSpace.java | 3 +- src/main/java/bsh/Types.java | 22 ++- src/main/jjtree/bsh.jjt | 9 +- src/test/java/bsh/TypeParametersTest.java | 6 +- src/test/resources/test-scripts/overload.bsh | 13 +- 10 files changed, 150 insertions(+), 78 deletions(-) diff --git a/src/main/java/bsh/BSHFormalParameter.java b/src/main/java/bsh/BSHFormalParameter.java index 21bdbc1df..5c1360e7e 100644 --- a/src/main/java/bsh/BSHFormalParameter.java +++ b/src/main/java/bsh/BSHFormalParameter.java @@ -27,6 +27,8 @@ package bsh; +import java.lang.reflect.Array; + /** A formal parameter declaration. For loose variable declaration type is null. @@ -38,6 +40,7 @@ class BSHFormalParameter extends SimpleNode // unsafe caching of type here public Class type; boolean isFinal = false; + boolean isVarArgs = false; BSHFormalParameter(int id) { super(id); } @@ -45,11 +48,11 @@ public String getTypeDescriptor( CallStack callstack, Interpreter interpreter, String defaultPackage ) { if ( jjtGetNumChildren() > 0 ) - return ((BSHType)jjtGetChild(0)).getTypeDescriptor( + return (isVarArgs ? "[" : "") + ((BSHType)jjtGetChild(0)).getTypeDescriptor( callstack, interpreter, defaultPackage ); else // this will probably not get used - return "Ljava/lang/Object;"; // Object type + return (isVarArgs ? "[" : "") +"Ljava/lang/Object;"; // Object type } /** @@ -63,6 +66,9 @@ public Object eval( CallStack callstack, Interpreter interpreter) else type = UNTYPED; + if (isVarArgs) + type = Array.newInstance(type, 0).getClass(); + return type; } } diff --git a/src/main/java/bsh/BSHFormalParameters.java b/src/main/java/bsh/BSHFormalParameters.java index 09c0a4412..bc13fc82c 100644 --- a/src/main/java/bsh/BSHFormalParameters.java +++ b/src/main/java/bsh/BSHFormalParameters.java @@ -38,6 +38,7 @@ class BSHFormalParameters extends SimpleNode Class [] paramTypes; int numArgs; String [] typeDescriptors; + boolean isVarArgs; BSHFormalParameters(int id) { super(id); } @@ -53,6 +54,7 @@ void insureParsed() for(int i=0; i creturnType; // Arguments private String [] paramNames; - private int numArgs; - private Class [] cparamTypes; + private int paramCount; + private Class [] cparamTypes; private Modifiers [] paramModifiers; // Scripted method body @@ -73,6 +77,7 @@ public class BshMethod implements Serializable { // Java Method, for a BshObject that delegates to a real Java method private Method javaMethod; private Object javaObject; + private boolean isVarArgs; // End method components @@ -83,11 +88,12 @@ public class BshMethod implements Serializable { this( method.name, method.returnType, method.paramsNode.getParamNames(), method.paramsNode.paramTypes, method.paramsNode.getParamModifiers(), method.blockNode, declaringNameSpace, modifiers ); + this.isVarArgs = method.isVarArgs; } BshMethod( - String name, Class returnType, String [] paramNames, - Class [] paramTypes, Modifiers [] paramModifiers, BSHBlock methodBody, + String name, Class returnType, String [] paramNames, + Class [] paramTypes, Modifiers [] paramModifiers, BSHBlock methodBody, NameSpace declaringNameSpace, Modifiers modifiers ) { this.name = name; @@ -95,7 +101,7 @@ public class BshMethod implements Serializable { this.paramNames = paramNames; this.paramModifiers = paramModifiers; if ( paramNames != null ) - this.numArgs = paramNames.length; + this.paramCount = paramNames.length; this.cparamTypes = paramTypes; this.methodBody = methodBody; this.declaringNameSpace = declaringNameSpace; @@ -127,10 +133,19 @@ loosely typed (untyped) arguments will be represented by null argument Note: bshmethod needs to re-evaluate arg types here This is broken. */ - public Class [] getParameterTypes() { return cparamTypes; } + public Class [] getParameterTypes() { + if (null == this.javaMethod) + return cparamTypes; + return this.javaMethod.getParameterTypes(); + } public String [] getParameterNames() { return paramNames; } public Modifiers [] getParameterModifiers() { return paramModifiers; } + public int getParameterCount() { + if (null == this.javaMethod) + return paramCount; + return this.javaMethod.getParameterCount(); + } /** Get the return type of the method. @return Returns null for a loosely typed return value, @@ -140,7 +155,11 @@ loosely typed (untyped) arguments will be represented by null argument Note: bshmethod needs to re-evaluate the method return type here. This is broken. */ - public Class getReturnType() { return creturnType; } + public Class getReturnType() { + if (null == this.javaMethod) + return creturnType; + return this.javaMethod.getReturnType(); + } public Modifiers getModifiers() { if (this.modifiers == null) @@ -148,7 +167,17 @@ public Modifiers getModifiers() { return this.modifiers; } - public String getName() { return name; } + public String getName() { + if (null == this.javaMethod) + return name; + return this.javaMethod.getName(); + } + + public boolean isVarArgs() { + if (null == this.javaMethod) + return isVarArgs; + return this.javaMethod.isVarArgs(); + } /** Invoke the declared method with the specified arguments and interpreter @@ -265,10 +294,9 @@ private Object invokeImpl( SimpleNode callerInfo, boolean overrideNameSpace ) throws EvalError { - Class returnType = getReturnType(); - Class [] paramTypes = getParameterTypes(); + Class returnType = getReturnType(); + Class [] paramTypes = getParameterTypes(); - // If null callstack if ( callstack == null ) callstack = new CallStack( declaringNameSpace ); @@ -276,8 +304,7 @@ private Object invokeImpl( argValues = new Object [] { }; // Cardinality (number of args) mismatch - if ( argValues.length != numArgs ) - { + if ( !isVarArgs() && argValues.length != getParameterCount() ) { /* // look for help string try { @@ -309,52 +336,73 @@ private Object invokeImpl( // should we do this for both cases above? localNameSpace.setNode( callerInfo ); + int lastParamIndex = getParameterCount() - 1; + Object varArgs = !isVarArgs() ? null + : Array.newInstance( + paramTypes[lastParamIndex].getComponentType(), + argValues.length-lastParamIndex); // set the method parameters in the local namespace - for(int i=0; i= lastParamIndex) ? lastParamIndex : i; + Class paramType = (isVarArgs() && k == lastParamIndex) + ? paramTypes[k].getComponentType() + : paramTypes[k]; + // Set typed variable - if ( paramTypes[i] != null ) - { + if ( null != paramType ) { try { - argValues[i] = - //Types.getAssignableForm( argValues[i], paramTypes[i] ); - Types.castObject( argValues[i], paramTypes[i], Types.ASSIGNMENT ); + argValues[i] = Types.castObject( + argValues[i], paramType, Types.ASSIGNMENT ); } catch( UtilEvalError e) { throw new EvalError( "Invalid argument: " - + "`"+paramNames[i]+"'" + " for method: " + + "`"+paramNames[k]+"'" + " for method: " + name + " : " + e.getMessage(), callerInfo, callstack ); } try { - localNameSpace.setTypedVariable( paramNames[i], - paramTypes[i], argValues[i], paramModifiers[i]); + if (isVarArgs() && i >= lastParamIndex) + Array.set(varArgs, i-k, Primitive.unwrap(argValues[i])); + else + localNameSpace.setTypedVariable( paramNames[k], + paramType, argValues[i], paramModifiers[k]); } catch ( UtilEvalError e2 ) { throw e2.toEvalError( "Typed method parameter assignment", callerInfo, callstack ); } - } - // Set untyped variable - else // untyped param - { + } else { // untyped param + // getAssignable would catch this for typed param if ( argValues[i] == Primitive.VOID) throw new EvalError( "Undefined variable or class name, parameter: " + - paramNames[i] + " to method: " + paramNames[k] + " to method: " + name, callerInfo, callstack ); - else - try { - localNameSpace.setLocalVariable( - paramNames[i], argValues[i], - interpreter.getStrictJava() ); - } catch ( UtilEvalError e3 ) { - throw e3.toEvalError( callerInfo, callstack ); - } + else try { + localNameSpace.setLocalVariable( + paramNames[k], argValues[i], + interpreter.getStrictJava() ); + } catch ( UtilEvalError e3 ) { + throw e3.toEvalError( "Typed method parameter assignment", + callerInfo, callstack ); + } } } + if (isVarArgs()) try { + localNameSpace.setTypedVariable( + paramNames[lastParamIndex], + paramTypes[lastParamIndex], + varArgs, + paramModifiers[lastParamIndex]); + } catch (UtilEvalError e1) { + throw e1.toEvalError("Typed method parameter assignment", + callerInfo, callstack); + } + // Push the new namespace on the call stack if ( !overrideNameSpace ) callstack.push( localNameSpace ); @@ -371,13 +419,12 @@ private Object invokeImpl( callstack.pop(); ReturnControl retControl = null; - if ( ret instanceof ReturnControl ) - { - retControl = (ReturnControl)ret; + if ( ret instanceof ReturnControl ) { + retControl = (ReturnControl) ret; // Method body can only use 'return' statement type return control. - if ( retControl.kind == retControl.RETURN ) - ret = ((ReturnControl)ret).value; + if ( retControl.kind == ReturnControl.RETURN ) + ret = retControl.value; else // retControl.returnPoint is the Node of the return statement throw new EvalError("'continue' or 'break' in method body", @@ -390,19 +437,15 @@ private Object invokeImpl( retControl.returnPoint, returnStack); } - if ( returnType != null ) - { + if ( returnType != null ) { // If return type void, return void as the value. if ( returnType == Void.TYPE ) return Primitive.VOID; // return type is a class try { - ret = - // Types.getAssignableForm( ret, (Class)returnType ); - Types.castObject( ret, returnType, Types.ASSIGNMENT ); - } catch( UtilEvalError e ) - { + ret = Types.castObject( ret, returnType, Types.ASSIGNMENT ); + } catch( UtilEvalError e ) { // Point to return statement point if we had one. // (else it was implicit return? What's the case here?) SimpleNode node = callerInfo; @@ -436,10 +479,10 @@ public boolean equals(Object o) { } if (o.getClass() == this.getClass()) { BshMethod m = (BshMethod)o; - if( !name.equals(m.name) || numArgs!=m.numArgs ) + if( !name.equals(m.name) || getParameterCount() != m.getParameterCount() ) return false; - for( int i=0; i cparamType : cparamTypes) { + for (final Class cparamType : getParameterTypes()) { h = h * 31 + (cparamType == null ? 0 : cparamType.hashCode()); } - return h + numArgs; + return h + getParameterCount(); } } diff --git a/src/main/java/bsh/NameSpace.java b/src/main/java/bsh/NameSpace.java index f616d7611..1faa15d0b 100644 --- a/src/main/java/bsh/NameSpace.java +++ b/src/main/java/bsh/NameSpace.java @@ -736,8 +736,7 @@ public void setTypedVariable(final String name, final Class type, * internal use. * @see Interpreter#source(String) * @see Interpreter#eval(String) */ - public void setMethod(BshMethod method) - throws UtilEvalError { + public void setMethod(BshMethod method) { String name = method.getName(); if (!this.methods.containsKey(name)) this.methods.put(name, new ArrayList(1)); diff --git a/src/main/java/bsh/Types.java b/src/main/java/bsh/Types.java index fe911e181..ec35d6eed 100644 --- a/src/main/java/bsh/Types.java +++ b/src/main/java/bsh/Types.java @@ -139,9 +139,27 @@ static boolean areSignaturesEqual(Class[] from, Class[] to) } private static boolean isSignatureVarargsAssignable( - Class[] from, Class[] to ) + Class[] from, Class[] to ) { - return false; + if (to.length == 0 || to.length > from.length) + return false; + int last = to.length - 1; + if (to[last] == null || !to[last].isArray()) + return false; + if (to.length == from.length + && from[last].isArray() + && !isJavaAssignable(to[last].getComponentType(), from[last].getComponentType())) + return false; + + if (!from[last].isArray()) + for (int i = last; i < from.length; i++) + if (!isJavaAssignable(to[last].getComponentType(), from[i])) + return false; + + for (int i = 0; i < last; i++) + if (!isJavaAssignable(to[i], from[i])) + return false; + return true; } /** diff --git a/src/main/jjtree/bsh.jjt b/src/main/jjtree/bsh.jjt index 3bd068ac8..9f552c69b 100644 --- a/src/main/jjtree/bsh.jjt +++ b/src/main/jjtree/bsh.jjt @@ -517,6 +517,7 @@ TOKEN : /* OPERATORS */ | < RSIGNEDSHIFTASSIGNX: "@right_shift_assign" > | < RUNSIGNEDSHIFTASSIGN: ">>>=" > | < RUNSIGNEDSHIFTASSIGNX: "@right_unsigned_shift_assign" > +| < ELLIPSIS: "..." > } @@ -685,14 +686,12 @@ void FormalParameters() #FormalParameters : void FormalParameter() #FormalParameter : { Token t; - Token s = null; } { - // added [] to Type for bsh. Removed [ final ] - is that legal? - LOOKAHEAD(2) [ s = "final" ] { - if ( s != null ) jjtThis.isFinal = true; - } Type() t= { jjtThis.name = t.image; } + LOOKAHEAD(2) [ "final" { jjtThis.isFinal = true; } ] + Type()[ { jjtThis.isVarArgs = true; } ] + t= { jjtThis.name = t.image; } | t= { jjtThis.name = t.image; } } diff --git a/src/test/java/bsh/TypeParametersTest.java b/src/test/java/bsh/TypeParametersTest.java index 92884bd1d..b5264404a 100644 --- a/src/test/java/bsh/TypeParametersTest.java +++ b/src/test/java/bsh/TypeParametersTest.java @@ -2,7 +2,6 @@ import org.junit.Rule; import org.junit.Test; -import org.junit.experimental.categories.Category; import org.junit.rules.ExpectedException; import org.junit.runner.RunWith; @@ -213,13 +212,12 @@ public void generics_wildcard_error_works() throws Exception { "lst" ); assertNotNull(ret); - assertEquals("List size is 1", 1, ((List)ret).size()); + assertEquals("List size is 1", 1, ((List)ret).size()); assertTrue(ret.getClass().getName(), ret instanceof List); } @Test - @Category(KnownIssue.class) - public void generics_arargs() throws Exception { + public void generics_varargs() throws Exception { final Object ret = eval( "public static void addToList (List listArg, T... elements) {", "for (T x : elements)", diff --git a/src/test/resources/test-scripts/overload.bsh b/src/test/resources/test-scripts/overload.bsh index b4f8c5a2d..42db12586 100644 --- a/src/test/resources/test-scripts/overload.bsh +++ b/src/test/resources/test-scripts/overload.bsh @@ -22,14 +22,23 @@ foo( Object o, String o2 ) { foo( String o, String o2 ) { return 6; } -foo() { +foo(String... a) { return 7; } +foo(String a, int... b) { + return 8; +} +foo() { + return 9; +} assert( foo("qbert") == 3 ); assert( foo( new Object() ) == 4 ); +assert( foo( new Object(), "foo" ) == 5 ); assert( foo( "foo", "bar" ) == 6 ); -assert( foo()==7 ); +assert( foo( "foo", "bar", "baz" ) == 7 ); +assert( foo( "foo", 1, 2 ) == 8 ); +assert( foo() == 9 ); //print (foo(99)); assert( foo(99) == 4 ); // int boxed to Object