diff --git a/.changeset/serious-suns-drive.md b/.changeset/serious-suns-drive.md new file mode 100644 index 0000000000..c689b16407 --- /dev/null +++ b/.changeset/serious-suns-drive.md @@ -0,0 +1,6 @@ +--- +"type-plus": minor +--- + +Add `Assignable`. +Deprecated `CanAssign` and `StrictCanAssign`. diff --git a/type-plus/ts/index.ts b/type-plus/ts/index.ts index 91a71c45df..fe82e22b17 100644 --- a/type-plus/ts/index.ts +++ b/type-plus/ts/index.ts @@ -86,6 +86,7 @@ export type * from './object/is_not_strict_object.js' export type * from './object/is_object.js' export type * from './object/is_strict_object.js' export * as ObjectPlus from './object/object_plus.js' +export type * from './predicates/assignable.js' export * from './predicates/index.js' export type { PrimitiveTypes } from './primitive.js' export * from './promise/index.js' @@ -121,8 +122,8 @@ export type * from './type_plus/branch/$input_options.js' export type * from './type_plus/branch/$is_distributive.js' export type * from './type_plus/branch/$resolve_branch.js' export type * from './type_plus/branch/$select.js' -export type * from './type_plus/branch/$selection_options.js' export type * from './type_plus/branch/$selection.js' +export type * from './type_plus/branch/$selection_options.js' export type * from './undefined/has_undefined.js' export type * from './undefined/is_not_undefined.js' export type * from './undefined/is_undefined.js' diff --git a/type-plus/ts/predicates/CanAssign.ts b/type-plus/ts/predicates/CanAssign.ts index 1073061833..65d31d9249 100644 --- a/type-plus/ts/predicates/CanAssign.ts +++ b/type-plus/ts/predicates/CanAssign.ts @@ -1,5 +1,5 @@ import type { NotExtendable } from './Extends.js' -import type { IsEmptyObject } from './IsEmptyObject.js' +import type { Assignable } from './assignable.js' /** * Can `A` assign to `B` @@ -10,6 +10,8 @@ import type { IsEmptyObject } from './IsEmptyObject.js' * * This is the correct behavior. * + * @deprecated use `Assignable` instead + * * @example * ```ts * CanAssign // boolean @@ -37,23 +39,15 @@ export type CanAssign = boolean extends A * * All branches in an union `A` are assignable to `B`. * + * @deprecated use `Assignable // false * StrictCanAssign // true * ``` */ -export type StrictCanAssign = IsEmptyObject extends true - ? Record extends B - ? Then - : Else - : boolean extends A - ? boolean extends B - ? Then - : Else - : [A] extends [B] - ? Then - : Else +export type StrictCanAssign = Assignable /** * @deprecated use `CanAssign` instead diff --git a/type-plus/ts/predicates/Extends.ts b/type-plus/ts/predicates/Extends.ts index 0e35d90b8f..2968cc425f 100644 --- a/type-plus/ts/predicates/Extends.ts +++ b/type-plus/ts/predicates/Extends.ts @@ -1,3 +1,6 @@ +/** + * @deprecated use `$Assignable` + */ export type Extendable = A extends B ? Then : Else export type NotExtendable = A extends B ? Else : Then export type IsExtend = A extends B ? Then : Else diff --git a/type-plus/ts/predicates/assignable.spec.ts b/type-plus/ts/predicates/assignable.spec.ts new file mode 100644 index 0000000000..57d1892710 --- /dev/null +++ b/type-plus/ts/predicates/assignable.spec.ts @@ -0,0 +1,113 @@ +import { it } from '@jest/globals' +import { testType, type Assignable, type $Then, type $Else } from '../index.js' + +it('check if A can be assigned to B', () => { + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.false>(true) + testType.false>(true) + testType.false>(true) + testType.false>(true) +}) + +it('returns true when B is `any` as anything can be assigned to `any`', () => { + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) +}) + +it('returns true when B is `unknown` as anything can be assigned to `unknown`', () => { + testType.true>(true) + testType.true>(true) + testType.true>(true) + + testType.true>(true) + testType.true>(true) + testType.true>(true) +}) + +it('returns false when B is `never` except when A is `never`', () => { + testType.false>(true) + testType.false>(true) + testType.true>(true) + + testType.false>(true) + testType.false>(true) + testType.false>(true) +}) + +it('works against special types', () => { + testType.equal, true>(true) + testType.equal, true>(true) + testType.equal, true>(true) +}) + +it('can disable distribution', () => { + testType.equal, boolean>(true) + testType.equal, false>(true) + + testType.equal, boolean>(true) + testType.equal, false>(true) +}) + +it('can use as filter', () => { + testType.equal, 1>(true) + testType.never>(true) +}) + +it('work as branching', () => { + testType.equal, $Then>(true) + testType.equal, $Then>(true) + testType.equal, $Else>(true) + testType.equal, $Then>(true) + testType.equal, $Then>(true) + testType.equal, $Else>(true) +}) + +it('works with partial customization', () => { + testType.equal, 1>(true) + testType.equal, 1>(true) + testType.equal, 1>(true) + testType.equal, 1>(true) + testType.equal, 2>(true) + testType.equal, 2>(true) + + testType.equal, true>(true) + testType.equal, true>(true) + testType.equal, true>(true) + testType.equal, true>(true) + testType.equal, false>(true) + testType.equal, false>(true) +}) + +it('can override $any branch', () => { + testType.equal, true>(true) + testType.equal, unknown>(true) + testType.equal, unknown>(true) +}) + +it('can override $unknown branch', () => { + testType.equal, true>(true) + testType.equal, unknown>(true) + testType.equal, unknown>(true) +}) + +it('can override $never branch', () => { + testType.equal, true>(true) + testType.equal, unknown>(true) + testType.equal, unknown>(true) +}) diff --git a/type-plus/ts/predicates/assignable.ts b/type-plus/ts/predicates/assignable.ts new file mode 100644 index 0000000000..689f8008ad --- /dev/null +++ b/type-plus/ts/predicates/assignable.ts @@ -0,0 +1,94 @@ +import type { $Any } from '../any/any.js' +import type { $Never } from '../never/never.js' +import type { $SpecialType } from '../type_plus/$special_type.js' +import type { $DistributiveDefault, $DistributiveOptions } from '../type_plus/branch/$distributive.js' +import type { $InputOptions } from '../type_plus/branch/$input_options.js' +import type { $IsDistributive } from '../type_plus/branch/$is_distributive.js' +import type { $ResolveBranch } from '../type_plus/branch/$resolve_branch.js' +import type { $Else, $SelectionBranch, $SelectionPredicate, $Then } from '../type_plus/branch/$selection.js' +import type { $SelectionOptions } from '../type_plus/branch/$selection_options.js' +import type { $Unknown } from '../unknown/unknown.js' + +/** + * 🧰 *tool utils* + * + * Validate if `A` is assignable to `B`. + * + * @example + * ```ts + * type R = Assignable // true + * type R = Assignable // true + * type R = Assignable // true + * type R = Assignable // true + * type R = Assignable<1, 1> // true + * type R = Assignable<'a', 'a'> // true + * type R = Assignable<'a', 'b'> // false + * type R = Assignable<'a', string> // true + * ``` + * + * 🔢 *customize* + * + * Filter to ensure `A` is assignable to `B`. + * + * @example + * ```ts + * type R = Assignable // any + * type R = Assignable<1, number, { selection: 'filter' }> // 1 + * ``` + * + * 🔢 *customize* + * + * Use unique branch identifiers to allow precise processing of the result. + * + * @example + * ```ts + * type R = Assignable // $Then + * ``` + * + * 🔢 *customize* + * + * Override special types branch. + * + * @example + * ```ts + * type R = Assignable // 1 + * type R = Assignable // 1 + * type R = Assignable // 1 + * ``` + */ +export type Assignable< + A, + B, + $O extends Assignable.$Options = {} +> = $SpecialType, + $unknown: $ResolveBranch, + $never: $ResolveBranch, + $else: $SpecialType, + $unknown: $ResolveBranch, + $never: $ResolveBranch, + $else: Assignable.$ + }> +}> + + +export namespace Assignable { + export type $Options = $SelectionOptions & $DistributiveOptions & $InputOptions<$Any | $Unknown | $Never> + export type $Default = $SelectionPredicate & $DistributiveDefault + export type $Branch = $SelectionBranch & $DistributiveDefault + + /** + * 🧰 *type util* + * + * + */ + export type $< + A, + B, + $O extends Assignable.$Options = {} + > = $IsDistributive<$O, { + $then: $ResolveBranch, + $else: $ResolveBranch + }> +} diff --git a/type-plus/ts/predicates/index.ts b/type-plus/ts/predicates/index.ts index cbdae9010c..38dbbb13a2 100644 --- a/type-plus/ts/predicates/index.ts +++ b/type-plus/ts/predicates/index.ts @@ -4,4 +4,3 @@ export type { If } from './If.js' export type { IsEmptyObject } from './IsEmptyObject.js' export type { IsLiteral } from './literal.js' export type { And, Not, Or, Xor } from './logical.js' -