Skip to content

Scala varargs implementation's use of Seq impairs Java interop #1342

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
scabug opened this issue Sep 16, 2008 · 14 comments
Closed

Scala varargs implementation's use of Seq impairs Java interop #1342

scabug opened this issue Sep 16, 2008 · 14 comments
Assignees
Milestone

Comments

@scabug
Copy link

scabug commented Sep 16, 2008

(No description for SI-1342.)

@scabug
Copy link
Author

scabug commented Sep 16, 2008

Imported From: https://issues.scala-lang.org/browse/SI-1342?orig=1
Reporter: @cunei

@scabug
Copy link
Author

scabug commented Nov 19, 2008

Bruno Bieth (mustaghattack) said:
This breaks java compatibility. Here is a simple example :

I have a java interface defined as follow :

interface JavaVarArgsInterface {
   void varArgsMethod( String ... args );
}

I want to implement it in scala now :

class ScalaVarArgsImpl extends JavaVarArgsInterface {
   def varArgsMethod( args : String*) {
        for( arg <- args ) println( arg )
   }
}

The compilation will pass but will generate the following class :

public class ScalaVarArgsImpl extends java.lang.Object implements JavaVarArgsInterface,scala.ScalaObject{
    public ScalaVarArgsImpl();
    public void varArgsMethod(scala.Seq);
    public int $$tag()       throws java.rmi.RemoteException;
}

Which obviously doesn't implement properly the JavaArgsInterface.

With 2.7.1-final we would do the following :

class ScalaVarArgsImpl extends JavaVarArgsInterface {
   def varArgsMethod( args : Array[String]) {
        for( arg <- args ) println( arg )
   }
}

And it would compile as expected.

If you compile this with 2.7.2-final you'll get :

ScalaVarArgsImpl.scala:1: error: class ScalaVarArgsImpl needs to be abstract, since method varArgsMethod in trait JavaVarArgsInterface of type (java.lang.String*)Unit is not defined
class ScalaVarArgsImpl extends JavaVarArgsInterface {
      ^
one error found

@scabug
Copy link
Author

scabug commented Jul 31, 2009

@paulp said:
See also #2220.

@scabug
Copy link
Author

scabug commented Oct 23, 2009

Stefan Endrullis (xylo) said:
I think this is one of the last major interoperability problems between Java and Scala and IMO it's by far the worst one, since it restricts you in the use of inheritance. You are only allowed to derive Scala classes from Java classes that don't have varargs methods. But I don't understand the problem, since Scala 2.7.1-final solved the problem already trivially by using arrays (write "foos: Array[Foo]" instead of "Foo... foos"). Or isn't it trivially? Why does the 2.8 compiler reject this solution?
I mean, I know it's not the smartest way to implement Java varargs methods in Scala by using array parameters, but at least it would solve the current problem that you're completely unable to derive Scala classes from such Java classes.

@scabug
Copy link
Author

scabug commented Oct 25, 2009

Ralph Apel (rapel) said:
Hi,

I coded the test case provided by mustaghattack.
I inserted a Thread.dumpStack at the error printout location in 2.8.0:RefChecks.scala, rebuilt and while compiling ScalaVarArgsImpl.scala with this modified scalac obtained
src/scala/ticket1342/ScalaVarArgsImpl.scala:3: error: class ScalaVarArgsImpl needs to be abstract, since method varArgsMethod in trait JavaVarArgsInterface of type (x$$1: <repeated...>[java.lang.String])Unit is not defined
class ScalaVarArgsImpl extends JavaVarArgsInterface {
^
java.lang.Exception: Stack trace
at java.lang.Thread.dumpStack(Thread.java:1206)
at scala.tools.nsc.typechecker.RefChecks$$RefCheckTransformer.abstractClassError$$1(RefChecks.scala:322)
at scala.tools.nsc.typechecker.RefChecks$$RefCheckTransformer$$$$anonfun$$checkAllOverrides$$1.apply(RefChecks.scala:347)
at scala.tools.nsc.typechecker.RefChecks$$RefCheckTransformer$$$$anonfun$$checkAllOverrides$$1.apply(RefChecks.scala:336)
at scala.collection.LinearSeqLike$$class.foreach(LinearSeqLike.scala:84)
at scala.collection.immutable.List.foreach(List.scala:29)
at scala.tools.nsc.typechecker.RefChecks$$RefCheckTransformer.checkAllOverrides(RefChecks.scala:336)
at scala.tools.nsc.typechecker.RefChecks$$RefCheckTransformer.transform(RefChecks.scala:928)
at scala.tools.nsc.ast.Trees$$Transformer.transformTemplate(Trees.scala:1532)
at scala.tools.nsc.ast.Trees$$Transformer$$$$anonfun$$transform$$2.apply(Trees.scala:1422)
at scala.tools.nsc.ast.Trees$$Transformer$$$$anonfun$$transform$$2.apply(Trees.scala:1421)
at scala.tools.nsc.ast.Trees$$Transformer.atOwner(Trees.scala:1556)
at scala.tools.nsc.ast.Trees$$Transformer.transform(Trees.scala:1420)
at scala.tools.nsc.typechecker.RefChecks$$RefCheckTransformer.transform(RefChecks.scala:1032)
at scala.tools.nsc.typechecker.RefChecks$$RefCheckTransformer.transformStat(RefChecks.scala:791)
at scala.tools.nsc.typechecker.RefChecks$$RefCheckTransformer$$$$anonfun$$6.apply(RefChecks.scala:709)
at scala.tools.nsc.typechecker.RefChecks$$RefCheckTransformer$$$$anonfun$$6.apply(RefChecks.scala:709)
at scala.collection.TraversableLike$$$$anonfun$$flatMap$$1.apply(TraversableLike.scala:167)
at scala.collection.TraversableLike$$$$anonfun$$flatMap$$1.apply(TraversableLike.scala:167)
at scala.collection.LinearSeqLike$$class.foreach(LinearSeqLike.scala:84)
at scala.collection.immutable.List.foreach(List.scala:29)
at scala.collection.TraversableLike$$class.flatMap(TraversableLike.scala:167)
at scala.collection.immutable.List.flatMap(List.scala:29)
at scala.tools.nsc.typechecker.RefChecks$$RefCheckTransformer.transformStats(RefChecks.scala:709)
at scala.tools.nsc.ast.Trees$$Transformer$$$$anonfun$$transform$$1.apply(Trees.scala:1416)
at scala.tools.nsc.ast.Trees$$Transformer$$$$anonfun$$transform$$1.apply(Trees.scala:1416)
at scala.tools.nsc.ast.Trees$$Transformer.atOwner(Trees.scala:1556)
at scala.tools.nsc.ast.Trees$$Transformer.transform(Trees.scala:1415)
at scala.tools.nsc.typechecker.RefChecks$$RefCheckTransformer.transform(RefChecks.scala:1032)
at scala.tools.nsc.ast.Trees$$Transformer.transformUnit(Trees.scala:1549)
at scala.tools.nsc.transform.Transform$$Phase.apply(Transform.scala:31)
at scala.tools.nsc.Global$$GlobalPhase.applyPhase(Global.scala:337)
at scala.tools.nsc.Global$$GlobalPhase$$$$anonfun$$run$$1.apply(Global.scala:315)
at scala.tools.nsc.Global$$GlobalPhase$$$$anonfun$$run$$1.apply(Global.scala:315)
at scala.collection.Iterator$$class.foreach(Iterator.scala:536)
at scala.collection.mutable.ListBuffer$$$$anon$$1.foreach(ListBuffer.scala:280)
at scala.tools.nsc.Global$$GlobalPhase.run(Global.scala:315)
at scala.tools.nsc.Global$$Run.compileSources(Global.scala:807)
at scala.tools.nsc.Global$$Run.compile(Global.scala:892)
at scala.tools.nsc.Main$$.process(Main.scala:92)
at scala.tools.nsc.Main$$.main(Main.scala:106)
at scala.tools.nsc.Main.main(Main.scala)
one error found

Then I added the following printout to the nonPrivateMembers cycle in
// 2. Check that only abstract classes have deferred members
for both 2.8.0 and 2.7.6 RefChecks

    for (member <- clazz.tpe.nonPrivateMembers) {

println("member="+member)
println("member.isDeferred="+member.isDeferred)
println("!(clazz hasFlag ABSTRACT)="+(!(clazz hasFlag ABSTRACT)))
println("!isAbstractTypeWithoutFBound(member)="+(!isAbstractTypeWithoutFBound(member)))
println("(member hasFlag JAVA)="+(member hasFlag JAVA))
println("(javaErasedOverridingSym(member) != NoSymbol)="+(javaErasedOverridingSym(member) != NoSymbol))
println("!((member hasFlag JAVA) && (javaErasedOverridingSym(member) != NoSymbol))="+(!((member hasFlag JAVA) && (javaErasedOverridingSym(member) != NoSymbol))))
if (member.isDeferred && !(clazz hasFlag ABSTRACT) &&
!isAbstractTypeWithoutFBound(member) &&
!((member hasFlag JAVA) && javaErasedOverridingSym(member) != NoSymbol)) {
abstractClassError(
false, infoString(member) + " is not defined" + analyzer.varNotice(member))
} else if ((member hasFlag ABSOVERRIDE) && member.isIncompleteIn(clazz)) {
...

Here are the different printouts I obtained for 2.7.6 and 2.8.0

2.7.6:
member=method varArgsMethod
member.isDeferred=false
!(clazz hasFlag ABSTRACT)=true
!isAbstractTypeWithoutFBound(member)=true
(member hasFlag JAVA)=false
(javaErasedOverridingSym(member) != NoSymbol)=false
!((member hasFlag JAVA) && (javaErasedOverridingSym(member) != NoSymbol))=true

2.8.0:
member=method varArgsMethod
member.isDeferred=true
!(clazz hasFlag ABSTRACT)=true
!isAbstractTypeWithoutFBound(member)=true
(member hasFlag JAVA)=true
(javaErasedOverridingSym(member) != NoSymbol)=false
!((member hasFlag JAVA) && (javaErasedOverridingSym(member) != NoSymbol))=true

So, there are two relevant differences:

  1. 2.8.0 classifies varArgsMethod as isDeferred whereas 2.7.6 doesn't
  2. 2.8.0 classifies varArgsMethod as hasFlag JAVA whereas 2.7.6 doesn't

For me this issue popped up while trying to adapt netgents scala-scripting solution to 2.8.0.
Hope these observations help...

@scabug
Copy link
Author

scabug commented Oct 25, 2009

Ralph Apel (rapel) said:

Just had a better look at the printout for 2.8.0: varArgsMethod appears two times!!

1: member=method varArgsMethod member.isDeferred=false !(clazz hasFlag ABSTRACT)=true !isAbstractTypeWithoutFBound(member)=true (member hasFlag JAVA)=false (javaErasedOverridingSym(member) != NoSymbol?)=false !((member hasFlag JAVA) && (javaErasedOverridingSym(member) != NoSymbol?))=true

2: member=method varArgsMethod member.isDeferred=true !(clazz hasFlag ABSTRACT)=true !isAbstractTypeWithoutFBound(member)=true (member hasFlag JAVA)=true (javaErasedOverridingSym(member) != NoSymbol?)=false !((member hasFlag JAVA) && (javaErasedOverridingSym(member) != NoSymbol?))=true

(1) is coincident with the output from 2.7.6 and it seems to represent the method from ScalaVarArgsImpl?.scala

(2) seems to describe the abstract method from JavaVarArgsInterface?.java

How does the latter get into clazz.tpe.nonPrivateMembers ?

@scabug
Copy link
Author

scabug commented Oct 25, 2009

Ralph Apel (rapel) said:
Should the test in the nonPrivateMembers cycle (RefChecks.scala:335) ever be conducted on members whith hasFlag(JAVA) ?

@scabug
Copy link
Author

scabug commented Oct 25, 2009

@odersky said:
This will be an absolute pain to implement, I am afraid. Scala varargs are Seqs and arrays are not Seqs so I see no way to do this directly. IMO the only way to do it is to add a case for this into the generation of bridge methods in erasure, I'd be delighted if someone else did it, though.

@scabug
Copy link
Author

scabug commented Oct 25, 2009

Ralph Apel (rapel) said:
No arrays involved in possible 2.8.0:RefChecks.scala:335 solution.
Just something like
if (member.isDeferred && !(clazz hasFlag ABSTRACT) &&
!isAbstractTypeWithoutFBound(member) &&
!(member hasFlag JAVA) &&
!((member hasFlag JAVA) && javaErasedOverridingSym(member) != NoSymbol)) {
abstractClassError(
false, infoString(member) + " is not defined" + analyzer.varNotice(member))
} else if
The cycle on line 355 gets varArgsMethos TWICE

@scabug
Copy link
Author

scabug commented Oct 28, 2009

@odersky said:
I am afraid that would amount to shooting the messenger. That test is there for a reason. Without it you'd get an AbstractMethodError at runtime. I'm working on a solution which involves creating bridge methods between these different views.

@scabug
Copy link
Author

scabug commented Oct 28, 2009

@odersky said:
Ok, this should be fixed in r19320

@scabug
Copy link
Author

scabug commented Nov 11, 2009

Stefan Endrullis (xylo) said:
First, I want to thank you for implementing this feature!

I tried it already and it worked fine in my first test case. But there's still an open issue. Consider the following classes:

  • ScalaSeqInterface.java
public interface ScalaSeqInterface {
  public String f(String... seq);
}
  • ScalaSeq.scala
class ScalaSeq extends ScalaSeqInterface {
  def f(seq: String*): String = "asdf"
}
  • ScalaSeqTest.java
public class ScalaSeqTest {
  public static void main(String[] args) {
    System.out.println(((ScalaSeqInterface) new ScalaSeq()).f(""));  // compiles
    System.out.println(new ScalaSeq().f(""));                        // does not compile
  }
}

Why can't I use new ScalaSeq().f("") in a Java class? Why do I have to cast the Scala class first?

PS: But since there's a workaround (via casting) for this problem, there might be more urgent issues at the moment.

@scabug
Copy link
Author

scabug commented Nov 15, 2009

@SethTisue said:
see also #1459

@scabug
Copy link
Author

scabug commented Nov 15, 2009

@odersky said:
"Why can't I use new ScalaSeq().f("") in a Java class? Why do I have to cast the Scala class first?"

It's because Scala and Java use different representations for varargs. For Java it's an array whereas for Scala it's a Seq (or, more, precisely, a WrappedArray, which is a subclass of Seq.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants