bug
bug copied to clipboard
Raw types in Java .class files are sometimes not cooked, depending on symbol initialization order
Reproduction steps
// J.java
abstract class Base<P> {
}
class Sub<V> extends Base<PolyClass> {
}
class PolyClass<T> {
}
class J {
static void m(Base te) {}
}
// Test.scala
class Test {
// workaround 1: refer to Column before referring to ReferenceDataEnum.
// Then the call to symbolOf[Column].unsafeTypeParams in ClassFileParser
// tells it that it polymorphic, and hence the arg-less reference in the
// `extends TypedEnum<Column>` is a raw type which must be cooked.
//
// new PolyClass[AnyRef]()
// workaround 2: jointly compile J.java with Test.scala
def foo[A](sub: Sub[A]) = {
// error: type mismatch;
// found : <empty>.this.Sub[A&0]
// required: <empty>.this.Base[P]( forSome { type P })
J.m(sub)
}
}
$ javac -d /tmp/out /Users/jz/code/scala/test/files/pos/java-raw-f-bound/J.java && scalac --scala-version 2.13.8 -cp /tmp/out -d /tmp/out /Users/jz/code/scala/test/files/pos/java-raw-f-bound/*.scala -explaintypes -Ydebug
[running phase parser on Test.scala]
[running phase namer on Test.scala]
[running phase packageobjects on Test.scala]
[running phase typer on Test.scala]
/Users/jz/code/scala/test/files/pos/java-raw-f-bound/Test.scala:14: error: type mismatch;
found : <empty>.this.Sub[A&0]
required: <empty>.this.Base[P]( forSome { type P })
Note: A&0 <: lang.this.Object, but Java-defined class Base is invariant in type P.
You may wish to investigate a wildcard type such as `_ <: lang.this.Object`. (SLS 3.2.10)
J.m(rde)
^
warning: !!! HK subtype check on ? and <empty>.this.PolyClass, but both don't normalize to polytypes:
tp1=? BoundedWildcardType
tp2=[T]<empty>.this.PolyClass[T] PolyType
warning: !!! HK subtype check on ?0P and <empty>.this.PolyClass, but both don't normalize to polytypes:
tp1=?0P TypeVar
tp2=[T]<empty>.this.PolyClass[T] PolyType
2 warnings
1 error
Problem
Compilation should give the same result, irrespective of the order of initialization of symbols. In this case, that result should be successful.
Relevant comment from ClassFileParser
// we could avoid this if we eagerly created class type param symbols here to expose through the
// ClassTypeCompleter to satisfy the calls to rawInfo.typeParams from Symbol.typeParams. That would
// require a refactor of `sigToType`.
//
// We would also need to make sure that clazzTParams is populated before member type completers called sig2type.
clazz.initialize
ClassfileParser.sigToType::processClassType
// isMonomorphicType is false if the info is incomplete, as it usually is here
// so have to check unsafeTypeParams.isEmpty before worrying about raw type case below,
// or we'll create a boatload of needless existentials.
else if (classSym.isMonomorphicType || {classSym.rawInfo.load(classSym); classSym.unsafeTypeParams.isEmpty}) tp
else debuglogResult(s"raw type from $classSym") {
// raw type - existentially quantify all type parameters
classExistentialType(pre, classSym)
}
I looked a bit, tricky stuff :)
The comment about calling sym.initialize in parseClass brings up the idea to know type params before completing the ClassTypeCompleter. That sounds like a valid cleanup (no need to call initialize eagerly).
The issue here however is that PolyClass still has the lazy type before that (ClassfileLoader), so it would not help.
I don't have any other idea than your suggestion to complete classSym before checking for type params, but that causes cycles (I tried narrowing it down to ClassfileLoaders, but still).
So maybe we need to come from the other end, check where we run into an issue with the raw type and see if we can add cookJavaRawInfo / fullyInitializeSymbol somewhere along the way.
🤷