Repository: gcanti/io-ts (ts)

Issue:
Issues when using intersection and types with fallback
## 🐛 Bug report

### Current Behavior

```typescript
import * as t from 'io-ts';
import { withFallback} from 'io-ts-types';

const MyType = t.intersection([
  withFallback(t.type({ prop1: t.string }), { prop1: 'a' }),
  withFallback(t.type({ prop2: t.number }), { prop2: 1 }),
]);

MyType.decode(undefined); // throws: TypeError: Cannot read property 'prop1' of undefined
```

### Expected behavior

As both of the subtypes will return a value when decoding `undefined`, the intersection should also succeed (or fail without exception).

`MyType.decode(undefined)` to be equal to `right({ prop1: 'a', prop2: 1 })`

### Suggested solution(s)

The mergeAll function used to merge all types in the validation of the interface should be able to handle an undefined input. And skip the `u[k] !== base[k]` check.


Pull request description:
improve internal `mergeAll` function, closes #532

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 7990f67..6c04639 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,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.
 
+# 2.2.13
+
+- **Bug Fix**
+  - improve internal `mergeAll` function, closes #532 (@gcanti)
+
 # 2.2.12
 
 - **Experimental**
diff --git a/package.json b/package.json
index 89b653f..22fbf48 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "io-ts",
-  "version": "2.2.12",
+  "version": "2.2.13",
   "description": "TypeScript runtime type system for IO decoding/encoding",
   "main": "lib/index.js",
   "module": "es6/index.js",
diff --git a/src/index.ts b/src/index.ts
index 10903a6..c2204bf 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1416,9 +1416,13 @@ export interface IntersectionC<CS extends [Mixed, Mixed, ...Array<Mixed>]>
     unknown
   > {}
 
-const mergeAll = (base: any, us: Array<any>): any => {
+/**
+ * @internal
+ */
+export const mergeAll = (base: any, us: Array<any>): any => {
   let equal = true
   let primitive = true
+  const baseIsNotADictionary = !UnknownRecord.is(base)
   for (const u of us) {
     if (u !== base) {
       equal = false
@@ -1435,7 +1439,7 @@ const mergeAll = (base: any, us: Array<any>): any => {
   let r: any = {}
   for (const u of us) {
     for (const k in u) {
-      if (u[k] !== base[k] || !r.hasOwnProperty(k)) {
+      if (!r.hasOwnProperty(k) || baseIsNotADictionary || u[k] !== base[k]) {
         r[k] = u[k]
       }
     }


Test patch:
diff --git a/test/2.1.x/intersection.ts b/test/2.1.x/intersection.ts
index 5a0e2b2..cd25150 100644
--- a/test/2.1.x/intersection.ts
+++ b/test/2.1.x/intersection.ts
@@ -3,6 +3,13 @@ import * as t from '../../src/index'
 import { assertFailure, assertStrictEqual, assertSuccess, NumberFromString } from './helpers'
 
 describe('intersection', () => {
+  it('mergeAll', () => {
+    assert.deepStrictEqual(t.mergeAll(undefined, [{ prop1: 'b', prop2: 2 }, { prop1: 'a' }, { prop2: 1 }]), {
+      prop1: 'a',
+      prop2: 1
+    })
+  })
+
   describe('name', () => {
     it('should assign a default name', () => {
       const T = t.intersection([t.type({ a: t.string }), t.type({ b: t.number })])