Java interface compatibility.
Artem Gr <[email protected]> Sat, 16 Apr 2005 22:07:30 +0400
| Newsgroups | gmane.comp.lang.nice.devel |
|---|---|
| Message-ID | <[email protected]> |
Nice currently doesn't provide Java-compatible interfaces: when i declare a Nice interface, there is an interface class generated, but it is empty, not a single method. In the Nice manual one reads: "It is possible to use libraries developed in Nice in a Java program". I wonder, if it is possible to write a single Java-compatible OO library without using interfaces. Daniel said one day, about jar shell extensions, that "if every tool was build as a library and used interfaces, it would be trivial to add such extensions...". Ironically, Nice support for interfaces isn't currently enough to create libraries. The problem of not having method declarations in an interface is related to how Nice generate dispatch methods. For every Nice method, Nice seems to search for a single dispatch method place. This place might be the (first-argument) class for which the method was generated, or a special "dispatch" class. No additional "compatibility" dispatch methods generated: if dispatch method is located in the first-argument class, then it will be absent from the "dispatch" class, and vice versa. It is impossible to place dispatch methods into a Java interface class, and Nice can have only a single dispatch method per method, that is, it can't designate the dispatch method (but without a body) into an interface, and then designate another dispatch method into an implementing class, therefore Nice methods implementing a Nice interface are always placed into "dispatch". Since methods where placed into "dispatch" they aren't present in the class implementing the interface in question, but that is currently okay, since the interface is not filled with methods. Becouse of this single-dispatch principle, Nice have a special independed handling for cases when a Nice method implements an existing Java interface: in these cases Nice compiler will make an exception to the "single-dispatch principle" by compiling a separate dispatch method into every implementing Nice class. This unlucky separation, of having Java interface methods dispatched differently than Nice interface methods, unnecessary complicates the compiler and is a principle crack on the way of Java interoperability: multi-method problem, instead of being handled uniformely, have be tuned separately for these two branches. Inability of the first branch to copy dispatch methods (the so-called "single-dispatch principle") might probably further complicate multi-method tinkering, since changing dispatch method location should then break compatibility with existing code. Since lack of Java-compatibility interfaces in Nice is often a show-stopper for me, i've made a patch that introduce dispatch method copying into the mentioned Nice-methods dispatching branch. With this patch Nice will generate Java-compatible interfaces and additional dispatch methods for all classes implementing these interfaces. The Nice itself uses the "dispatch" class, as before, to invoke these methods, but now Java classes might use these methods too, in which case the methods are invoked thru the additional dispatch methods placed in the implementing classes. The patch currently passes the testsuite and compiler bootstrap. I post it here for initial review, since i'm not sure what additional problems it might impose, with multi-methods perhaps, and becouse i would like these two branches (Nice and Java methods) to be merged , which is beyond my abilities, but for wich i could make some preparations if further discussion will suggest something.
patch.txt
(text/plain, 10.8 KB)
Index: src/bossa/syntax/compilation.nice
===================================================================
RCS file: /cvsroot/nice/Nice/src/bossa/syntax/compilation.nice,v
retrieving revision 1.8
diff -u -r1.8 compilation.nice
--- src/bossa/syntax/compilation.nice 26 Mar 2005 15:33:02 -0000 1.8
+++ src/bossa/syntax/compilation.nice 16 Apr 2005 17:59:08 -0000
@@ -22,12 +22,10 @@
make("java.lang.Error").getDeclaredMethod("<init>",
[cast(gnu.bytecode.Type.string_type)]);
-public void compileNiceMethod(NiceMethod m,
- Stack<Alternative> sortedAlternatives,
- bossa.modules.Package module)
-{
- gnu.expr.LambdaExp lexp = m.getLambda();
-
+private void makeBody(NiceMethod m,
+ gnu.expr.LambdaExp lexp,
+ Stack<Alternative> sortedAlternatives){
+
// parameters of the alternative function are the same in each case,
// so we compute them just once
int arity = m.getArity();
@@ -37,15 +35,62 @@
params[rank++] = new gnu.expr.ThisExp(lexp.outerClass());
for(gnu.expr.Declaration param = lexp.firstDecl(); rank < arity; param = param.nextDecl())
params[rank++] = new gnu.expr.ReferenceExp(param);
-
+
gnu.expr.Expression body = dispatchNiceMethod
- (sortedAlternatives.iterator(),
- m.javaReturnType(), m.javaReturnType().isVoid(), params);
+ (sortedAlternatives.iterator(),
+ m.javaReturnType(), m.javaReturnType().isVoid(), params);
if (m.isMain())
body = beautifyUncaughtExceptions(body);
- nice.tools.code.Gen.setMethodBody(lexp, m.getContract().compile(body));
+ body = m.getContract().compile(body);
+ nice.tools.code.Gen.setMethodBody(lexp, body);
+}
+
+public void compileNiceMethod(NiceMethod m,
+ Stack<Alternative> sortedAlternatives,
+ bossa.modules.Package module)
+{
+ gnu.expr.LambdaExp lexp = m.getLambda();
+
+ makeBody( m, lexp, sortedAlternatives );
+
+ if(m.getArity() != 0){
+ ?NiceClass receiver = getNiceClass(m.getArgTypes()[0]);
+ if(receiver != null && receiver.isInterface() && ! ( m instanceof MethodWithDefault )){
+
+ // If alternative method is implementing an interface,
+ // then generate a dispatch method as a class member,
+ // for Java interface invocations to find the implementation.
+
+ String name = m.getName().toString();
+ let argTypes = m.javaArgTypes();
+ let retType = m.javaReturnType();
+ let fullName = m.getFullName();
+ gnu.expr.LambdaExp res = generateMethod
+ (name, argTypes, retType, m.getSymbols(), toplevel: true, member: true);
+ res.parameterCopies = cast(notNull(m.formalParameters()).getParameterCopies());
+ //makeBody( m, res, sortedAlternatives );
+ receiver.addJavaMethod(res);
+
+ for(a : sortedAlternatives){
+ ?NiceClass c = declaringClass(a);
+ if(c == null) continue;
+ println( "method " m " alternative " a );
+
+ res = generateMethod
+ (name, argTypes, retType, m.getSymbols(), toplevel: true, member: true);
+ res.parameterCopies = cast(notNull(m.formalParameters()).getParameterCopies());
+ res.addBytecodeAttribute(new gnu.bytecode.MiscAttr("id", fullName.getBytes()));
+ makeBody( m, res, sortedAlternatives );
+ c.addJavaMethod(res);
+ }
+ }
+ }
+}
+
+private void catchMe2(){ // bossa.syntax.catchMe2
+ println( "dispatchNiceMethod generated 'Message not understood'." );
}
private gnu.expr.Expression dispatchNiceMethod(Iterator<Alternative> sortedAlternatives,
@@ -145,13 +190,20 @@
}
}
-private NiceClass declaringClass(JavaMethod m, Alternative alt)
+private ?NiceClass declaringClass(Alternative alt)
{
?mlsub.typing.TypeConstructor firstArgument = alt.getPatterns()[0].getTC();
let def = getTypeDefinition(firstArgument);
if (def != null && def.getImplementation() instanceof NiceClass)
return cast(def.getImplementation());
+ return null;
+}
+
+
+private NiceClass declaringClass(JavaMethod m, Alternative alt)
+{
+ let cl = declaringClass(alt); if(cl != null) return cl;
// Explain that this cannot be done.
String msg = m + " is a native method.\n";
Index: src/bossa/syntax/dispatchTest.nice
===================================================================
RCS file: /cvsroot/nice/Nice/src/bossa/syntax/dispatchTest.nice,v
retrieving revision 1.7
diff -u -r1.7 dispatchTest.nice
--- src/bossa/syntax/dispatchTest.nice 14 Jan 2005 22:47:11 -0000 1.7
+++ src/bossa/syntax/dispatchTest.nice 16 Apr 2005 17:59:14 -0000
@@ -70,7 +70,7 @@
testMethod(m, sortedAlternatives, false);
if(bossa.util.Debug.codeGeneration)
- bossa.util.Debug.println("Generating dispatch function for "+m);
+ bossa.util.Debug.println("Generating Nice dispatch function for "+m);
compileNiceMethod(m, sortedAlternatives, module);
}
@@ -83,7 +83,7 @@
testMethod(m, sortedAlternatives, true);
if(bossa.util.Debug.codeGeneration)
- bossa.util.Debug.println("Generating dispatch function for " + m);
+ bossa.util.Debug.println("Generating Java dispatch function for " + m);
compileJavaMethod(m, sortedAlternatives, module);
}
Index: src/bossa/syntax/modifiers.nice
===================================================================
RCS file: /cvsroot/nice/Nice/src/bossa/syntax/modifiers.nice,v
retrieving revision 1.1
diff -u -r1.1 modifiers.nice
--- src/bossa/syntax/modifiers.nice 25 Nov 2004 19:28:18 -0000 1.1
+++ src/bossa/syntax/modifiers.nice 16 Apr 2005 17:59:18 -0000
@@ -37,6 +37,10 @@
short bits = 0;
short getBits() = bits;
+// {
+// if( this.getModifier( INTERFACE ) ) return bits | ABSTRACT;
+// return bits;
+// }
private void setBit( short bit )
{
@@ -125,6 +129,7 @@
void makeInterface( )
{
this.setModifier( INTERFACE );
+ this.setModifier( ABSTRACT );
}
boolean isInterface() = this.getModifier( INTERFACE );
@@ -151,7 +156,8 @@
if( this.getModifier( VOLATILE ) ) buf.append(" volatile");
if( this.getModifier( TRANSIENT ) ) buf.append(" transient");
if( this.getModifier( NATIVE ) ) buf.append(" native");
- if( this.getModifier( ABSTRACT ) ) buf.append(" abstract");
+ if( ! this.getModifier( INTERFACE ) )
+ if( this.getModifier( ABSTRACT ) ) buf.append(" abstract");
return buf.toString();
}
}
Index: src/bossa/syntax/niceMethod.nice
===================================================================
RCS file: /cvsroot/nice/Nice/src/bossa/syntax/niceMethod.nice,v
retrieving revision 1.28
diff -u -r1.28 niceMethod.nice
--- src/bossa/syntax/niceMethod.nice 7 Apr 2005 22:28:12 -0000 1.28
+++ src/bossa/syntax/niceMethod.nice 16 Apr 2005 17:59:32 -0000
@@ -147,7 +147,7 @@
// Only consider imported methods.
// Methods being compiled will be found in the direct way
// if they override this method.
- if (d.module == null || ! d.module.compiled())
+ if (maybeNull(d.module) == null || ! d.module.compiled())
return false;
return
@@ -344,6 +344,12 @@
(name, constraint, returnType, params, body, contract, isOverride, vis);
}
+public void catchMe( NiceMethod def, NiceClass receiver ){
+ println( "Interface method " def.getName() );
+ if( def instanceof MethodWithDefault )
+ println( "have implementation: " def.implementation );
+}
+
public gnu.expr.Expression getDispatchMethod(NiceMethod def, Module module)
{
String name = def.getName().toString();
@@ -358,12 +364,13 @@
else
{
receiver = getNiceClass(def.getArgTypes()[0]);
+// if( receiver != null && receiver.isInterface() ) def.catchMe( receiver );
if (receiver != null &&
(
- // JVM interfaces cannot contain code.
- receiver.isInterface()
- ||
+ receiver.isInterface()
+// (receiver.isInterface() && def instanceof MethodWithDefault)
+ ||
// For the moment, don't compile inside classes from another package
receiver.definition.module.pkg != def.module.pkg
||
@@ -417,7 +424,18 @@
// add unique information to disambiguate which method this represents
res.addBytecodeAttribute
(new gnu.bytecode.MiscAttr("id", fullName.getBytes()));
-
+
+// ?NiceClass iface = def.getArity() != 0 ? getNiceClass(def.getArgTypes()[0]) : null;
+// if( receiver == null && iface != null && iface.isInterface() && ! ( def instanceof MethodWithDefault ) ){
+// def.catchMe( iface );
+// gnu.expr.LambdaExp res2 = generateMethod
+// (name, argTypes, retType, def.getSymbols(), toplevel: true, member: true);
+// res2.parameterCopies = cast(notNull(def.formalParameters()).getParameterCopies());
+// //let m1 = module.pkg.addMethod( res, false );
+// //let m2 = iface.addJavaMethod( res2 );
+// iface.addJavaMethod( res2 );
+// }
+
if (receiver != null)
return receiver.addJavaMethod(res);
else
Index: src/gnu/bytecode/Method.java
===================================================================
RCS file: /cvsroot/nice/Nice/src/gnu/bytecode/Method.java,v
retrieving revision 1.9
diff -u -r1.9 Method.java
--- src/gnu/bytecode/Method.java 2 Mar 2004 20:21:11 -0000 1.9
+++ src/gnu/bytecode/Method.java 16 Apr 2005 18:00:03 -0000
@@ -241,7 +241,7 @@
throws java.io.IOException
{
- if (code == null)
+ if (code == null && !isAbstract())
throw new Error("Method "+this+" has no code");
//return;
Index: src/gnu/expr/LambdaExp.java
===================================================================
RCS file: /cvsroot/nice/Nice/src/gnu/expr/LambdaExp.java,v
retrieving revision 1.30
diff -u -r1.30 LambdaExp.java
--- src/gnu/expr/LambdaExp.java 20 Feb 2005 22:09:39 -0000 1.30
+++ src/gnu/expr/LambdaExp.java 16 Apr 2005 18:01:09 -0000
@@ -1525,13 +1525,17 @@
decl.var = null;
}
}
- comp.method.initCode();
- allocChildClasses(comp);
- allocParameters(comp);
- enterFunction(comp);
-
- compileBody(comp);
- compileEnd(comp);
+ if (comp.curClass.isInterface() && comp.method.isAbstract()){
+ System.out.println( "interface method " + comp.method + " - skipping code" );
+ }else{
+ comp.method.initCode();
+ allocChildClasses(comp);
+ allocParameters(comp);
+ enterFunction(comp);
+
+ compileBody(comp);
+ compileEnd(comp);
+ }
}
}
Index: testsuite/compiler/delme/example.nice
===================================================================
RCS file: testsuite/compiler/delme/example.nice
diff -N testsuite/compiler/delme/example.nice
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ testsuite/compiler/delme/example.nice 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,16 @@
+import testsuite;
+
+void _testOverride()
+{
+ compile(pkg: "a", """
+public class A {}
+public int foo(A a1, A a2) = 1;
+""");
+
+ compile(pkg: "b", imp: "a", """
+class B extends A {}
+foo(A a1, B b2) = 2;
+""", check: "foo(new A(), new B()) == 2");
+
+ run("b");
+}