#1438 How have I offended the 1.0.58 compiler?

yachris Sat 5 Mar 2011

This works under 1.0.56, and fails under 1.0.58.

The code:

// Class which implements SingletonMap

using concurrent

const class SingletonMap
{
   static const Duration waitTime := 20000ms

   static SingletonMap getInstance()
   {
      return instance
   }

   Obj? getValueForKey(Str table, Str name)
   {
      return actor.send([table, name].toImmutable).get(waitTime)
   }

   Void setValueForKey(Str table, Str name, Obj? val)
   {
      actor.send([table, name, val].toImmutable)
   }

   Str[] getKeysForTable(Str table)
   {
      actor.send([table].toImmutable).get(waitTime)
   }

   internal Void resetForTestingOnly()
   {
      actor.send(null)
   }

   private new make() 
   {
      actor = Actor(ActorPool()) |msg| { receive(msg) }
   }

   private Obj? receive(Obj? msg)
   {
      Str:Obj? map := Actor.locals.getOrAdd("__map") { Str:Obj[:] }
      if (msg == null)
      {
         Actor.locals.keys.each |key|
         {
            Actor.locals[key] = null
         }
         return null
      }
      else if (msg->size  == 1)
      {
         tableName := msg->get(0)
         Str:Obj? innerMap := map.getOrAdd(tableName) { Str:Obj[:] }
         return innerMap.keys 
      }
      else if (msg->size  == 2)
      {
         tableName := msg->get(0)
         Str:Obj? innerMap := map.getOrAdd(tableName) { Str:Obj[:] }
         return innerMap[msg->get(1)]
      }
      else 
      {
         tableName := msg->get(0)
         Str:Obj? innerMap := map.getOrAdd(tableName) { Str:Obj[:] }
         return innerMap.set(msg->get(1), msg->get(2))
      }
   }

   private const static SingletonMap instance := make 
   private const Actor actor
}

The tests:

// Test the SingletonMap class

internal class SingletonMapTest : Test
{
   override Void setup()
   {
      a := SingletonMap.getInstance
      a.resetForTestingOnly
   }

   Void testFactoryProducesOnlyOneInstance()
   {
      a := SingletonMap.getInstance()
      b := SingletonMap.getInstance()
      verifySame(a, b)
   }

   Void testStoreAndRetrieveValues()
   {
      a := SingletonMap.getInstance()
      verifyEq(null, a.getValueForKey("Vars", "foo"))
      a.setValueForKey("Vars", "foo", 11.1f)
      verifyEq(11.1f, a.getValueForKey("Vars", "foo"))
      verifyEq(null, a.getValueForKey("Vars", "bar"))
      a.setValueForKey("Vars", "bar", 12.3f)
      verifyEq(11.1f, a.getValueForKey("Vars", "foo"))
      verifyEq(12.3f, a.getValueForKey("Vars", "bar"))
   }

   Void testStoreAndRetrieveValuesInDifferentTables()
   {
      a := SingletonMap.getInstance()
      verifyEq(null, a.getValueForKey("Vars", "foo"))
      a.setValueForKey("Vars", "foo", 11.1f)
      verifyEq(null, a.getValueForKey("Consts", "foo"))
      a.setValueForKey("Consts", "foo", 13.5f)
      verifyEq(11.1f, a.getValueForKey("Vars", "foo"))
      verifyEq(13.5f, a.getValueForKey("Consts", "foo"))
   }

   Void testGetAllTableKeys()
   {
      a := SingletonMap.getInstance()
      a.setValueForKey("Vars", "foo", 11.1f)
      a.setValueForKey("Vars", "bar", 11.1f)
      a.setValueForKey("Consts", "foo", 13.5f)
      a.setValueForKey("Consts", "baz", 13.5f)
      a.setValueForKey("Consts", "quux", 13.5f)
      verifyEq(["bar", "foo"], a.getKeysForTable("Vars").sort())
      verifyEq(["baz", "foo", "quux"], a.getKeysForTable("Consts").sort())
   }

   Void testResetForTesting()
   {
      a := SingletonMap.getInstance()
      verifyEq(null, a.getValueForKey("Vars", "foo"))
      a.setValueForKey("Vars", "foo", 11.1f)
      verifyEq(null, a.getValueForKey("Consts", "foo"))
      a.setValueForKey("Consts", "foo", 13.5f)
      verifyEq(11.1f, a.getValueForKey("Vars", "foo"))
      a.resetForTestingOnly()
      verifyEq(null, a.getValueForKey("Vars", "foo"))
      verifyEq(null, a.getValueForKey("Consts", "foo"))
   }
}

My fan -version output:

$ fan -version
Fantom Launcher
Copyright (c) 2006-2011, Brian Frank and Andy Frank
Licensed under the Academic Free License version 3.0

Java Runtime:
  java.version:    1.6.0_22
  java.vm.name:    Java HotSpot(TM) 64-Bit Server VM
  java.vm.vendor:  Apple Inc.
  java.vm.version: 17.1-b03-307
  java.home:       /System/Library/Java/JavaVirtualMachines/1.6.0.jdk/Contents/Home
  fan.platform:    macosx-x86_64
  fan.version:     1.0.58
  fan.env:         sys::BootEnv
  fan.home:        /Users/me/fantom-1.0.58

brian Sat 5 Mar 2011

It doesn't really have anything to do with the compiler actually. I believe your problem is caused by a fix to how Map handles null values. In build 1.0.57 we changed the behavior of containsKey to work correctly if the value if null. See #1356.

So consider this code:

private Obj? receive(Obj? msg)
{
   Str:Obj? map := Actor.locals.getOrAdd("__map") { Str:Obj[:] }

What is happening is that "__map" has actually be already mapped to null. So it does a get, not an add (with the result being null). But then you are trying to assign null to a non-nullable variable so NullErr is thrown.

vkuzkokov Sat 5 Mar 2011

There is also a potential problem not covered by tests: You always create Str:Obj[:], while Str:Obj?[:] (with question mark) is what, I think, you need there.

yachris Sun 6 Mar 2011

@brian

I believe your problem is caused by a fix to how Map handles null values.

Bingo, that was it, thanks.

@vkuzkokov

There is also a potential problem not covered by tests: You always create Str:Obj[:], while Str:Obj?[:] (with question mark) is what, I think, you need there.

Good point -- I changed all the Str:Obj? declarations to Str:Obj, since this implements the "null is not an allowed value" idea. It's not a fantom map :-)

Login or Signup to reply.