-
Notifications
You must be signed in to change notification settings - Fork 21
Defdef vparamss and symbol have inconsistent types #11884
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
Comments
A self-contained (i.e. not a repo clone) reproduction would be lovely, if possible. |
You can see the problem independently of Scala.js, using the reproduction above in a single file
which shows: [[syntax trees at end of mixin]] // Test.scala
package <empty>{type} {
sealed class StreamT extends Object {
<paramaccessor> private[this] val step: Object = _;
<stable> <accessor> <paramaccessor> def step(): Object = StreamT.this{StreamT}.step{Object};
def <init>(step: Object): StreamT = {
StreamT.this{StreamT}.step{Object} = step{Object}{Unit};
StreamT.super{Object}.<init>{()Object}(){Object};
(){Unit}
}{Unit}
};
sealed abstract class Step extends Object {
def <init>(): Step = {
Step.super{Object}.<init>{()Object}(){Object};
(){Unit}
}{Unit}
};
object Main extends Object {
implicit <synthetic> def AnyOps(actual: Object): Main$AnyOps = new Main$AnyOps{Main$AnyOps}{(actual: Object)Main$AnyOps}(actual{Object}){Main$AnyOps};
def main(args: Array[String]): Unit = Main.this{Main.type}.AnyOps{(actual: Object)Main$AnyOps}((scala.Predef.???{()Nothing}(){Nothing}: StreamT){StreamT}.step{()Object}(){Object}){Main$AnyOps}.mustMatch{(f: PartialFunction)Unit}(({
new <$anon: Function1>{anonfun$main$1}{()anonfun$main$1}(){anonfun$main$1}
}{anonfun$main$1}: PartialFunction){PartialFunction}){Unit};
def <init>(): Main.type = {
Main.super{Object}.<init>{()Object}(){Object};
(){Unit}
}{Unit}
};
implicit class Main$AnyOps extends Object {
def mustMatch(f: PartialFunction): Unit = {
f.applyOrElse{(x: Object, default: Function1)Object}(scala.Predef.???{()Nothing}(){Nothing}, scala.Predef.???{()Nothing}(){Nothing}){Object};
(){Unit}
}{Unit};
def <init>(actual: Object): Main$AnyOps = {
Main$AnyOps.super{Object}.<init>{()Object}(){Object};
(){Unit}
}{Unit}
};
@SerialVersionUID(value = 0) final <synthetic> class anonfun$main$1 extends scala.runtime.AbstractPartialFunction with java.io.Serializable {
final override def applyOrElse(x1: Object, default: Function1): Object = {
case <synthetic> val x1: Step = (((x1.$asInstanceOf{[T0]()T0}[Step]{()Step}(){Step}: Step){Step}: Step){Step}: Step){Step};
case4(){
matchEnd3{(x: Object)Object}(scala.Boolean.box{(x: Boolean)Boolean}(true{Boolean(true)}){Object}){Object}
}{Object};
matchEnd3(x: Object){
x{Object}
}{Object}
}{Object};
final def isDefinedAt(x1: Step): Boolean = {
case <synthetic> val x1: Step = (((x1{Step}: Step){Step}: Step){Step}: Step){Step};
case4(){
matchEnd3{(x: Boolean)Boolean}(true{Boolean(true)}){Boolean}
}{Boolean};
matchEnd3(x: Boolean){
x{Boolean}
}{Boolean}
}{Boolean};
final <bridge> <artifact> def isDefinedAt(x: Object): Boolean = anonfun$main$1.this{anonfun$main$1}.isDefinedAt{(x1: Step)Boolean}(x.$asInstanceOf{[T0]()T0}[Step]{()Step}(){Step}){Boolean};
final override <bridge> <artifact> def applyOrElse(x: Object, default: Function1): Object = anonfun$main$1.this{anonfun$main$1}.applyOrElse{(x1: Step, default: Function1)Object}(x.$asInstanceOf{[T0]()T0}[Step]{()Step}(){Step}, default{Function1}){Object};
def <init>(): <$anon: Function1> = {
anonfun$main$1.super{runtime.AbstractPartialFunction}.<init>{()scala.runtime.AbstractPartialFunction}(){scala.runtime.AbstractPartialFunction};
(){Unit}
}{Unit}
}
} In that print-out you can see that the first (non-bridge) definition of final override def applyOrElse(x1: Object, default: Function1): Object = { but it is called from the bridge as final override <bridge> <artifact> def applyOrElse(x: Object, default: Function1): Object =
anonfun$main$1.this{anonfun$main$1}.applyOrElse{(x1: Step, default: Function1)Object}(x.$asInstanceOf{[T0]()T0}[Step]{()Step}(){Step}, default{Function1}){Object}; which shows that the symbol of the non-bridge More fundamentally, this means that the |
Out of interest, it seems this isn't a new regression. I checked up to 2.10.7, because I couldn't get |
comment in a (closed-source) compiler plugin of mine:
certainly sounds related...? |
Yup, definitely sounds related. Nice catch! |
Looks like @lrytz knew this fact too in #3140 (comment) ? |
So ... does that mean that it's valid/expected that the symbols not be the same? Regardless, surely you would consider it a bug that they don't have the same types ( |
I would say the type in the |
It's obviously the |
I think this might be the same cause as #11817. |
I would say so. A type is just a type, it stands on its own, it can be cloned and still has the same meaning (unlike Symbols). For example scala> :power
scala> class C[T] { def f(x: T): Int = 0 }
scala> class D extends C[String]
scala> val f = typeOf[C[_]].member(TermName("f"))
f: $r.intp.global.Symbol = method f
scala> f.tpe.asSeenFrom(typeOf[D], f.owner)
res12: $r.intp.global.Type = (x: String)Int This is, in a way, still the type of A MethodType happens to use Symbols to represent its paramters, which is useful for representing (Name+Type) and is also useful for representing dependent types (scalac used De Bruijn indices before the named & default parameters refactoring).
I didn't really manage to follow through the example here, maybe you can clarify...? |
The problem is that, given the val sym = dd.symbol
println(sym.tpe.params.map(_.tpe))
// List(Step, Function1)
println(vparamss.flatten.map(_.symbol.tpe))
// List(Object, Function1) So the This can already be seen right after erasure, so erasure is clearly to blame here. This is extremely bad, because the full name of a method (its descriptor) is derived from the method symbol's parameters (it has to, since the descriptor has to be computed at call site as well), but the type of the formal parameters are derived from On the JVM, I think what allows it to survive is that:
So, it's chance, basically. In the Scala.js IR, the type of formal parameters and of references to local vars are all stored, and checked for consistency. This triggers the error in Scala.js. |
I see now, thanks. That looks like a bug, here's a minimization (I couldn't find one without involving object Main {
type Id[A] = A
def a: PartialFunction[Id[String], Boolean] = { case _ => true }
def b: PartialFunction[String, Boolean] = { case _ => true }
} With object Main extends Object {
def a(): PartialFunction = ({
new <$anon: Function1>{anonfun$a$1}{()anonfun$a$1}(){anonfun$a$1}
}{anonfun$a$1}: PartialFunction){PartialFunction};
def b(): PartialFunction = ({
new <$anon: Function1>{anonfun$b$1}{()anonfun$b$1}(){anonfun$b$1}
}{anonfun$b$1}: PartialFunction){PartialFunction};
}
final <synthetic> class anonfun$a$1 {
final override def applyOrElse(x1: Object, default: Function1): Object = ...
final <bridge> <artifact> def isDefinedAt(x: Object): Boolean = ...
}
final <synthetic> class anonfun$b$1 {
final override def applyOrElse(x1: String, default: Function1): Object = ...
final <bridge> <artifact> def isDefinedAt(x: Object): Boolean = ...
} |
@sjrd are you able to work around this? is this a "I thought you might want to know" type of ticket, or a "I really need this fixed" type ticket? |
@errikos is working on a workaround, but it is very very ugly. Usually we don't even have a choice. We have to work around everything scalac throws at us, because we need to support old versions of Scala. However, here it is triggered a) rarely ( So, the bottom line is: if it can be fixed in scalac, I could avoid introducing a several-dozen-LoC-long hack in Scala.js to work around a scalac bug in an obscure corner case. |
… aliases The issue occurs due to a bug in scalac, where Defdef vparamss and the respective symbol have inconsistent types. For more information, please see: scala/bug#11884
The issue occurs due to a bug in scalac, where Defdef vparamss and the respective symbol have inconsistent types. For more information, please see: scala/bug#11884
Yes, erasure is the problem 👍 It's natural to assume that the arguments of a type wouldn't matter for erasure. |
… signature. In some rare cases that involve Higher Kinded Types and type aliases, scalac produces DefDef's whose params' types do not match the method type. This is filed upstream as scala/bug#11884 We work around the issue by patching a posteriori the types of `js.ParamDef`s and their `js.VarRef`s to match the type advertised by the method type. In the entire test suite, the only method that requires a patch is the `PartialFunction`'s `applyOrElse` in the newly added test case.
… signature. In some rare cases that involve Higher Kinded Types and type aliases, scalac produces DefDef's whose params' types do not match the method type. This is filed upstream as scala/bug#11884 We work around the issue by patching a posteriori the types of `js.ParamDef`s and their `js.VarRef`s to match the type advertised by the method type. In the entire test suite, the only method that requires a patch is the `PartialFunction`'s `applyOrElse` in the newly added test case.
… signature. In some rare cases that involve Higher Kinded Types and type aliases, scalac produces DefDef's whose params' types do not match the method type. This is filed upstream as scala/bug#11884 We work around the issue by patching a posteriori the types of `js.ParamDef`s and their `js.VarRef`s to match the type advertised by the method type. In the entire test suite, the only method that requires a patch is the `PartialFunction`'s `applyOrElse` in the newly added test case.
Me and @sjrd stumbled upon this bug while trying to fix scala-js/scala-js#3953. It seems that
Defdef.vparamss
and the respectiveDefdef
's symbol have inconsistent types.Steps for reproduction:
Clone the master branch scala-js/scala-js@cc04be5 of Scala.js locally and replace
examples/helloworld/src/main/scala/helloworld/HelloWorld.scala
contents with the following:Then, open an SBT shell and execute:
Please note that the 2_12 suffix indicates the Scala version to use, so you can also use 2.11 or 2.13.
You should get the following error:
Here is the IR that is produced for the lambda inside the main method:
It seems that the
applyOrElse
method mangled name has the correct types (Step
,Function1
), but the actual parameter types are wrong (Any
,Function1
). The parameter types are extracted fromDefdef.vparamss
.The JVM bytecode also seems to have the correct type, which you can consult in SBT:
and then:
gives:
As @gzm0 pointed out in scala-js/scala-js#3953 (comment):
The text was updated successfully, but these errors were encountered: