Repository: gcanti/tcomb (js)

Issue:
declare doesn't handle custom union `dispatch`s
Test case 

```js
import t from 'tcomb'

const U = t.declare('U')

U.dispatch = (x) => t.String.is(x) ? t.String : U

U.define(t.union([t.String, t.list(U)]))

console.log(U.dispatch) // <= wrong, default dispatch implementation
```

Pull request description:
allow declare'd unions with custom dispatch, fix #242

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8e51cea..3164c71 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -12,6 +12,11 @@
 **Note**: Gaps between patch versions are faulty/broken releases.
 **Note**: A feature tagged as Experimental is in a high state of flux, you're at risk of it changing without notice.
 
+# 3.2.11
+
+- **Bug Fix**
+  - allow declare'd unions with custom dispatch, fix #242 (@gcanti)
+
 # 3.2.10
 
 - **Bug Fix**
diff --git a/docs/API.md b/docs/API.md
index 6e659d6..a7764c6 100644
--- a/docs/API.md
+++ b/docs/API.md
@@ -833,7 +833,18 @@ A.define(t.struct({
 }));
 ```
 
-**Note**. **Do not** try to type-check structures with circular references, that would blow the stack.
+**Warning**. **Do not** try to type-check structures with circular references, that would blow the stack.
+
+**Warning**. If you define a union with a custom `dispatch`, do define the `dispatch` function before calling `define`
+
+```js
+const U = t.declare('U')
+// good
+U.dispatch = (x) => t.String.is(x) ? t.String : U
+U.define(t.union([t.String, t.list(U)]))
+// bad
+// U.dispatch = (x) => t.String.is(x) ? t.String : U
+```
 
 # Updating immutable instances
 
diff --git a/lib/declare.js b/lib/declare.js
index 010d972..912ef61 100644
--- a/lib/declare.js
+++ b/lib/declare.js
@@ -4,6 +4,7 @@ var isType = require('./isType');
 var isNil = require('./isNil');
 var mixin = require('./mixin');
 var getTypeName = require('./getTypeName');
+var isUnion = require('./isUnion');
 
 // All the .declare-d types should be clearly different from each other thus they should have
 // different names when a name was not explicitly provided.
@@ -19,6 +20,9 @@ module.exports = function declare(name) {
   function Declare(value, path) {
     if (process.env.NODE_ENV !== 'production') {
       assert(!isNil(type), function () { return 'Type declared but not defined, don\'t forget to call .define on every declared type'; });
+      if (isUnion(type)) {
+        assert(type.dispatch === Declare.dispatch, function () { return 'Please define the custom ' + name + '.dispatch function before calling ' + name + '.define()'; });
+      }
     }
     return type(value, path);
   }
@@ -30,6 +34,9 @@ module.exports = function declare(name) {
       assert(isNil(spec.meta.name) && Object.keys(spec.prototype).length === 0, function () { return 'Invalid argument type ' + assert.stringify(spec) + ' supplied to define(type) (expected a fresh, unnamed type)'; });
     }
 
+    if (isUnion(spec) && Declare.hasOwnProperty('dispatch')) {
+      spec.dispatch = Declare.dispatch;
+    }
     type = spec;
     mixin(Declare, type, true); // true because it overwrites Declare.displayName
     if (name) {
diff --git a/package.json b/package.json
index 04d1ca2..4b41cd2 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "tcomb",
-  "version": "3.2.10",
+  "version": "3.2.11",
   "description": "Type checking and DDD for JavaScript",
   "main": "index.js",
   "typings": "index.d.ts",


Test patch:
diff --git a/test/declare.js b/test/declare.js
index efac3c7..d45d4cf 100644
--- a/test/declare.js
+++ b/test/declare.js
@@ -124,6 +124,25 @@ describe('t.declare([name])', function () {
       assert.strictEqual(create(B, 'a string'), 'a string');
     });
 
+    it('should play well with custom dispatch', function () {
+      function dispatch(x) {
+        return t.String.is(x) ? t.String : U;
+      }
+      var U = t.declare('U');
+      U.dispatch = dispatch;
+      var UU = t.union([t.String, t.list(U)]);
+      U.define(UU);
+      assert.strictEqual(U.dispatch, dispatch);
+      assert.strictEqual(UU.dispatch, dispatch);
+
+      var U2 = t.declare('U');
+      U2.define(t.union([t.String, t.list(U)]));
+      U2.dispatch = dispatch;
+      throwsWithMessage(function () {
+        U2('a');
+      }, '[tcomb] Please define the custom U.dispatch function before calling U.define()');
+    });
+
   });
 
   describe('constructor', function () {