#2453 js: MapType casting errs

SlimerDude Sat 5 Sep 2015

Hi, I found some differences in how Javascript handles nullable List and Map types:

Java:
Int[]#.fits(Int[]?#)          // --> false
[Int:Int]#.fits([Int:Int]?#)  // --> false

Javascript:
Int[]#.fits(Int[]?#)          // --> true
[Int:Int]#.fits([Int:Int]?#)  // --> true

And the following raises an Err in Javascript (but is fine in Java's runtime):

types := (Int?[]?) [,]
--> sys::CastErr: sys::Obj?[] cannot be cast to sys::Int?[]?

It may look inconspicuous but it also occurs when using default method params:

Void stuff(Int[]? list := [,]) {
   ...
}

...

stuff()  // --> Cast Err

Or even:

Void stuff(Int[]? list) {
   ...
}

...

list := [,]
stuff(list)  // --> Cast Err

SlimerDude Wed 9 Sep 2015

While converting an existing code base to run in Javascript, I've encountered this bug many times. It often manifests itself after using functional methods in Lists and Maps which, by default, return Obj?.

Example:

class Example {
  Int:Str strs := [:]

  Void main() {
    ints := Int:Int?[1:1, 2:2]

    strs = ints.map { it.toStr }
    // --> sys::CastErr: [sys::Int:sys::Obj?] cannot be cast to [sys::Int:sys::Str]
  }
}

The current workaround is, one-by-one, convert the functions to use a full signature:

class Example {
  Int:Str strs := [:]

  Void main() {
    ints := Int:Int?[1:1, 2:2]

    strs = ints.map |Int doh -> Str| { doh.toStr }
  }
}

andy Wed 9 Sep 2015

Ticket promoted to #2453 and assigned to andy

SlimerDude Wed 9 Sep 2015

I suspect this is the same down cast problem; when you call TabPane.tabs() in Javascript you get:

sys::CastErr: fwt::Widget[] cannot be cast to fwt::Tab[]

The following workaround patch fixes it:

diff -r a77e9070251b src/fwt/fan/TabPane.fan
--- a/src/fwt/fan/TabPane.fan	Fri Sep 04 09:11:51 2015 -0400
+++ b/src/fwt/fan/TabPane.fan	Wed Sep 09 23:34:30 2015 +0100
@@ -33,7 +33,7 @@
   ** Get the list of installed tabs.  Tabs are added and
   ** removed using normal `Widget.add` and `Widget.remove`.
   **
-  Tab[] tabs() { return Tab[,].addAll(children) }
+  Tab[] tabs() { children.map |kid->Tab| { kid } }
 
   **
   ** The currently selected index of `tabs`.

SlimerDude Tue 20 Oct 2015

A quick test and a fix for the JS casting bugs:

diff -r 978760e66cf1 src/sys/js/fan/Type.js
--- a/src/sys/js/fan/Type.js	Mon Oct 19 19:58:53 2015 -0400
+++ b/src/sys/js/fan/Type.js	Wed Oct 21 02:59:15 2015 +0100
@@ -852,7 +852,8 @@
 
   if (that instanceof fan.sys.MapType)
   {
-    return this.k.is(that.k) && this.v.is(that.v);
+    return ((this.k.qname() == "sys::Obj") || this.k.is(that.k)) &&
+           ((this.v.qname() == "sys::Obj") || this.v.is(that.v));
   }
   if (that instanceof fan.sys.Type)
   {
diff -r 978760e66cf1 src/testSys/fan/TypeTest.fan
--- a/src/testSys/fan/TypeTest.fan	Mon Oct 19 19:58:53 2015 -0400
+++ b/src/testSys/fan/TypeTest.fan	Wed Oct 21 02:59:15 2015 +0100
@@ -207,6 +207,13 @@
     map2 := (Int:Int[]?) Obj:Obj?[:]
     map3 := (Int:Int?[]?) Obj:Obj[:]
     map4 := (Int:Int?[]?) Obj:Obj?[:]
+    
+    // JS casting bug - see #2453    
+    // sys::CastErr: [sys::Int:sys::Obj?] cannot be cast to [sys::Int:sys::Str]
+    map5 := (Int:Str) Int:Int[1:1].map { it.toStr }
+    
+    // always worked okay
+    list5 := (Str[]) Int[1].map { it.toStr }
   }
 
   Void verifyFits(Type a, Type b, Bool expected)

andy Thu 22 Oct 2015

Ticket resolved in 1.0.68

That seems pretty safe - pushed

Login or Signup to reply.