$ 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
brianSat 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.
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.
vkuzkokovSat 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.
yachrisSun 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 :-)
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 -versionoutput: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[:], whileStr:Obj?[:](with question mark) is what, I think, you need there.yachris Sun 6 Mar 2011
@brian
Bingo, that was it, thanks.
@vkuzkokov
Good point -- I changed all the
Str:Obj?declarations toStr:Obj, since this implements the "null is not an allowed value" idea. It's not a fantom map :-)