Skip to content

Commit

Permalink
feat: merge NeverType to IsNever
Browse files Browse the repository at this point in the history
  • Loading branch information
unional committed Sep 24, 2023
1 parent e6c9954 commit e61d38d
Show file tree
Hide file tree
Showing 15 changed files with 312 additions and 316 deletions.
12 changes: 9 additions & 3 deletions type-plus/ts/any/is_any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import type { $Else, $ResolveSelection, $SelectionOptions, $Then } from '../type

/**
* 🎭 *predicate*
* 🔢 *customize*
*
* Validate if `T` is exactly `any`.
*
* @example
* ```ts
* type R = IsAny<any> // true
*
Expand All @@ -14,8 +14,11 @@ import type { $Else, $ResolveSelection, $SelectionOptions, $Then } from '../type
* type R = IsAny<string | boolean> // false
* ```
*
* 🔢 *customize*: filter
* 🔢 *customize*
*
* Filter to ensure `T` is exactly `any`.
*
* @example
* ```ts
* type R = IsAny<any, { selection: 'filter' }> // any
*
Expand All @@ -24,8 +27,11 @@ import type { $Else, $ResolveSelection, $SelectionOptions, $Then } from '../type
* type R = IsAny<string | boolean, { selection: 'filter' }> // never
* ```
*
* 🔢 *customize*: branching
* 🔢 *customize*
*
* Use unique branch identifiers to allow precise processing of the result.
*
* @example
* ```ts
* type R = IsAny<any, $SelectionBranch> // $Then
* type R = IsAny<string, $SelectionBranch> // $Else
Expand Down
20 changes: 18 additions & 2 deletions type-plus/ts/any/is_not_any.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@ import type { IsAny } from './is_any.js'

/**
* 🎭 *predicate*
* 🔢 *customize*
*
* Validate if `T` is not exactly `any`.
*
* @example
* ```ts
* type R = IsNotAny<any> // false
*
Expand All @@ -15,8 +15,24 @@ import type { IsAny } from './is_any.js'
* type R = IsNotAny<string | boolean> // true
* ```
*
* 🔢 *customize*: branching
* 🔢 *customize*
*
* Filter to ensure `T` is not exactly `any`.
*
* @example
* ```ts
* type R = IsNotAny<any, { selection: 'filter' }> // never
*
* type R = IsNotAny<never, { selection: 'filter' }> // never
* type R = IsNotAny<unknown, { selection: 'filter' }> // unknown
* type R = IsNotAny<string | boolean, { selection: 'filter' }> // string | boolean
* ```
*
* 🔢 *customize*
*
* Use unique branch identifiers to allow precise processing of the result.
*
* @example
* ```ts
* type R = IsNotAny<any, $SelectionBranch> // $Else
* type R = IsNotAny<string, $SelectionBranch> // $Then
Expand Down
92 changes: 23 additions & 69 deletions type-plus/ts/any/readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,74 +4,11 @@
It is a supertype of all types.
It is a way to opt-out of type checking and let the values pass through compile-time checks.

## [AnyType](./any_type.ts)

`AnyType<T, $O = { $then: T, $else: never }>`

🌪️ *filter*
🔢 *customize*

Filter to ensure `T` is exactly `any`.

```ts
type R = AnyType<any> // any

type R = AnyType<never> // never
type R = AnyType<unknown> // never
type R = AnyType<string | boolean> // never
```
🔢 *customize*: as predicate/validate (= `IsAny`)
```ts
type R = AnyType<any, $SelectionPredicate> // true
type R = AnyType<string, $SelectionPredicate> // false
```
🔢 *customize*: branching
```ts
type R = AnyType<any, $SelectionBranch> // $Then
type R = AnyType<string, $SelectionBranch> // $Else
```
## [NotAnyType](./not_any_type.ts)
`NotAnyType<T, $O = { $then: T, $else: never }>`
🌪️ *filter*
🔢 *customize*
Filter to ensure `T` is not exactly `any`.
```ts
type R = NotAnyType<any> // never

type R = NotAnyType<never> // never
type R = NotAnyType<unknown> // unknown
type R = NotAnyType<string | boolean> // string | boolean
```
🔢 *customize*: as predicate/validate (= `IsNotAny`)
```ts
type R = NotAnyType<string, $SelectionPredicate> // true
type R = NotAnyType<any, $SelectionPredicate> // false
```
🔢 *customize*: branching
```ts
type R = NotAnyType<string, $SelectionBranch> // $Then
type R = NotAnyType<any, $SelectionBranch> // $Else
```
## [IsAny](./is_any.ts)

`IsAny<T, $O = { $then: true, $else: false }>`
`IsAny<T, $O = { selection: 'predicate' | 'filter', $then: true, $else: false }>`

🎭 *predicate*
🔢 *customize*

Validate if `T` is exactly `any`.

Expand All @@ -83,7 +20,9 @@ type R = IsAny<unknown> // false
type R = IsAny<string | boolean> // false
```
🔢 *customize*: filter
🔢 *customize*
Filter to ensure `T` is exactly `any`.
```ts
type R = IsAny<any, { selection: 'filter' }> // any
Expand All @@ -93,7 +32,9 @@ type R = IsAny<unknown, { selection: 'filter' }> // never
type R = IsAny<string | boolean, { selection: 'filter' }> // never
```
🔢 *customize*: branching
🔢 *customize*
Use unique branch identifiers to allow precise processing of the result.
```ts
type R = IsAny<any, $SelectionBranch> // $Then
Expand All @@ -102,10 +43,9 @@ type R = IsAny<string, $SelectionBranch> // $Else
### [IsNotAny](./is_not_any.ts)
`IsNotAny<T, $O = { $then: true, $else: false }>`
`IsNotAny<T, $O = { selection: 'predicate' | 'filter', $then: true, $else: false }>`
🎭 *predicate*
🔢 *customize*
Validate if `T` is not exactly `any`.
Expand All @@ -117,7 +57,21 @@ type R = IsNotAny<unknown> // true
type R = IsNotAny<string | boolean> // true
```
🔢 *customize*: branching
🔢 *customize*
Filter to ensure `T` is not exactly `any`.
```ts
type R = IsNotAny<any, { selection: 'filter' }> // never

type R = IsNotAny<never, { selection: 'filter' }> // never
type R = IsNotAny<unknown, { selection: 'filter' }> // unknown
type R = IsNotAny<string | boolean, { selection: 'filter' }> // string | boolean
```
🔢 *customize*
Use unique branch identifiers to allow precise processing of the result.
```ts
type R = IsNotAny<any, $SelectionBranch> // $Else
Expand Down
2 changes: 0 additions & 2 deletions type-plus/ts/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,6 @@ export * from './mix_types/merge.js'
export type * from './never/is_never.js'
export type * from './never/is_not_never.js'
export type * from './never/never.js'
export type * from './never/never_type.js'
export type * from './never/not_never_type.js'
export * from './nodejs/index.js'
export * from './nominal/index.js'
export type * from './null/non_null.js'
Expand Down
45 changes: 44 additions & 1 deletion type-plus/ts/never/is_never.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { it } from '@jest/globals'
// never intersect with any type is never
import { testType, type IsNever } from '../index.js'
import { describe } from 'node:test'
import { testType, type $NotNever, type IsNever } from '../index.js'

it('returns true for never', () => {
testType.true<IsNever<never>>(true)
Expand Down Expand Up @@ -48,3 +49,45 @@ it('can override Then/Else', () => {
testType.equal<IsNever<unknown, { $then: 1, $else: 2 }>, 2>(true)
testType.equal<IsNever<void, { $then: 1, $else: 2 }>, 2>(true)
})

describe('filter', () => {

it('returns never if T is never', () => {
testType.equal<IsNever<never, { selection: 'filter' }>, never>(true)
})

it('returns $NotNever for other special types', () => {
testType.equal<IsNever<unknown, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<void, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<any, { selection: 'filter' }>, $NotNever>(true)
})

it('returns $NotNever for other types', () => {
testType.equal<IsNever<undefined, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<null, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<number, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<boolean, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<true, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<false, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<string, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<'', { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<symbol, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<bigint, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<{}, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<string[], { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<[], { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<Function, { selection: 'filter' }>, $NotNever>(true)
testType.equal<IsNever<() => void, { selection: 'filter' }>, $NotNever>(true)
})

it('returns $NotNever for union type', () => {
testType.equal<IsNever<never | 1, { selection: 'filter' }>, $NotNever>(true)
})

it('returns never for intersection type', () => {
// TypeScript resolve this to `never` automatically,
// so `IsNever<>` actually does not do anthing in this case.
testType.never<never & { a: 1 }>(true)
testType.never<IsNever<never & { a: 1 }, { selection: 'filter' }>>(true)
})
})
55 changes: 49 additions & 6 deletions type-plus/ts/never/is_never.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,72 @@
import type { $SelectionOptions, $SelectionPredicate } from '../type_plus/branch/selection.js'
import type { $SelectionOptions } from '../type_plus/branch/selection.js'
import type { $ResolveOptions } from '../type_plus/resolve_options.js'
import type { $NotNever } from './never.js'

/**
* 🎭 *predicate*
* 🔢 *customize*
*
* Validate if `T` is `never`.
*
* @example
* ```ts
* type R = IsNever<never> // true
*
* type R = IsNever<1> // false
* ```
*
* 🔢 *customize*: branching
* 🔢 *customize*
*
* Filter to ensure `T` is `never`, otherwise returns `$NotNever`.
*
* Filter normally returns `never` in the `$else` clause.
* But since we are checking for `never` here,
* we have to return `$NotNever` instead.
*
* @example
* ```ts
* type R = IsNever<never, { selection: 'filter' }> // never
*
* type R = IsNever<1, { selection: 'filter' }> // $NotNever
* ```
*
* 🔢 *customize*
*
* Filter to ensure `T` is `never`, otherwise returns `unknown`.
*
* @example
* ```ts
* type R = IsNever<1, { selection: 'filter-unknown' }> // unknown
* ```
*
* 🔢 *customize*
*
* Use unique branch identifiers to allow precise processing of the result.
*
* @example
* ```ts
* type R = IsNever<never, $SelectionBranch> // $Then
* type R = IsNever<1, $SelectionBranch> // $Else
* ```
*/
export type IsNever<
T,
$O extends $SelectionOptions = $SelectionPredicate
$O extends IsNever.$Options = {}
> = [T, never] extends [never, T]
? $ResolveOptions<[$O['$then'], true]>
: $ResolveOptions<[$O['$else'], false]>
? $ResolveOptions<[$O['$then'], $O['selection'] extends 'filter' | 'filter-unknown' ? T : true]>
: $ResolveOptions<[$O['$else'], $O['selection'] extends 'filter'
? $NotNever
: $O['selection'] extends 'filter-unknown' ? unknown : false]>


export namespace IsNever {
export type $Options = Omit<$SelectionOptions, 'selection'> & {
/**
* On top of `selection` values from `$SelectionOptions`,
*
* it also support:
*
* `filter-unknown` which returns `unknown` when `T` is `never`
*/
selection?: 'predicate' | 'filter' | 'filter-unknown' | undefined
}
}
Loading

0 comments on commit e61d38d

Please sign in to comment.