Repository: gcanti/io-ts
Pull request #457 (2020-04-29 08:46:02)

version 2.2.2


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 342a633..dff0679 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,10 +14,24 @@
 **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.2
+
+- **Experimental**
+  - add `Eq` module (@gcanti)
+  - `Codec`
+    - add `TypeOf` operator (@gcanti)
+  - `Decoder`
+    - add `DecodeError` interface (@gcanti)
+  - `Encoder`
+    - add `TypeOf` operator (@gcanti)
+  - `Guard`
+    - add `TypeOf` operator (@gcanti)
+
 # 2.2.1
 
 - **Experimental**
-  - collect all errors while decoding, closes #449 (@gcanti)
+  - `Decoder`
+    - collect all errors while decoding, closes #449 (@gcanti)
 
 # 2.2.0
 
diff --git a/Eq.md b/Eq.md
new file mode 100644
index 0000000..0a38285
--- /dev/null
+++ b/Eq.md
@@ -0,0 +1,70 @@
+<!-- START doctoc generated TOC please keep comment here to allow auto update -->
+<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
+
+- [Eq interface](#eq-interface)
+- [Built-in primitive eqs](#built-in-primitive-eqs)
+- [Combinators](#combinators)
+
+<!-- END doctoc generated TOC please keep comment here to allow auto update -->
+
+# Eq interface
+
+```ts
+export interface Eq<A> {
+  readonly equals: (x: A, y: A) => boolean
+}
+```
+
+The `Eq` type class represents types which support decidable equality.
+
+Instances must satisfy the following laws:
+
+1. Reflexivity: `E.equals(a, a) === true`
+2. Symmetry: `E.equals(a, b) === E.equals(b, a)`
+3. Transitivity: if `E.equals(a, b) === true` and `E.equals(b, c) === true`, then `E.equals(a, c) === true`
+
+**Example**
+
+```ts
+import { Eq } from 'fp-ts/lib/Eq'
+
+export const string: Eq<string> = {
+  equals: (x, y) => x === y
+}
+```
+
+# Built-in primitive eqs
+
+- `string: Eq<string>`
+- `number: Eq<number>`
+- `boolean: Eq<boolean>`
+- `UnknownArray: Eq<Array<unknown>>`
+- `UnknownRecord: Eq<Record<string, unknown>>`
+
+# Combinators
+
+- `literal`
+- `nullable`
+- `type`
+- `partial`
+- `record`
+- `array`
+- `tuple`
+- `intersection`
+- `sum`
+- `lazy`
+
+**Example**
+
+```ts
+import * as E from 'io-ts/lib/Eq'
+
+const Person = E.type({
+  name: E.string,
+  age: E.number
+})
+
+console.log(Person.equals({ name: 'a', age: 0 }, { name: 'a', age: 0 })) // => true
+console.log(Person.equals({ name: 'a', age: 0 }, { name: '', age: 0 })) // => false
+console.log(Person.equals({ name: 'a', age: 0 }, { name: 'a', age: 1 })) // => false
+```
diff --git a/README.md b/README.md
index 3971ad7..5d9cb74 100644
--- a/README.md
+++ b/README.md
@@ -8,6 +8,7 @@
 
 - [Installation](#installation)
 - [Documentation](#documentation)
+  - [Usage](#usage)
 
 <!-- END doctoc generated TOC please keep comment here to allow auto update -->
 
@@ -38,4 +39,5 @@ Experimental features are published in order to get early feedback from the comm
 - [`Decoder`](Decoder.md)
 - [`Encoder`](Encoder.md)
 - [`Codec`](Codec.md)
+- [`Eq`](Eq.md)
 - [`Schema` (advanced feature)](Schema.md)
diff --git a/Schema.md b/Schema.md
index b251723..afc0546 100644
--- a/Schema.md
+++ b/Schema.md
@@ -8,6 +8,7 @@ import * as D from 'io-ts/lib/Decoder'
 import * as E from 'io-ts/lib/Encoder'
 import * as C from 'io-ts/lib/Codec'
 import * as G from 'io-ts/lib/Guard'
+import * as Eq from 'io-ts/lib/Eq'
 
 export const Person = S.make((S) =>
   S.type({
@@ -20,4 +21,5 @@ export const PersonDecoder = Person(D.decoder)
 export const PersonEncoder = Person(E.encoder)
 export const PersonCodec = Person(C.codec)
 export const PersonGuard = Person(G.guard)
+export const PersonEq = Person(Eq.eq)
 ```
diff --git a/declaration/index.ts b/declaration/index.ts
deleted file mode 100644
index 86231ac..0000000
--- a/declaration/index.ts
+++ /dev/null
@@ -1,179 +0,0 @@
-import * as t from '../src'
-
-//
-// interface
-//
-
-const T1 = t.type({
-  a: t.string
-})
-
-const T2 = t.type({
-  b: T1
-})
-
-const T3 = t.type({
-  c: T2
-})
-
-const T4 = t.type({
-  d: T3
-})
-
-export const T5 = t.type({
-  e: T4
-})
-
-//
-// refinement
-//
-
-export const R1 = t.refinement(T5, () => true)
-
-//
-// recursion
-//
-
-type R = {
-  a: number
-  b: R | undefined | null
-}
-
-export const Rec1: t.Type<R> = t.recursion('R1', self =>
-  t.interface({
-    a: t.number,
-    b: t.union([self, t.undefined, t.null])
-  })
-)
-
-//
-// array
-//
-
-export const A1 = t.array(T5)
-
-//
-// partial
-//
-
-const P1 = t.partial({
-  a: t.string
-})
-
-const P2 = t.partial({
-  b: P1
-})
-
-const P3 = t.partial({
-  c: P2
-})
-
-const P4 = t.partial({
-  d: P3
-})
-
-export const P5 = t.partial({
-  e: P4
-})
-
-//
-// record
-//
-
-const D1 = t.record(t.string, t.number)
-
-const D2 = t.record(t.string, D1)
-
-const D3 = t.record(t.string, D2)
-
-const D4 = t.record(t.string, D3)
-
-export const D5 = t.record(t.string, D4)
-
-//
-// union
-//
-
-export const U1 = t.union([T5, D5])
-
-//
-// intersection
-//
-
-export const I1 = t.intersection([T5, D5])
-
-//
-// tuple
-//
-
-export const Tu1 = t.tuple([T5, D5])
-
-//
-// readonly type
-//
-
-export const RO1 = t.readonly(T5)
-
-//
-// readonly array
-//
-
-export const RA1 = t.readonlyArray(T5)
-
-//
-// strict
-//
-
-const S1 = t.strict({
-  a: t.string
-})
-
-const S2 = t.strict({
-  b: S1
-})
-
-const S3 = t.strict({
-  c: S2
-})
-
-const S4 = t.strict({
-  d: S3
-})
-
-export const S5 = t.strict({
-  e: S4
-})
-
-//
-// tagged union
-//
-
-const TUMA = t.type({
-  type: t.literal(true),
-  foo: t.string
-})
-
-const TUMB = t.type(
-  {
-    type: t.literal(false),
-    bar: t.number
-  },
-  'B'
-)
-
-export const TU1 = t.taggedUnion('type', [TUMA, TUMB])
-
-//
-// exact
-//
-
-export const E1 = t.exact(T5)
-
-//
-// brand
-//
-
-export const B1 = t.type({
-  name: t.string,
-  age: t.Int
-})
diff --git a/declaration/out/declaration/index.d.ts b/declaration/out/declaration/index.d.ts
deleted file mode 100644
index 59398b9..0000000
--- a/declaration/out/declaration/index.d.ts
+++ /dev/null
@@ -1,140 +0,0 @@
-import * as t from '../src';
-export declare const T5: t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>;
-export declare const R1: t.RefinementC<t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>>;
-declare type R = {
-    a: number;
-    b: R | undefined | null;
-};
-export declare const Rec1: t.Type<R>;
-export declare const A1: t.ArrayC<t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>>;
-export declare const P5: t.PartialC<{
-    e: t.PartialC<{
-        d: t.PartialC<{
-            c: t.PartialC<{
-                b: t.PartialC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>;
-export declare const D5: t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.NumberC>>>>>;
-export declare const U1: t.UnionC<[t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.NumberC>>>>>]>;
-export declare const I1: t.IntersectionC<[t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.NumberC>>>>>]>;
-export declare const Tu1: t.TupleC<[t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.NumberC>>>>>]>;
-export declare const RO1: t.ReadonlyC<t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>>;
-export declare const RA1: t.ReadonlyArrayC<t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>>;
-export declare const S5: t.ExactC<t.TypeC<{
-    e: t.ExactC<t.TypeC<{
-        d: t.ExactC<t.TypeC<{
-            c: t.ExactC<t.TypeC<{
-                b: t.ExactC<t.TypeC<{
-                    a: t.StringC;
-                }>>;
-            }>>;
-        }>>;
-    }>>;
-}>>;
-export declare const TU1: t.TaggedUnionC<"type", [t.TypeC<{
-    type: t.LiteralC<true>;
-    foo: t.StringC;
-}>, t.TypeC<{
-    type: t.LiteralC<false>;
-    bar: t.NumberC;
-}>]>;
-export declare const E1: t.ExactC<t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>>;
-export declare const B1: t.TypeC<{
-    name: t.StringC;
-    age: t.BrandC<t.NumberC, t.IntBrand>;
-}>;
-export {};
diff --git a/declaration/tsconfig.json b/declaration/tsconfig.json
deleted file mode 100644
index f08e871..0000000
--- a/declaration/tsconfig.json
+++ /dev/null
@@ -1,12 +0,0 @@
-{
-  "compilerOptions": {
-    "outDir": "./out",
-    "declaration": true,
-    "module": "commonjs",
-    "strict": true,
-    "lib": ["es6"],
-    "emitDeclarationOnly": true,
-    "target": "es5"
-  },
-  "include": ["./index.ts"]
-}
diff --git a/docs/modules/Codec.ts.md b/docs/modules/Codec.ts.md
index 64571bf..18cde81 100644
--- a/docs/modules/Codec.ts.md
+++ b/docs/modules/Codec.ts.md
@@ -13,6 +13,7 @@ Added in v2.2.0
 <h2 class="text-delta">Table of contents</h2>
 
 - [Codec (interface)](#codec-interface)
+- [TypeOf (type alias)](#typeof-type-alias)
 - [URI (type alias)](#uri-type-alias)
 - [URI](#uri)
 - [UnknownArray](#unknownarray)
@@ -41,7 +42,7 @@ Added in v2.2.0
 
 Laws:
 
-1. `pipe(codec.decode(u), E.fold(() => u, codec.encode) = u` for all `u` in `unknown`
+1. `pipe(codec.decode(u), E.fold(() => u, codec.encode)) = u` for all `u` in `unknown`
 2. `codec.decode(codec.encode(a)) = E.right(a)` for all `a` in `A`
 
 **Signature**
@@ -52,6 +53,16 @@ export interface Codec<A> extends D.Decoder<A>, E.Encoder<A> {}
 
 Added in v2.2.0
 
+# TypeOf (type alias)
+
+**Signature**
+
+```ts
+export type TypeOf<C> = C extends Codec<infer A> ? A : never
+```
+
+Added in v2.2.2
+
 # URI (type alias)
 
 **Signature**
@@ -267,7 +278,7 @@ Added in v2.2.0
 ```ts
 export declare function withExpected<A>(
   codec: Codec<A>,
-  expected: (actual: unknown, nea: NonEmptyArray<Tree<string>>) => NonEmptyArray<Tree<string>>
+  expected: (actual: unknown, e: D.DecodeError) => D.DecodeError
 ): Codec<A>
 ```
 
diff --git a/docs/modules/Decoder.ts.md b/docs/modules/Decoder.ts.md
index 2fe8c8b..7b3aa55 100644
--- a/docs/modules/Decoder.ts.md
+++ b/docs/modules/Decoder.ts.md
@@ -12,6 +12,7 @@ Added in v2.2.0
 
 <h2 class="text-delta">Table of contents</h2>
 
+- [DecodeError (interface)](#decodeerror-interface)
 - [Decoder (interface)](#decoder-interface)
 - [TypeOf (type alias)](#typeof-type-alias)
 - [URI (type alias)](#uri-type-alias)
@@ -28,6 +29,7 @@ Added in v2.2.0
 - [failure](#failure)
 - [fromGuard](#fromguard)
 - [intersection](#intersection)
+- [isNotEmpty](#isnotempty)
 - [lazy](#lazy)
 - [literal](#literal)
 - [map](#map)
@@ -49,13 +51,23 @@ Added in v2.2.0
 
 ---
 
+# DecodeError (interface)
+
+**Signature**
+
+```ts
+export interface DecodeError extends NonEmptyArray<Tree<string>> {}
+```
+
+Added in v2.2.2
+
 # Decoder (interface)
 
 **Signature**
 
 ```ts
 export interface Decoder<A> {
-  readonly decode: (u: unknown) => Either<NonEmptyArray<Tree<string>>, A>
+  readonly decode: (u: unknown) => Either<DecodeError, A>
 }
 ```
 
@@ -189,7 +201,7 @@ Added in v2.2.0
 **Signature**
 
 ```ts
-export declare function failure<A = never>(message: string): Either<NonEmptyArray<Tree<string>>, A>
+export declare function failure<A = never>(message: string): Either<DecodeError, A>
 ```
 
 Added in v2.2.0
@@ -214,6 +226,16 @@ export declare function intersection<A, B>(left: Decoder<A>, right: Decoder<B>):
 
 Added in v2.2.0
 
+# isNotEmpty
+
+**Signature**
+
+```ts
+export declare function isNotEmpty<A>(as: ReadonlyArray<A>): as is NonEmptyArray<A>
+```
+
+Added in v2.2.2
+
 # lazy
 
 **Signature**
@@ -333,7 +355,7 @@ Added in v2.2.0
 **Signature**
 
 ```ts
-export declare function success<A>(a: A): Either<NonEmptyArray<Tree<string>>, A>
+export declare function success<A>(a: A): Either<DecodeError, A>
 ```
 
 Added in v2.2.0
@@ -401,7 +423,7 @@ Added in v2.2.0
 ```ts
 export declare function withExpected<A>(
   decoder: Decoder<A>,
-  expected: (actual: unknown, nea: NonEmptyArray<Tree<string>>) => NonEmptyArray<Tree<string>>
+  expected: (actual: unknown, e: DecodeError) => DecodeError
 ): Decoder<A>
 ```
 
diff --git a/docs/modules/Encoder.ts.md b/docs/modules/Encoder.ts.md
index 7450dd6..efeb677 100644
--- a/docs/modules/Encoder.ts.md
+++ b/docs/modules/Encoder.ts.md
@@ -13,6 +13,7 @@ Added in v2.2.0
 <h2 class="text-delta">Table of contents</h2>
 
 - [Encoder (interface)](#encoder-interface)
+- [TypeOf (type alias)](#typeof-type-alias)
 - [URI (type alias)](#uri-type-alias)
 - [URI](#uri)
 - [array](#array)
@@ -42,6 +43,16 @@ export interface Encoder<A> {
 
 Added in v2.2.0
 
+# TypeOf (type alias)
+
+**Signature**
+
+```ts
+export type TypeOf<E> = E extends Encoder<infer A> ? A : never
+```
+
+Added in v2.2.2
+
 # URI (type alias)
 
 **Signature**
diff --git a/docs/modules/Eq.ts.md b/docs/modules/Eq.ts.md
new file mode 100644
index 0000000..a55a06c
--- /dev/null
+++ b/docs/modules/Eq.ts.md
@@ -0,0 +1,194 @@
+---
+title: Eq.ts
+nav_order: 4
+parent: Modules
+---
+
+# Eq overview
+
+Added in v2.2.2
+
+---
+
+<h2 class="text-delta">Table of contents</h2>
+
+- [TypeOf (type alias)](#typeof-type-alias)
+- [UnknownArray](#unknownarray)
+- [UnknownRecord](#unknownrecord)
+- [array](#array)
+- [boolean](#boolean)
+- [eq](#eq)
+- [intersection](#intersection)
+- [lazy](#lazy)
+- [nullable](#nullable)
+- [number](#number)
+- [partial](#partial)
+- [record](#record)
+- [string](#string)
+- [sum](#sum)
+- [tuple](#tuple)
+- [type](#type)
+
+---
+
+# TypeOf (type alias)
+
+**Signature**
+
+```ts
+export type TypeOf<E> = E extends Eq<infer A> ? A : never
+```
+
+Added in v2.2.2
+
+# UnknownArray
+
+**Signature**
+
+```ts
+export declare const UnknownArray: E.Eq<unknown[]>
+```
+
+Added in v2.2.2
+
+# UnknownRecord
+
+**Signature**
+
+```ts
+export declare const UnknownRecord: E.Eq<Record<string, unknown>>
+```
+
+Added in v2.2.2
+
+# array
+
+**Signature**
+
+```ts
+export declare const array: <A>(eq: E.Eq<A>) => E.Eq<A[]>
+```
+
+Added in v2.2.2
+
+# boolean
+
+**Signature**
+
+```ts
+export declare const boolean: E.Eq<boolean>
+```
+
+Added in v2.2.2
+
+# eq
+
+**Signature**
+
+```ts
+export declare const eq: Contravariant1<'Eq'> & S.Schemable<'Eq'>
+```
+
+Added in v2.2.2
+
+# intersection
+
+**Signature**
+
+```ts
+export declare function intersection<A, B>(left: Eq<A>, right: Eq<B>): Eq<A & B>
+```
+
+Added in v2.2.2
+
+# lazy
+
+**Signature**
+
+```ts
+export declare function lazy<A>(f: () => Eq<A>): Eq<A>
+```
+
+Added in v2.2.2
+
+# nullable
+
+**Signature**
+
+```ts
+export declare function nullable<A>(or: Eq<A>): Eq<null | A>
+```
+
+Added in v2.2.2
+
+# number
+
+**Signature**
+
+```ts
+export declare const number: E.Eq<number>
+```
+
+Added in v2.2.2
+
+# partial
+
+**Signature**
+
+```ts
+export declare function partial<A>(properties: { [K in keyof A]: Eq<A[K]> }): Eq<Partial<A>>
+```
+
+Added in v2.2.2
+
+# record
+
+**Signature**
+
+```ts
+export declare const record: <A>(codomain: E.Eq<A>) => E.Eq<Record<string, A>>
+```
+
+Added in v2.2.2
+
+# string
+
+**Signature**
+
+```ts
+export declare const string: E.Eq<string>
+```
+
+Added in v2.2.2
+
+# sum
+
+**Signature**
+
+```ts
+export declare function sum<T extends string>(
+  tag: T
+): <A>(members: { [K in keyof A]: Eq<A[K] & Record<T, K>> }) => Eq<A[keyof A]>
+```
+
+Added in v2.2.2
+
+# tuple
+
+**Signature**
+
+```ts
+export declare const tuple: <A extends readonly unknown[]>(...components: { [K in keyof A]: E.Eq<A[K]> }) => E.Eq<A>
+```
+
+Added in v2.2.2
+
+# type
+
+**Signature**
+
+```ts
+export declare const type: <A>(eqs: { [K in keyof A]: E.Eq<A[K]> }) => E.Eq<A>
+```
+
+Added in v2.2.2
diff --git a/docs/modules/Guard.ts.md b/docs/modules/Guard.ts.md
index 4bd61f1..ebbd591 100644
--- a/docs/modules/Guard.ts.md
+++ b/docs/modules/Guard.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Guard.ts
-nav_order: 4
+nav_order: 5
 parent: Modules
 ---
 
@@ -13,6 +13,7 @@ Added in v2.2.0
 <h2 class="text-delta">Table of contents</h2>
 
 - [Guard (interface)](#guard-interface)
+- [TypeOf (type alias)](#typeof-type-alias)
 - [URI (type alias)](#uri-type-alias)
 - [URI](#uri)
 - [UnknownArray](#unknownarray)
@@ -49,6 +50,16 @@ export interface Guard<A> {
 
 Added in v2.2.0
 
+# TypeOf (type alias)
+
+**Signature**
+
+```ts
+export type TypeOf<G> = G extends Guard<infer A> ? A : never
+```
+
+Added in v2.2.2
+
 # URI (type alias)
 
 **Signature**
diff --git a/docs/modules/PathReporter.ts.md b/docs/modules/PathReporter.ts.md
index 4116f44..e46f461 100644
--- a/docs/modules/PathReporter.ts.md
+++ b/docs/modules/PathReporter.ts.md
@@ -1,6 +1,6 @@
 ---
 title: PathReporter.ts
-nav_order: 6
+nav_order: 7
 parent: Modules
 ---
 
diff --git a/docs/modules/Reporter.ts.md b/docs/modules/Reporter.ts.md
index a76c048..29f039b 100644
--- a/docs/modules/Reporter.ts.md
+++ b/docs/modules/Reporter.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Reporter.ts
-nav_order: 7
+nav_order: 8
 parent: Modules
 ---
 
diff --git a/docs/modules/Schema.ts.md b/docs/modules/Schema.ts.md
index 6500e9c..634ad87 100644
--- a/docs/modules/Schema.ts.md
+++ b/docs/modules/Schema.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Schema.ts
-nav_order: 8
+nav_order: 9
 parent: Modules
 ---
 
diff --git a/docs/modules/Schemable.ts.md b/docs/modules/Schemable.ts.md
index 2c38c3d..afd53b2 100644
--- a/docs/modules/Schemable.ts.md
+++ b/docs/modules/Schemable.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Schemable.ts
-nav_order: 9
+nav_order: 10
 parent: Modules
 ---
 
diff --git a/docs/modules/Tree.ts.md b/docs/modules/Tree.ts.md
index 488a86b..4b880a0 100644
--- a/docs/modules/Tree.ts.md
+++ b/docs/modules/Tree.ts.md
@@ -1,6 +1,6 @@
 ---
 title: Tree.ts
-nav_order: 10
+nav_order: 11
 parent: Modules
 ---
 
@@ -21,7 +21,7 @@ Added in v2.2.0
 **Signature**
 
 ```ts
-export declare function draw(es: NonEmptyArray<Tree<string>>): string
+export declare function draw(e: DecodeError): string
 ```
 
 Added in v2.2.0
diff --git a/docs/modules/index.ts.md b/docs/modules/index.ts.md
index 3bcd78e..f3f3a66 100644
--- a/docs/modules/index.ts.md
+++ b/docs/modules/index.ts.md
@@ -1,6 +1,6 @@
 ---
 title: index.ts
-nav_order: 5
+nav_order: 6
 parent: Modules
 ---
 
diff --git a/jest.config.js b/jest.config.js
index 247b387..ea4cd8f 100644
--- a/jest.config.js
+++ b/jest.config.js
@@ -16,5 +16,5 @@ module.exports = {
       statements: 100
     }
   },
-  modulePathIgnorePatterns: ['helpers.ts']
+  modulePathIgnorePatterns: ['2.1.x/helpers.ts', 'Arbitrary.ts']
 }
diff --git a/package-lock.json b/package-lock.json
index c7d3109..30d56f4 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
 {
   "name": "io-ts",
-  "version": "2.1.3",
+  "version": "2.2.2",
   "lockfileVersion": 1,
   "requires": true,
   "dependencies": {
@@ -2482,6 +2482,16 @@
       "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=",
       "dev": true
     },
+    "fast-check": {
+      "version": "1.24.2",
+      "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-1.24.2.tgz",
+      "integrity": "sha512-ZL48cyZZLJnVsUj127Zi1mfFLM98yzw0LlSSH8CMeVmpL5RCfSRcZSZZ0kJWrRK4eOgNFnXXKNDbzuRb3Vsdhg==",
+      "dev": true,
+      "requires": {
+        "pure-rand": "^2.0.0",
+        "tslib": "^1.10.0"
+      }
+    },
     "fast-deep-equal": {
       "version": "3.1.1",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.1.tgz",
@@ -5883,6 +5893,12 @@
       "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
       "dev": true
     },
+    "pure-rand": {
+      "version": "2.0.0",
+      "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-2.0.0.tgz",
+      "integrity": "sha512-mk98aayyd00xbfHgE3uEmAUGzz3jCdm8Mkf5DUXUhc7egmOaGG2D7qhVlynGenNe9VaNJZvzO9hkc8myuTkDgw==",
+      "dev": true
+    },
     "qs": {
       "version": "6.5.2",
       "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
diff --git a/package.json b/package.json
index c454f0c..0fe71f4 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "io-ts",
-  "version": "2.2.1",
+  "version": "2.2.2",
   "description": "TypeScript runtime type system for IO decoding/encoding",
   "files": [
     "lib",
@@ -15,15 +15,14 @@
     "jest": "jest",
     "prettier": "prettier --no-semi --single-quote --print-width 120 --parser typescript --list-different \"{src,test}/**/*.ts\"",
     "fix-prettier": "prettier --no-semi --single-quote --print-width 120 --parser typescript --write \"{src,test,examples,exercises}/**/*.ts\"",
-    "test": "npm run prettier && npm run lint && npm run dtslint && npm run declaration && npm run jest && npm run docs",
+    "test": "npm run prettier && npm run lint && npm run dtslint && npm run jest && npm run docs",
     "clean": "rimraf lib/* es6/*",
     "build": "npm run clean && tsc && tsc -p tsconfig.es6.json && npm run import-path-rewrite",
     "prepublish": "npm run build",
     "perf": "ts-node perf/index",
     "dtslint": "dtslint dtslint",
-    "declaration": "tsc -p declaration/tsconfig.json",
     "mocha": "TS_NODE_CACHE=false mocha -r ts-node/register test/*.ts",
-    "doctoc": "doctoc README.md Type.md Decoder.md Encoder.md Codec.md",
+    "doctoc": "doctoc README.md Type.md Decoder.md Encoder.md Codec.md Eq.md",
     "docs": "docs-ts",
     "import-path-rewrite": "import-path-rewrite"
   },
@@ -49,6 +48,7 @@
     "docs-ts": "0.4.0",
     "doctoc": "1.4.0",
     "dtslint": "github:gcanti/dtslint",
+    "fast-check": "^1.24.2",
     "fp-ts": "2.5.3",
     "import-path-rewrite": "github:gcanti/import-path-rewrite",
     "jest": "25.2.7",
diff --git a/perf/typescript-runtime-type-benchmarks.ts b/perf/typescript-runtime-type-benchmarks.ts
new file mode 100644
index 0000000..6615d4b
--- /dev/null
+++ b/perf/typescript-runtime-type-benchmarks.ts
@@ -0,0 +1,71 @@
+import * as Benchmark from 'benchmark'
+import * as t from '../src/Decoder'
+import * as G from '../src/Guard'
+
+/*
+decode x 1,138,915 ops/sec ±0.44% (87 runs sampled)
+is x 1,975,690 ops/sec ±0.44% (91 runs sampled)
+ */
+
+const suite = new Benchmark.Suite()
+
+const decoder = t.type({
+  number: t.number,
+  negNumber: t.number,
+  maxNumber: t.number,
+  string: t.string,
+  longString: t.string,
+  boolean: t.boolean,
+  deeplyNested: t.type({
+    foo: t.string,
+    num: t.number,
+    bool: t.boolean
+  })
+})
+
+const guard = G.type({
+  number: G.number,
+  negNumber: G.number,
+  maxNumber: G.number,
+  string: G.string,
+  longString: G.string,
+  boolean: G.boolean,
+  deeplyNested: G.type({
+    foo: G.string,
+    num: G.number,
+    bool: G.boolean
+  })
+})
+
+export const good = {
+  number: 1,
+  negNumber: -1,
+  maxNumber: Number.MAX_VALUE,
+  string: 'string',
+  longString:
+    'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Vivendum intellegat et qui, ei denique consequuntur vix. Semper aeterno percipit ut his, sea ex utinam referrentur repudiandae. No epicuri hendrerit consetetur sit, sit dicta adipiscing ex, in facete detracto deterruisset duo. Quot populo ad qui. Sit fugit nostrum et. Ad per diam dicant interesset, lorem iusto sensibus ut sed. No dicam aperiam vis. Pri posse graeco definitiones cu, id eam populo quaestio adipiscing, usu quod malorum te. Ex nam agam veri, dicunt efficiantur ad qui, ad legere adversarium sit. Commune platonem mel id, brute adipiscing duo an. Vivendum intellegat et qui, ei denique consequuntur vix. Offendit eleifend moderatius ex vix, quem odio mazim et qui, purto expetendis cotidieque quo cu, veri persius vituperata ei nec. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.',
+  boolean: true,
+  deeplyNested: {
+    foo: 'bar',
+    num: 1,
+    bool: false
+  }
+}
+
+// console.log(decoder.decode(good))
+// console.log(guard.is(good))
+
+suite
+  .add('decode', function () {
+    decoder.decode(good)
+  })
+  .add('is', function () {
+    guard.is(good)
+  })
+  .on('cycle', function (event: any) {
+    console.log(String(event.target))
+  })
+  .on('complete', function (this: any) {
+    console.log('Fastest is ' + this.filter('fastest').map('name'))
+  })
+  .run({ async: true })
diff --git a/src/Codec.ts b/src/Codec.ts
index 61afdc3..9e52a0d 100644
--- a/src/Codec.ts
+++ b/src/Codec.ts
@@ -2,8 +2,6 @@
  * @since 2.2.0
  */
 import { Invariant1 } from 'fp-ts/lib/Invariant'
-import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'
-import { Tree } from 'fp-ts/lib/Tree'
 import * as D from './Decoder'
 import * as E from './Encoder'
 import { Schemable, Literal } from './Schemable'
@@ -15,13 +13,18 @@ import { Schemable, Literal } from './Schemable'
 /**
  * Laws:
  *
- * 1. `pipe(codec.decode(u), E.fold(() => u, codec.encode) = u` for all `u` in `unknown`
+ * 1. `pipe(codec.decode(u), E.fold(() => u, codec.encode)) = u` for all `u` in `unknown`
  * 2. `codec.decode(codec.encode(a)) = E.right(a)` for all `a` in `A`
  *
  * @since 2.2.0
  */
 export interface Codec<A> extends D.Decoder<A>, E.Encoder<A> {}
 
+/**
+ * @since 2.2.2
+ */
+export type TypeOf<C> = C extends Codec<infer A> ? A : never
+
 // -------------------------------------------------------------------------------------
 // constructors
 // -------------------------------------------------------------------------------------
@@ -81,7 +84,7 @@ export const UnknownRecord: Codec<Record<string, unknown>> = make(D.decoder.Unkn
  */
 export function withExpected<A>(
   codec: Codec<A>,
-  expected: (actual: unknown, nea: NonEmptyArray<Tree<string>>) => NonEmptyArray<Tree<string>>
+  expected: (actual: unknown, e: D.DecodeError) => D.DecodeError
 ): Codec<A> {
   return make(D.withExpected(codec, expected), codec)
 }
diff --git a/src/Decoder.ts b/src/Decoder.ts
index 524df45..10858ea 100644
--- a/src/Decoder.ts
+++ b/src/Decoder.ts
@@ -14,11 +14,16 @@ import { Schemable, memoize, WithUnion, Literal } from './Schemable'
 // model
 // -------------------------------------------------------------------------------------
 
+/**
+ * @since 2.2.2
+ */
+export interface DecodeError extends NonEmptyArray<Tree<string>> {}
+
 /**
  * @since 2.2.0
  */
 export interface Decoder<A> {
-  readonly decode: (u: unknown) => Either<NonEmptyArray<Tree<string>>, A>
+  readonly decode: (u: unknown) => Either<DecodeError, A>
 }
 
 /**
@@ -45,18 +50,21 @@ export function tree<A>(value: A, forest: Forest<A> = empty): Tree<A> {
 /**
  * @since 2.2.0
  */
-export function success<A>(a: A): Either<NonEmptyArray<Tree<string>>, A> {
+export function success<A>(a: A): Either<DecodeError, A> {
   return right(a)
 }
 
 /**
  * @since 2.2.0
  */
-export function failure<A = never>(message: string): Either<NonEmptyArray<Tree<string>>, A> {
+export function failure<A = never>(message: string): Either<DecodeError, A> {
   return left([tree(message)])
 }
 
-function isNotEmpty<A>(as: Array<A>): as is NonEmptyArray<A> {
+/**
+ * @since 2.2.2
+ */
+export function isNotEmpty<A>(as: ReadonlyArray<A>): as is NonEmptyArray<A> {
   return as.length > 0
 }
 
@@ -123,7 +131,7 @@ export const UnknownRecord: Decoder<Record<string, unknown>> = fromGuard(G.Unkno
  */
 export function withExpected<A>(
   decoder: Decoder<A>,
-  expected: (actual: unknown, nea: NonEmptyArray<Tree<string>>) => NonEmptyArray<Tree<string>>
+  expected: (actual: unknown, e: DecodeError) => DecodeError
 ): Decoder<A> {
   return {
     decode: (u) =>
@@ -423,16 +431,16 @@ export function union<A extends ReadonlyArray<unknown>>(
       if (isRight(e)) {
         return e
       } else {
-        const forest: NonEmptyArray<Tree<string>> = [tree(`member 0`, e.left)]
+        const errors: DecodeError = [tree(`member 0`, e.left)]
         for (let i = 1; i < len; i++) {
           const e = members[i].decode(u)
           if (isRight(e)) {
             return e
           } else {
-            forest.push(tree(`member ${i}`, e.left))
+            errors.push(tree(`member ${i}`, e.left))
           }
         }
-        return left(forest)
+        return left(errors)
       }
     }
   }
diff --git a/src/Encoder.ts b/src/Encoder.ts
index 907d292..33581b4 100644
--- a/src/Encoder.ts
+++ b/src/Encoder.ts
@@ -18,6 +18,11 @@ export interface Encoder<A> {
   readonly encode: (a: A) => unknown
 }
 
+/**
+ * @since 2.2.2
+ */
+export type TypeOf<E> = E extends Encoder<infer A> ? A : never
+
 // -------------------------------------------------------------------------------------
 // primitives
 // -------------------------------------------------------------------------------------
diff --git a/src/Eq.ts b/src/Eq.ts
new file mode 100644
index 0000000..78ba937
--- /dev/null
+++ b/src/Eq.ts
@@ -0,0 +1,168 @@
+/**
+ * @since 2.2.2
+ */
+import * as A from 'fp-ts/lib/Array'
+import * as E from 'fp-ts/lib/Eq'
+import * as R from 'fp-ts/lib/Record'
+import * as S from './Schemable'
+import Eq = E.Eq
+
+/**
+ * @since 2.2.2
+ */
+export type TypeOf<E> = E extends Eq<infer A> ? A : never
+
+// -------------------------------------------------------------------------------------
+// primitives
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.2
+ */
+export const string: Eq<string> = E.eqString
+
+/**
+ * @since 2.2.2
+ */
+export const number: Eq<number> = E.eqNumber
+
+/**
+ * @since 2.2.2
+ */
+export const boolean: Eq<boolean> = E.eqBoolean
+
+/**
+ * @since 2.2.2
+ */
+export const UnknownArray: Eq<Array<unknown>> = E.fromEquals((x, y) => x.length === y.length)
+
+/**
+ * @since 2.2.2
+ */
+export const UnknownRecord: Eq<Record<string, unknown>> = E.fromEquals((x, y) => {
+  for (const k in x) {
+    if (!(k in y)) {
+      return false
+    }
+  }
+  for (const k in y) {
+    if (!(k in x)) {
+      return false
+    }
+  }
+  return true
+})
+
+// -------------------------------------------------------------------------------------
+// combinators
+// -------------------------------------------------------------------------------------
+
+/**
+ * @since 2.2.2
+ */
+export function nullable<A>(or: Eq<A>): Eq<null | A> {
+  return {
+    equals: (x, y) => (x === null || y === null ? x === y : or.equals(x, y))
+  }
+}
+
+/**
+ * @since 2.2.2
+ */
+export const type: <A>(eqs: { [K in keyof A]: Eq<A[K]> }) => Eq<A> = E.getStructEq
+
+/**
+ * @since 2.2.2
+ */
+export function partial<A>(properties: { [K in keyof A]: Eq<A[K]> }): Eq<Partial<A>> {
+  return {
+    equals: (x, y) => {
+      for (const k in properties) {
+        const xk = x[k]
+        const yk = y[k]
+        if (!(xk === undefined || yk === undefined ? xk === yk : properties[k].equals(xk!, yk!))) {
+          return false
+        }
+      }
+      return true
+    }
+  }
+}
+
+/**
+ * @since 2.2.2
+ */
+export const record: <A>(codomain: Eq<A>) => Eq<Record<string, A>> = R.getEq
+
+/**
+ * @since 2.2.2
+ */
+export const array: <A>(eq: Eq<A>) => Eq<Array<A>> = A.getEq
+
+/**
+ * @since 2.2.2
+ */
+export const tuple: <A extends ReadonlyArray<unknown>>(
+  ...components: { [K in keyof A]: Eq<A[K]> }
+) => Eq<A> = E.getTupleEq as any
+
+/**
+ * @since 2.2.2
+ */
+export function intersection<A, B>(left: Eq<A>, right: Eq<B>): Eq<A & B> {
+  return {
+    equals: (x, y) => left.equals(x, y) && right.equals(x, y)
+  }
+}
+
+/**
+ * @since 2.2.2
+ */
+export function sum<T extends string>(
+  tag: T
+): <A>(members: { [K in keyof A]: Eq<A[K] & Record<T, K>> }) => Eq<A[keyof A]> {
+  return (members: Record<string, Eq<any>>) => {
+    return {
+      equals: (x: Record<string, any>, y: Record<string, any>) => {
+        const vx = x[tag]
+        const vy = y[tag]
+        if (vx !== vy) {
+          return false
+        }
+        return members[vx].equals(x, y)
+      }
+    }
+  }
+}
+
+/**
+ * @since 2.2.2
+ */
+export function lazy<A>(f: () => Eq<A>): Eq<A> {
+  const get = S.memoize<void, Eq<A>>(f)
+  return {
+    equals: (x, y) => get().equals(x, y)
+  }
+}
+
+/**
+ * @since 2.2.2
+ */
+export const eq: typeof E.eq & S.Schemable<E.URI> = {
+  ...E.eq,
+  literal: () => E.eqStrict,
+  string,
+  number,
+  boolean,
+  UnknownArray,
+  UnknownRecord,
+  nullable,
+  type,
+  partial,
+  record,
+  array,
+  tuple,
+  intersection,
+  sum,
+  lazy: (_, f) => lazy(f)
+}
diff --git a/src/Guard.ts b/src/Guard.ts
index e21c827..7a5feae 100644
--- a/src/Guard.ts
+++ b/src/Guard.ts
@@ -14,6 +14,11 @@ export interface Guard<A> {
   is: (u: unknown) => u is A
 }
 
+/**
+ * @since 2.2.2
+ */
+export type TypeOf<G> = G extends Guard<infer A> ? A : never
+
 // -------------------------------------------------------------------------------------
 // constructors
 // -------------------------------------------------------------------------------------
diff --git a/src/Tree.ts b/src/Tree.ts
index 79a38c1..bd13b83 100644
--- a/src/Tree.ts
+++ b/src/Tree.ts
@@ -1,12 +1,12 @@
 /**
  * @since 2.2.0
  */
-import { drawTree, Tree } from 'fp-ts/lib/Tree'
-import { NonEmptyArray } from 'fp-ts/lib/NonEmptyArray'
+import { drawTree } from 'fp-ts/lib/Tree'
+import { DecodeError } from './Decoder'
 
 /**
  * @since 2.2.0
  */
-export function draw(es: NonEmptyArray<Tree<string>>): string {
-  return es.map(drawTree).join('\n')
+export function draw(e: DecodeError): string {
+  return e.map(drawTree).join('\n')
 }


Test patch:
diff --git a/test/PathReporter.ts b/test/2.1.x/PathReporter.ts
similarity index 94%
rename from test/PathReporter.ts
rename to test/2.1.x/PathReporter.ts
index 8448187..48b70cb 100644
--- a/test/PathReporter.ts
+++ b/test/2.1.x/PathReporter.ts
@@ -1,6 +1,6 @@
 import * as assert from 'assert'
-import * as t from '../src'
-import { PathReporter } from '../src/PathReporter'
+import * as t from '../../src'
+import { PathReporter } from '../../src/PathReporter'
 import { NumberFromString } from './helpers'
 
 describe('PathReporter', () => {
diff --git a/test/ThrowReporter.ts b/test/2.1.x/ThrowReporter.ts
similarity index 83%
rename from test/ThrowReporter.ts
rename to test/2.1.x/ThrowReporter.ts
index 1fbee86..33ac2e3 100644
--- a/test/ThrowReporter.ts
+++ b/test/2.1.x/ThrowReporter.ts
@@ -1,6 +1,6 @@
 import * as assert from 'assert'
-import * as t from '../src'
-import { ThrowReporter } from '../src/ThrowReporter'
+import * as t from '../../src'
+import { ThrowReporter } from '../../src/ThrowReporter'
 
 describe('ThrowReporter', () => {
   it('should throw on invalid inputs', () => {
diff --git a/test/TypeClass.ts b/test/2.1.x/TypeClass.ts
similarity index 98%
rename from test/TypeClass.ts
rename to test/2.1.x/TypeClass.ts
index 0a37d0c..c179b3e 100644
--- a/test/TypeClass.ts
+++ b/test/2.1.x/TypeClass.ts
@@ -1,7 +1,7 @@
 import * as assert from 'assert'
 import { Either, fold, right } from 'fp-ts/lib/Either'
 import { pipe } from 'fp-ts/lib/pipeable'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertFailure, assertSuccess } from './helpers'
 
 const BAA = new t.Type<number, string, string>(
diff --git a/test/array.ts b/test/2.1.x/array.ts
similarity index 98%
rename from test/array.ts
rename to test/2.1.x/array.ts
index 0437ecb..e34a307 100644
--- a/test/array.ts
+++ b/test/2.1.x/array.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertFailure, assertStrictEqual, assertStrictSuccess, assertSuccess, NumberFromString } from './helpers'
 
 describe('array', () => {
diff --git a/test/brand.ts b/test/2.1.x/brand.ts
similarity index 98%
rename from test/brand.ts
rename to test/2.1.x/brand.ts
index 2b70ada..3c2e296 100644
--- a/test/brand.ts
+++ b/test/2.1.x/brand.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure, assertStrictEqual, NumberFromString } from './helpers'
 
 interface PositiveBrand {
diff --git a/test/default-types.ts b/test/2.1.x/default-types.ts
similarity index 99%
rename from test/default-types.ts
rename to test/2.1.x/default-types.ts
index 2b4ca6b..c9f34c6 100644
--- a/test/default-types.ts
+++ b/test/2.1.x/default-types.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure } from './helpers'
 
 describe('UnknownRecord', () => {
diff --git a/test/exact.ts b/test/2.1.x/exact.ts
similarity index 99%
rename from test/exact.ts
rename to test/2.1.x/exact.ts
index 9d75ae8..b4694e2 100644
--- a/test/exact.ts
+++ b/test/2.1.x/exact.ts
@@ -1,4 +1,4 @@
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure, assertStrictEqual, NumberFromString } from './helpers'
 import * as assert from 'assert'
 
diff --git a/test/helpers.ts b/test/2.1.x/helpers.ts
similarity index 96%
rename from test/helpers.ts
rename to test/2.1.x/helpers.ts
index 2b66804..de77335 100644
--- a/test/helpers.ts
+++ b/test/2.1.x/helpers.ts
@@ -1,7 +1,7 @@
 import * as assert from 'assert'
 import { right, either, fold } from 'fp-ts/lib/Either'
-import * as t from '../src/index'
-import { PathReporter } from '../src/PathReporter'
+import * as t from '../../src/index'
+import { PathReporter } from '../../src/PathReporter'
 import { pipe } from 'fp-ts/lib/pipeable'
 
 export function assertStrictEqual<T>(result: t.Validation<T>, expected: any): void {
diff --git a/test/intersection.ts b/test/2.1.x/intersection.ts
similarity index 99%
rename from test/intersection.ts
rename to test/2.1.x/intersection.ts
index 014c45e..5a0e2b2 100644
--- a/test/intersection.ts
+++ b/test/2.1.x/intersection.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertFailure, assertStrictEqual, assertSuccess, NumberFromString } from './helpers'
 
 describe('intersection', () => {
diff --git a/test/keyof.ts b/test/2.1.x/keyof.ts
similarity index 97%
rename from test/keyof.ts
rename to test/2.1.x/keyof.ts
index 578e3c6..736a89c 100644
--- a/test/keyof.ts
+++ b/test/2.1.x/keyof.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure } from './helpers'
 
 describe('keyof', () => {
diff --git a/test/literal.ts b/test/2.1.x/literal.ts
similarity index 97%
rename from test/literal.ts
rename to test/2.1.x/literal.ts
index 058a782..76b61a1 100644
--- a/test/literal.ts
+++ b/test/2.1.x/literal.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure } from './helpers'
 
 describe('literal', () => {
diff --git a/test/partial.ts b/test/2.1.x/partial.ts
similarity index 99%
rename from test/partial.ts
rename to test/2.1.x/partial.ts
index 6024000..cfa8119 100644
--- a/test/partial.ts
+++ b/test/2.1.x/partial.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure, assertStrictEqual, withDefault, NumberFromString } from './helpers'
 
 describe('partial', () => {
diff --git a/test/readonly.ts b/test/2.1.x/readonly.ts
similarity index 98%
rename from test/readonly.ts
rename to test/2.1.x/readonly.ts
index f077ba7..42f6100 100644
--- a/test/readonly.ts
+++ b/test/2.1.x/readonly.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure, NumberFromString } from './helpers'
 import { either } from 'fp-ts/lib/Either'
 
diff --git a/test/readonlyArray.ts b/test/2.1.x/readonlyArray.ts
similarity index 98%
rename from test/readonlyArray.ts
rename to test/2.1.x/readonlyArray.ts
index 991ce2d..71ccd09 100644
--- a/test/readonlyArray.ts
+++ b/test/2.1.x/readonlyArray.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure, NumberFromString } from './helpers'
 import { either } from 'fp-ts/lib/Either'
 
diff --git a/test/record.ts b/test/2.1.x/record.ts
similarity index 99%
rename from test/record.ts
rename to test/2.1.x/record.ts
index d0884fc..f925e8b 100644
--- a/test/record.ts
+++ b/test/2.1.x/record.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import {
   assertFailure,
   assertStrictEqual,
diff --git a/test/recursion.ts b/test/2.1.x/recursion.ts
similarity index 99%
rename from test/recursion.ts
rename to test/2.1.x/recursion.ts
index 537c474..5a1001b 100644
--- a/test/recursion.ts
+++ b/test/2.1.x/recursion.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure, assertStrictEqual, NumberFromString } from './helpers'
 
 type T = {
diff --git a/test/refinement.ts b/test/2.1.x/refinement.ts
similarity index 98%
rename from test/refinement.ts
rename to test/2.1.x/refinement.ts
index 596a342..8e303f9 100644
--- a/test/refinement.ts
+++ b/test/2.1.x/refinement.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure, assertStrictEqual, IntegerFromString, NumberFromString } from './helpers'
 
 describe('refinement', () => {
diff --git a/test/strict.ts b/test/2.1.x/strict.ts
similarity index 98%
rename from test/strict.ts
rename to test/2.1.x/strict.ts
index 35a4cf7..0717e2b 100644
--- a/test/strict.ts
+++ b/test/2.1.x/strict.ts
@@ -1,4 +1,4 @@
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure, assertStrictEqual, NumberFromString } from './helpers'
 import * as assert from 'assert'
 
diff --git a/test/strictInterfaceWithOptionals.ts b/test/2.1.x/strictInterfaceWithOptionals.ts
similarity index 98%
rename from test/strictInterfaceWithOptionals.ts
rename to test/2.1.x/strictInterfaceWithOptionals.ts
index 836ebab..50e9219 100644
--- a/test/strictInterfaceWithOptionals.ts
+++ b/test/2.1.x/strictInterfaceWithOptionals.ts
@@ -1,4 +1,4 @@
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertSuccess, assertFailure, NumberFromString } from './helpers'
 import * as assert from 'assert'
 
diff --git a/test/taggedUnion.ts b/test/2.1.x/taggedUnion.ts
similarity index 99%
rename from test/taggedUnion.ts
rename to test/2.1.x/taggedUnion.ts
index 95e6388..32e177a 100644
--- a/test/taggedUnion.ts
+++ b/test/2.1.x/taggedUnion.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertFailure, assertStrictEqual, assertSuccess, NumberFromString } from './helpers'
 
 // tslint:disable-next-line: deprecation
diff --git a/test/tuple.ts b/test/2.1.x/tuple.ts
similarity index 98%
rename from test/tuple.ts
rename to test/2.1.x/tuple.ts
index 461ec15..6f574b4 100644
--- a/test/tuple.ts
+++ b/test/2.1.x/tuple.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertFailure, assertStrictEqual, assertSuccess, NumberFromString } from './helpers'
 
 describe('tuple', () => {
diff --git a/test/type.ts b/test/2.1.x/type.ts
similarity index 99%
rename from test/type.ts
rename to test/2.1.x/type.ts
index 813dbb8..66be70f 100644
--- a/test/type.ts
+++ b/test/2.1.x/type.ts
@@ -1,7 +1,7 @@
 import * as assert from 'assert'
 import { fold } from 'fp-ts/lib/Either'
 import { pipe } from 'fp-ts/lib/pipeable'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertFailure, assertStrictEqual, assertSuccess, NumberFromString } from './helpers'
 
 describe('type', () => {
diff --git a/test/union.ts b/test/2.1.x/union.ts
similarity index 99%
rename from test/union.ts
rename to test/2.1.x/union.ts
index 0ca2a3c..1410dfa 100644
--- a/test/union.ts
+++ b/test/2.1.x/union.ts
@@ -1,5 +1,5 @@
 import * as assert from 'assert'
-import * as t from '../src/index'
+import * as t from '../../src/index'
 import { assertFailure, assertStrictEqual, assertSuccess, NumberFromString } from './helpers'
 import { either } from 'fp-ts/lib/Either'
 
diff --git a/test/Arbitrary.ts b/test/Arbitrary.ts
new file mode 100644
index 0000000..bc2bf6c
--- /dev/null
+++ b/test/Arbitrary.ts
@@ -0,0 +1,133 @@
+/**
+ * An instance of `Schemable` for `fast-check` arbitraries that emit valid values
+ */
+import * as fc from 'fast-check'
+import * as S from '../src/Schemable'
+import { intersect } from '../src/Decoder'
+
+// -------------------------------------------------------------------------------------
+// model
+// -------------------------------------------------------------------------------------
+
+export interface Arbitrary<A> extends fc.Arbitrary<A> {}
+
+// -------------------------------------------------------------------------------------
+// constructors
+// -------------------------------------------------------------------------------------
+
+export function literal<A extends ReadonlyArray<S.Literal>>(...values: A): Arbitrary<A[number]> {
+  return fc.oneof(...values.map((v) => fc.constant(v)))
+}
+
+// -------------------------------------------------------------------------------------
+// primitives
+// -------------------------------------------------------------------------------------
+
+export const string: Arbitrary<string> = fc.oneof(
+  fc.string(),
+  fc.asciiString(),
+  fc.fullUnicodeString(),
+  fc.hexaString(),
+  fc.lorem()
+)
+
+export const number: Arbitrary<number> = fc.oneof(fc.float(), fc.double(), fc.integer())
+
+export const boolean: Arbitrary<boolean> = fc.boolean()
+
+export const UnknownArray: Arbitrary<Array<unknown>> = fc.array(fc.anything())
+
+export const UnknownRecord: Arbitrary<Record<string, unknown>> = fc.dictionary(string, fc.anything())
+
+// -------------------------------------------------------------------------------------
+// combinators
+// -------------------------------------------------------------------------------------
+
+export function nullable<A>(or: Arbitrary<A>): Arbitrary<null | A> {
+  return fc.oneof(fc.constant(null), or)
+}
+
+export function type<A>(properties: { [K in keyof A]: Arbitrary<A[K]> }): Arbitrary<A> {
+  return fc.record(properties)
+}
+
+export function partial<A>(properties: { [K in keyof A]: Arbitrary<A[K]> }): Arbitrary<Partial<A>> {
+  const keys = fc.oneof(...Object.keys(properties).map((p) => fc.constant(p)))
+  return keys.chain((key) => {
+    const p: any = { ...properties }
+    delete p[key]
+    return fc.record(p)
+  })
+}
+
+export function record<A>(codomain: Arbitrary<A>): Arbitrary<Record<string, A>> {
+  return fc.dictionary(string, codomain)
+}
+
+export function array<A>(items: Arbitrary<A>): Arbitrary<Array<A>> {
+  return fc.array(items)
+}
+
+export function tuple<A extends ReadonlyArray<unknown>>(
+  ...components: { [K in keyof A]: Arbitrary<A[K]> }
+): Arbitrary<A> {
+  if (components.length === 0) {
+    return fc.constant([]) as any
+  }
+  return (fc.tuple as any)(...components)
+}
+
+export function intersection<A, B>(left: Arbitrary<A>, right: Arbitrary<B>): Arbitrary<A & B> {
+  return fc.tuple(left, right).map(([a, b]) => intersect(a, b))
+}
+
+export function sum<T extends string>(
+  _tag: T
+): <A>(members: { [K in keyof A]: Arbitrary<A[K] & Record<T, K>> }) => Arbitrary<A[keyof A]> {
+  return (members: Record<string, Arbitrary<any>>) => fc.oneof(...Object.keys(members).map((k) => members[k]))
+}
+
+export function lazy<A>(f: () => Arbitrary<A>): Arbitrary<A> {
+  const get = S.memoize<void, Arbitrary<A>>(f)
+  return fc.constant(null).chain(() => get())
+}
+
+export function union<A extends ReadonlyArray<unknown>>(
+  ...members: { [K in keyof A]: Arbitrary<A[K]> }
+): Arbitrary<A[number]> {
+  return fc.oneof(...members)
+}
+
+// -------------------------------------------------------------------------------------
+// instances
+// -------------------------------------------------------------------------------------
+
+export const URI = 'Arbitrary'
+
+export type URI = typeof URI
+
+declare module 'fp-ts/lib/HKT' {
+  interface URItoKind<A> {
+    readonly Arbitrary: Arbitrary<A>
+  }
+}
+
+export const arbitrary: S.Schemable<URI> & S.WithUnion<URI> = {
+  URI,
+  literal,
+  string,
+  number,
+  boolean,
+  UnknownArray,
+  UnknownRecord,
+  nullable,
+  type,
+  partial,
+  record,
+  array,
+  tuple: tuple as S.Schemable<URI>['tuple'],
+  intersection,
+  sum,
+  lazy: (_, f) => lazy(f),
+  union
+}
diff --git a/test/Codec.ts b/test/Codec.ts
index d2b96ca..20f1ec4 100644
--- a/test/Codec.ts
+++ b/test/Codec.ts
@@ -229,6 +229,19 @@ describe('Codec', () => {
           ])
         )
       })
+
+      it('should support getters', () => {
+        class A {
+          get a() {
+            return 'a'
+          }
+          get b() {
+            return 'b'
+          }
+        }
+        const codec = C.type({ a: C.string, b: C.string })
+        assert.deepStrictEqual(codec.decode(new A()), right({ a: 'a', b: 'b' }))
+      })
     })
 
     describe('encode', () => {
@@ -293,6 +306,19 @@ describe('Codec', () => {
           ])
         )
       })
+
+      it('should support getters', () => {
+        class A {
+          get a() {
+            return 'a'
+          }
+          get b() {
+            return 'b'
+          }
+        }
+        const codec = C.partial({ a: C.string, b: C.string })
+        assert.deepStrictEqual(codec.decode(new A()), right({ a: 'a', b: 'b' }))
+      })
     })
 
     describe('encode', () => {
diff --git a/test/Eq.ts b/test/Eq.ts
new file mode 100644
index 0000000..525fa33
--- /dev/null
+++ b/test/Eq.ts
@@ -0,0 +1,82 @@
+import * as assert from 'assert'
+import * as E from '../src/Eq'
+import { Eq } from 'fp-ts/lib/Eq'
+
+describe('Eq', () => {
+  it('literal', () => {
+    const eq = E.eq.literal('a', null)
+    assert.deepStrictEqual(eq.equals('a', 'a'), true)
+    assert.deepStrictEqual(eq.equals(null, null), true)
+    assert.deepStrictEqual(eq.equals('a', null), false)
+  })
+
+  it('UnknownArray', () => {
+    const eq = E.UnknownArray
+    assert.deepStrictEqual(eq.equals(['a'], ['a']), true)
+    assert.deepStrictEqual(eq.equals(['a'], ['b']), true)
+    assert.deepStrictEqual(eq.equals(['a'], ['a', 'b']), false)
+  })
+
+  it('UnknownRecord', () => {
+    const eq = E.UnknownRecord
+    assert.deepStrictEqual(eq.equals({}, {}), true)
+    assert.deepStrictEqual(eq.equals({ a: 1 }, { a: 1 }), true)
+    assert.deepStrictEqual(eq.equals({ a: 1 }, { a: 2 }), true)
+    assert.deepStrictEqual(eq.equals({ a: 1 }, { a: 1, b: true }), false)
+    assert.deepStrictEqual(eq.equals({ a: 1, b: true }, { a: 1 }), false)
+  })
+
+  it('partial', () => {
+    const eq = E.partial({ a: E.number })
+    assert.deepStrictEqual(eq.equals({ a: 1 }, { a: 1 }), true)
+    assert.deepStrictEqual(eq.equals({ a: undefined }, { a: undefined }), true)
+    assert.deepStrictEqual(eq.equals({}, { a: undefined }), true)
+    assert.deepStrictEqual(eq.equals({}, {}), true)
+    assert.deepStrictEqual(eq.equals({ a: 1 }, {}), false)
+  })
+
+  it('tuple', () => {
+    const eq = E.tuple(E.string, E.number)
+    assert.deepStrictEqual(eq.equals(['a', 1], ['a', 1]), true)
+    assert.deepStrictEqual(eq.equals(['a', 1], ['b', 1]), false)
+    assert.deepStrictEqual(eq.equals(['a', 1], ['a', 2]), false)
+  })
+
+  it('intersection', () => {
+    const eq = E.intersection(E.type({ a: E.string }), E.type({ b: E.number }))
+    assert.deepStrictEqual(eq.equals({ a: 'a', b: 1 }, { a: 'a', b: 1 }), true)
+    assert.deepStrictEqual(eq.equals({ a: 'a', b: 1 }, { a: 'c', b: 1 }), false)
+    assert.deepStrictEqual(eq.equals({ a: 'a', b: 1 }, { a: 'a', b: 2 }), false)
+  })
+
+  it('lazy', () => {
+    interface A {
+      a: number
+      b: Array<A>
+    }
+
+    const eq: Eq<A> = E.eq.lazy('A', () =>
+      E.type({
+        a: E.number,
+        b: E.array(eq)
+      })
+    )
+    assert.strictEqual(eq.equals({ a: 1, b: [] }, { a: 1, b: [] }), true)
+    assert.strictEqual(eq.equals({ a: 1, b: [{ a: 2, b: [] }] }, { a: 1, b: [{ a: 2, b: [] }] }), true)
+    assert.strictEqual(eq.equals({ a: 1, b: [] }, { a: 2, b: [] }), false)
+    assert.strictEqual(eq.equals({ a: 1, b: [{ a: 2, b: [] }] }, { a: 1, b: [{ a: 3, b: [] }] }), false)
+  })
+
+  it('sum', () => {
+    const sum = E.sum('_tag')
+    const eq = sum({
+      A: E.type({ _tag: E.eq.literal('A'), a: E.string }),
+      B: E.type({ _tag: E.eq.literal('B'), b: E.number })
+    })
+    assert.strictEqual(eq.equals({ _tag: 'A', a: 'a' }, { _tag: 'A', a: 'a' }), true)
+    assert.strictEqual(eq.equals({ _tag: 'B', b: 1 }, { _tag: 'B', b: 1 }), true)
+    assert.strictEqual(eq.equals({ _tag: 'A', a: 'a' }, { _tag: 'B', b: 1 }), false)
+    assert.strictEqual(eq.equals({ _tag: 'A', a: 'a' }, { _tag: 'A', a: 'b' }), false)
+    assert.strictEqual(eq.equals({ _tag: 'B', b: 1 }, { _tag: 'B', b: 2 }), false)
+  })
+})
diff --git a/test/Guard.ts b/test/Guard.ts
index 1c65cf7..7d89a21 100644
--- a/test/Guard.ts
+++ b/test/Guard.ts
@@ -52,6 +52,19 @@ describe('Guard', () => {
       const guard = G.type({ a: undef })
       assert.strictEqual(guard.is({}), false)
     })
+
+    it('should support getters', () => {
+      class A {
+        get a() {
+          return 'a'
+        }
+        get b() {
+          return 'b'
+        }
+      }
+      const guard = G.type({ a: G.string, b: G.string })
+      assert.deepStrictEqual(guard.is(new A()), true)
+    })
   })
 
   describe('partial', () => {
@@ -73,6 +86,19 @@ describe('Guard', () => {
       assert.strictEqual(guard.is(undefined), false)
       assert.strictEqual(guard.is({ a: 'a', b: 'b' }), false)
     })
+
+    it('should support getters', () => {
+      class A {
+        get a() {
+          return 'a'
+        }
+        get b() {
+          return 'b'
+        }
+      }
+      const guard = G.partial({ a: G.string, b: G.string })
+      assert.deepStrictEqual(guard.is(new A()), true)
+    })
   })
 
   describe('record', () => {
diff --git a/test/Schema.ts b/test/Schema.ts
index 6b8f2a6..2db29ba 100644
--- a/test/Schema.ts
+++ b/test/Schema.ts
@@ -1,19 +1,51 @@
-import * as assert from 'assert'
-import { right } from 'fp-ts/lib/Either'
+import { isRight, right } from 'fp-ts/lib/Either'
+import { Schema, make } from '../src/Schema'
+import * as G from '../src/Guard'
 import * as D from '../src/Decoder'
-import * as S from '../src/Schema'
 import * as E from '../src/Encoder'
-import * as G from '../src/Guard'
 import * as C from '../src/Codec'
+import * as Eq from '../src/Eq'
+import * as A from './Arbitrary'
+import * as fc from 'fast-check'
+import * as assert from 'assert'
 
-const Person = S.make((S) =>
-  S.type({
-    name: S.string,
-    age: S.number
-  })
-)
+function isDeepStrictEqual(actual: unknown, expected: unknown): boolean {
+  try {
+    assert.deepStrictEqual(actual, expected)
+    return true
+  } catch {
+    return false
+  }
+}
 
+function check<A>(schema: Schema<A>): void {
+  const arb = schema(A.arbitrary)
+  const codec = schema(C.codec)
+  const guard = schema(G.guard)
+  const eq = schema(Eq.eq)
+  // decoders, guards and eqs should be aligned
+  fc.assert(fc.property(arb, (a) => isRight(codec.decode(a)) && guard.is(a) && eq.equals(a, a)))
+  // laws
+  fc.assert(fc.property(arb, (a) => isRight(codec.decode(codec.encode(a)))))
+  fc.assert(
+    fc.property(arb, (u) => {
+      const a = codec.decode(u)
+      if (isRight(a)) {
+        const o = a.right
+        return isDeepStrictEqual(codec.encode(o), u) && eq.equals(o, u)
+      }
+      return false
+    })
+  )
+}
 describe('Schemable', () => {
+  const Person = make((S) =>
+    S.type({
+      name: S.string,
+      age: S.number
+    })
+  )
+
   it('should handle decoders', () => {
     assert.deepStrictEqual(Person(D.decoder).decode({ name: 'name', age: 46 }), right({ name: 'name', age: 46 }))
   })
@@ -30,4 +62,93 @@ describe('Schemable', () => {
     assert.deepStrictEqual(Person(C.codec).decode({ name: 'name', age: 46 }), right({ name: 'name', age: 46 }))
     assert.deepStrictEqual(Person(E.encoder).encode({ name: 'name', age: 46 }), { name: 'name', age: 46 })
   })
+
+  it('string', () => {
+    check(make((S) => S.string))
+  })
+
+  it('number', () => {
+    check(make((S) => S.number))
+  })
+
+  it('boolean', () => {
+    check(make((S) => S.boolean))
+  })
+
+  it('UnknownArray', () => {
+    check(make((S) => S.UnknownArray))
+  })
+
+  it('UnknownRecord', () => {
+    check(make((S) => S.UnknownRecord))
+  })
+
+  it('literal', () => {
+    check(make((S) => S.literal('a')))
+    check(make((S) => S.literal('a', 1)))
+    check(make((S) => S.literal('a', null)))
+  })
+
+  it('nullable', () => {
+    check(make((S) => S.nullable(S.string)))
+  })
+
+  it('type', () => {
+    check(
+      make((S) =>
+        S.type({
+          name: S.string,
+          age: S.number
+        })
+      )
+    )
+  })
+
+  it('partial', () => {
+    check(
+      make((S) =>
+        S.partial({
+          name: S.string,
+          age: S.number
+        })
+      )
+    )
+  })
+
+  it('record', () => {
+    check(make((S) => S.record(S.string)))
+  })
+
+  it('array', () => {
+    check(make((S) => S.array(S.string)))
+  })
+
+  it('tuple', () => {
+    check(make((S) => S.tuple()))
+    check(make((S) => S.tuple(S.string)))
+    check(make((S) => S.tuple(S.string, S.number)))
+  })
+
+  it('intersection', () => {
+    check(make((S) => S.intersection(S.type({ a: S.string }), S.type({ b: S.number }))))
+  })
+
+  it('sum', () => {
+    const A = make((S) => S.type({ _tag: S.literal('A'), a: S.string }))
+    const B = make((S) => S.type({ _tag: S.literal('B'), b: S.number }))
+    check(make((S) => S.sum('_tag')({ A: A(S), B: B(S) })))
+  })
+
+  it('lazy', () => {
+    interface A {
+      a: string
+      b?: A
+      c?: number
+    }
+
+    const schema: Schema<A> = make((S) =>
+      S.lazy('A', () => S.intersection(S.type({ a: S.string }), S.partial({ b: schema(S), c: S.number })))
+    )
+    check(schema)
+  })
 })
diff --git a/test/declaration.fixture b/test/declaration.fixture
deleted file mode 100644
index 59398b9..0000000
--- a/test/declaration.fixture
+++ /dev/null
@@ -1,140 +0,0 @@
-import * as t from '../src';
-export declare const T5: t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>;
-export declare const R1: t.RefinementC<t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>>;
-declare type R = {
-    a: number;
-    b: R | undefined | null;
-};
-export declare const Rec1: t.Type<R>;
-export declare const A1: t.ArrayC<t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>>;
-export declare const P5: t.PartialC<{
-    e: t.PartialC<{
-        d: t.PartialC<{
-            c: t.PartialC<{
-                b: t.PartialC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>;
-export declare const D5: t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.NumberC>>>>>;
-export declare const U1: t.UnionC<[t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.NumberC>>>>>]>;
-export declare const I1: t.IntersectionC<[t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.NumberC>>>>>]>;
-export declare const Tu1: t.TupleC<[t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.RecordC<t.StringC, t.NumberC>>>>>]>;
-export declare const RO1: t.ReadonlyC<t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>>;
-export declare const RA1: t.ReadonlyArrayC<t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>>;
-export declare const S5: t.ExactC<t.TypeC<{
-    e: t.ExactC<t.TypeC<{
-        d: t.ExactC<t.TypeC<{
-            c: t.ExactC<t.TypeC<{
-                b: t.ExactC<t.TypeC<{
-                    a: t.StringC;
-                }>>;
-            }>>;
-        }>>;
-    }>>;
-}>>;
-export declare const TU1: t.TaggedUnionC<"type", [t.TypeC<{
-    type: t.LiteralC<true>;
-    foo: t.StringC;
-}>, t.TypeC<{
-    type: t.LiteralC<false>;
-    bar: t.NumberC;
-}>]>;
-export declare const E1: t.ExactC<t.TypeC<{
-    e: t.TypeC<{
-        d: t.TypeC<{
-            c: t.TypeC<{
-                b: t.TypeC<{
-                    a: t.StringC;
-                }>;
-            }>;
-        }>;
-    }>;
-}>>;
-export declare const B1: t.TypeC<{
-    name: t.StringC;
-    age: t.BrandC<t.NumberC, t.IntBrand>;
-}>;
-export {};
diff --git a/test/declaration.ts b/test/declaration.ts
deleted file mode 100644
index 1cad00d..0000000
--- a/test/declaration.ts
+++ /dev/null
@@ -1,11 +0,0 @@
-import * as assert from 'assert'
-import { readFileSync } from 'fs'
-
-const actual = readFileSync('./declaration/out/declaration/index.d.ts').toString()
-const expected = readFileSync('./test/declaration.fixture').toString()
-
-describe('emitted declaration file', () => {
-  it('should emit nested types', () => {
-    assert.strictEqual(actual, expected)
-  })
-})