scala3 icon indicating copy to clipboard operation
scala3 copied to clipboard

[scala.js] Dotty does not produce null/undefined property on anonymous class

Open exoego opened this issue 3 years ago • 7 comments

Compiler version

3.1.0

Minimized code

  • With Scala.js 1.8.0
trait Foo extends js.Object {
  var extra: js.UndefOr[String]
}

val anon1 = new js.Object() {
  var extra = "a"
}
val anon2 = new js.Object() {
  var extra = js.undefined
}
val anon3 = new js.Object() {
  var extra = null
}
val foo1 = new Foo {
   var extra = "a"
}
val foo2 = new Foo {
  var extra: js.UndefOr[String] = js.undefined
}
val foo3 = new Foo {
  var extra: js.UndefOr[String] = null
}
println(js.Object.hasProperty(anon1, "extra"))
println(js.Object.hasProperty(anon2, "extra"))
println(js.Object.hasProperty(anon3, "extra"))
println(js.Object.hasProperty(foo1, "extra"))
println(js.Object.hasProperty(foo2, "extra"))
println(js.Object.hasProperty(foo3, "extra"))

Output

On Scala 2.13.7 and 2.12.15,

true
true
true
true
true
true

However on Scala 3.1.0,

true
false
false
true
true
true

which means null or undefined property on anonymous class is missing.

val foo2 = new Foo {
  var extra: js.UndefOr[String] = js.undefined
}
val foo3 = new Foo {
  var extra: js.UndefOr[String] = null
}

Expectation

All properties should exist even if its value is null or undefined.

exoego avatar Dec 24 '21 08:12 exoego

Thanks for the report. I'm starting my vacation today, so I won't be able to look at it in the next two weeks.

I think you forgot the definition of Foo in your reproduction.

sjrd avatar Dec 24 '21 08:12 sjrd

Added Foo definition

exoego avatar Dec 24 '21 08:12 exoego

This appears to happen only when the type of the var is Unit or Null (whether inferred, or explicitly written). It does not seem to be related to having a supertrait, since I can also reproduce the issue when the trait declares the member but the class implements it with a more specific type that is explicitly Null or Unit.

Also, I can reproduce this with a named class, local or top-level. So it's also not related to anonymous classes.

The situations where this issue pops up are therefore summarized as:

  • A field of type Unit or Null in a non-native JS class.

sjrd avatar Jan 21 '22 11:01 sjrd

Related: constant-value fields are also lost:

class Bar extends Foo {
  final val extra = "foo"
}

sjrd avatar Jan 21 '22 11:01 sjrd

The culprits are https://github.com/lampepfl/dotty/blob/c99f6caa74e74a67dd42e8df6ede53c29cd7fce9/compiler/src/dotty/tools/dotc/transform/Memoize.scala#L151-L154 and https://github.com/lampepfl/dotty/blob/c99f6caa74e74a67dd42e8df6ede53c29cd7fce9/compiler/src/dotty/tools/dotc/transform/Memoize.scala#L162 They should not apply to members of non-native JS classes.

sjrd avatar Jan 21 '22 11:01 sjrd

Both cases are also problematic for @JSExportTopLevel vals and vars.

sjrd avatar Jan 21 '22 11:01 sjrd

@sjrd I also hit this issue when trying to implement an object comparison method like deepEquals(jsObjectA, jsObjectB) in Scala 3 + Scala.js.

xerial avatar Sep 19 '22 08:09 xerial