diff --git a/changelog/0.29.0.md b/changelog/0.29.0.md index e4cca9f..1291219 100644 --- a/changelog/0.29.0.md +++ b/changelog/0.29.0.md @@ -6,6 +6,15 @@ Revision 0.29.0 makes a minor interface and schema representation change to the As this revision constitutes a breaking representation change for `Type.Not`, a minor semver revision is required. +## Contents + +- Enhancements + - [Type.Not Representation Change](#Representation-Change) + - [Not Inversion](#Not-Inversion) + - [Inference Limitations](#Inference-Limitations) + + + ## Type.Not Representation Change The `Type.Not` was first introduced in Revision 0.26.0. This type accepted two arguments, the first is the `not` type, the second is the `allowed` type. In 0.26.0, TypeBox would treat the `allowed` type as the inferred type with the schema represented in the following form. @@ -81,11 +90,13 @@ const T = { // type T = Static // type T = number ``` -The 0.29.0 `Not` type properly represents the JSON Schema `not` keyword in its simplest form, as well as making better use of the type intersection narrowing capabilities of TypeScript with respect to inference. +The 0.29.0 `Not` type properly represents the JSON Schema `not` keyword in its simplest form, as well as making better use of intersection type narrowing capabilities of TypeScript. -### Invert Not + -In revision 0.29.0, it is possible to invert the `Not` type. TypeBox will track each inversion and statically infer appropriately. +## Not Inversion + +The not type can be inverted through nesting. ```typescript // not not string @@ -105,4 +116,29 @@ const T = { // inferred as // type T = Static // type T = string -``` \ No newline at end of file +``` + + + +## Inference Limitations + +Not types are synonymous with the concept of [negated types](https://github.com/microsoft/TypeScript/issues/4196) which are not supported in the TypeScript language. Because of this, it is not currently possible to infer negated types in a way one would naturally expect for some cases. Consider the following. + +```typescript +const T = Type.Intersect([Type.String(), Type.Not(Type.String())]) + +type T = Static // type T = string & not string + // actual: string + // expect: never +``` +As such, the use of Not types should be used with some consideration to current limitations, and reserved primarily for narrowing cases such as the following. + +```typescript +const T = Type.Intersect([Type.String(), Type.Not(Type.Literal('disallowed string'))]) + +type T = Static // type T = string & not 'disallowed string' + // actual: string + // expect: string +``` + + diff --git a/readme.md b/readme.md index 7e7caaa..a390850 100644 --- a/readme.md +++ b/readme.md @@ -888,14 +888,16 @@ const C = Type.Index(T, Type.KeyOf(T)) // const C = { ### Not Types -Not types are supported with `Type.Not`. This type represents the JSON Schema `not` keyword and will statically infer as `unknown`. Note that Not types are not supported in TypeScript, but can still be partially expressed by interpreting `not` as the broad type `unknown`. When used in intersections, the Not type can be used to create refined assertion rules for specific types, with the inference derived from TypeScript's ability to narrow from `unknown` to `T` via intersection. - -For example, consider a type which is `number` but not `1 | 2 | 3` and where the static type would still technically be a `number`. The following shows a pseudo TypeScript example using `not` followed by the TypeBox implementation. +TypeBox has partial support for the JSON schema `not` keyword with `Type.Not`. This type is synonymous with the concept of a [negated types](https://github.com/microsoft/TypeScript/issues/4196) which are not supported in the TypeScript language. TypeBox does provide partial inference support via the intersection of `T & not U` (where all negated types infer as `unknown`). This can be used in the following context. ```typescript -// Pseudo TypeScript +// TypeScript -type T = number & not (1 | 2 | 3) // allow all numbers except 1, 2, 3 +type T = Exclude // all numbers except 1, 2, 3 + // + // ideally expressed as: + // + // type T = number & not (1 | 2 | 3) // TypeBox @@ -914,11 +916,11 @@ const T = Type.Intersect([ // const T = { // ] // } -type T = Static // evaluates as: +type T = Static // inferred: // - // type T = (number & (not (1 | 2 | 3))) - // type T = (number & (unknown)) - // type T = (number) + // type T = number & not (1 | 2 | 3) + // type T = number & unknown + // type T = number ``` The Not type can be used with constraints to define schematics for types that would otherwise be difficult to express.