-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: adds
Assoc
, Universal
, evaluate
modules
- Loading branch information
1 parent
d89ddf1
commit 515cc14
Showing
9 changed files
with
266 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,180 @@ | ||
import * as any from "../any" | ||
import { TypeError, assert, describe, empty, enforce, evaluate, expect, never, nonempty } from "../exports"; | ||
|
||
export { | ||
assoc as Assoc, | ||
} | ||
|
||
declare namespace impl { | ||
type parseNumeric<type> = type extends `${infer x extends number}` ? x : never | ||
type arrayIndex<acc extends any.array<any.number>, type> | ||
= Extract<keyof type, `${acc["length"]}`> extends any.string<infer ix> | ||
? [ix] extends [never] ? acc | ||
: arrayIndex<[...acc, parseNumeric<ix>], type> | ||
: never.close.inline_var<"ix"> | ||
; | ||
|
||
type is_<ix extends void[], order, struct> | ||
= [order, []] extends [[], order] | ||
? [struct, {}] extends [{}, struct] | ||
? true | ||
: false | ||
: [struct, {}] extends [{}, struct] ? false | ||
: order extends empty.array ? [keyof struct] extends [never] ? true : false | ||
: order extends nonempty.array<infer head, infer tail> | ||
? [head] extends [keyof struct] | ||
? is_<[...ix, void], tail, Omit<struct, head>> | ||
: false | ||
: never.close.inline_var<"head" | "tail"> | ||
; | ||
} | ||
|
||
namespace impl { | ||
export const base = class { | ||
constructor(type: object) { | ||
Object.assign(this, type); | ||
} | ||
} as new <const type extends object>(type: type) => type; | ||
} | ||
|
||
declare const is | ||
: <const type>(type: type) => assoc.is<type> | ||
; | ||
|
||
type of<type extends any.entries> = never | [ | ||
{ [e in type[number]as e[0]]: e[1] }, | ||
Extract<{ -readonly [ix in keyof type]: type[ix][0] }, any.array>, | ||
] | ||
|
||
type make< | ||
type, | ||
constraint, | ||
returns | ||
> = [type] extends [TypeError<any>] | ||
? [type] extends [constraint & infer err] ? err | ||
: never : returns | ||
; | ||
|
||
declare const indices | ||
: <const type>(type: type) => indices<type> | ||
; | ||
type indices<type> = impl.arrayIndex<[], type> | ||
|
||
declare const separate | ||
: <const type>(type: type) => separate<type> | ||
; | ||
type separate<type> | ||
= indices<type> extends any.list<infer index> | ||
? { [ix in keyof index]: type[ix & keyof type] } extends any.list<infer order> | ||
? { [ix in Exclude<keyof type, keyof index>]: type[ix] } extends any.object<infer struct> | ||
? [order: order, object: struct] | ||
: never.close.inline_var<"struct"> | ||
: never.close.inline_var<"order"> | ||
: never.close.inline_var<"index"> | ||
; | ||
|
||
type is<type> | ||
= [type] extends [never] ? false | ||
: [Readonly<type>, empty.array] extends [empty.array, Readonly<type>] ? false | ||
: [type, []] extends [[], type] ? false | ||
: [type, {}] extends [{}, type] ? false | ||
: [any.array] extends [type] ? false | ||
: separate<type> extends [infer order, infer struct] | ||
? impl.is_<[], order, struct> | ||
: never.close.inline_var<"order" | "struct"> | ||
; | ||
|
||
/* @ts-expect-error - internal use only */ | ||
class Assoc<const type extends object> extends impl.base<type> { } | ||
type associative<type extends any.entries> = make<type, any.entries, assoc<of<type>>> | ||
|
||
type assoc<type extends readonly [any.object, any.array]> = never | Assoc<type[0] & type[1]> | ||
declare function assoc | ||
<const type extends any.entries & enforce.uniqNonNumericIndex<type>>(...type: type): associative<type> | ||
|
||
declare namespace assoc { | ||
export { | ||
is, | ||
separate, | ||
indices, | ||
} | ||
} | ||
|
||
namespace assoc { | ||
assoc.is = is | ||
assoc.separate = separate | ||
assoc.indices = indices | ||
} | ||
|
||
type __is__ = [ | ||
// ^? | ||
expect<assert.isFalse<assoc.is<any.array>>>, | ||
expect<assert.isFalse<assoc.is<[]>>>, | ||
expect<assert.isFalse<assoc.is<{}>>>, | ||
expect<assert.isFalse<assoc.is<[] & {}>>>, | ||
expect<assert.isFalse<assoc.is<[1]>>>, | ||
expect<assert.isFalse<assoc.is<[1] & { abc: 123 }>>>, | ||
expect<assert.isFalse<assoc.is<[] & { abc: 123 }>>>, | ||
expect<assert.isFalse<assoc.is<["abc", "def", "xyz"] & { abc: 123, def: 455, ghi: 789 }>>>, | ||
expect<assert.isFalse<assoc.is<["abc", "def", "ghii"] & { abc: 123, def: 455, ghi: 789 }>>>, | ||
expect<assert.isFalse<assoc.is<["abc", "def"] & { abc: 123, def: 455, ghi: 789 }>>>, | ||
// correct | ||
expect<assert.isTrue<assoc.is<["abc"] & { abc: 123 }>>>, | ||
expect<assert.isTrue<assoc.is<["abc", "def"] & { abc: 123, def: 455 }>>>, | ||
expect<assert.isTrue<assoc.is<["abc", "def"] & { abc: 123, def: 455 }>>>, | ||
] | ||
|
||
namespace __Spec__ { | ||
declare const failureCases: [ | ||
never, | ||
any, | ||
unknown, | ||
{}, | ||
[], | ||
readonly [], | ||
{} & [], | ||
any.array, | ||
any.object, | ||
any.array & any.object, | ||
{ abc: 123 } & ["xyz"], | ||
{ abc: 1230 }, | ||
{ 0: 123, 1: 456 } & [0, 1], | ||
] | ||
declare const happyPath: [ | ||
{ abc: 123 } & ["abc"], | ||
{ abc: 123, def: 456 } & ["abc", "def"], | ||
] | ||
declare const assoc1: Assoc<{ abc: 123; def: 456; } & ["abc", "def"]> | ||
declare const err1: TypeError<[𝗺𝘀𝗴: "Expected keys to be unique, but encountered 1 or more duplicate keys", 𝗴𝗼𝘁: ["abc"]]> | ||
|
||
describe("Assoc", () => { | ||
// ^? | ||
return [ | ||
describe("assoc", t => [ | ||
expect(t.assert.equal(assoc(["abc", 123], ["def", 456]), assoc1)), | ||
/* @ts-expect-error: this directive makes sure passing invalid input raises a TypeError */ | ||
expect(t.assert.equal(assoc(["abc", 123], ["abc", 456]), err1)), | ||
]), | ||
describe("is", t => [ | ||
// ^? | ||
expect(t.assert.isTrue(is(happyPath[0]))), | ||
expect(t.assert.isTrue(is(happyPath[1]))), | ||
]), | ||
describe("is (not)", t => [ | ||
// ^? | ||
expect(t.assert.isFalse(is(failureCases[0]))), | ||
expect(t.assert.isFalse(is(failureCases[1]))), | ||
expect(t.assert.isFalse(is(failureCases[2]))), | ||
expect(t.assert.isFalse(is(failureCases[3]))), | ||
expect(t.assert.isFalse(is(failureCases[4]))), | ||
expect(t.assert.isFalse(is(failureCases[5]))), | ||
expect(t.assert.isFalse(is(failureCases[6]))), | ||
expect(t.assert.isFalse(is(failureCases[7]))), | ||
expect(t.assert.isFalse(is(failureCases[8]))), | ||
expect(t.assert.isFalse(is(failureCases[9]))), | ||
expect(t.assert.isFalse(is(failureCases[10]))), | ||
]), | ||
] | ||
}) | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { Assoc } from "./associative" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
export { | ||
evaluate, | ||
} | ||
|
||
import { any } from "../exports"; | ||
|
||
/** | ||
* @example | ||
* interface A { b: B } | ||
* interface B { a: A } | ||
* declare const A: A | ||
* | ||
* const b = evaluate(A) | ||
* b // => const b: { b: A } | ||
* type b = evaluate<A> | ||
* // ^? type b = { b: A } | ||
* | ||
* const ba = evaluate(A, 2) | ||
* ba // => const ba: { b: { a: A } } | ||
* type ba = evaluate<A, 2> | ||
* // ^? type ba = { b: { a: A } } | ||
* | ||
* const bababababab = evaluate(A, -1) | ||
* bababababab // => const bababababab: { b: { a: { b: { a: { b: { a: { b: { a: { b: { a: { b: any } } } } } } } } } } } | ||
* type bababababab = evaluate<A, -1> | ||
* // ^? type bababababab = { b: { a: { b: { a: { b: { a: { b: { a: { b: { a: { b: any } } } } } } } } } } } | ||
*/ | ||
function evaluate<const type>(type: type): evaluate<type, 1> | ||
function evaluate<const type, maxDepth extends number>(type: type, maxDepth: maxDepth): evaluate<type, maxDepth> | ||
function evaluate<const type, maxDepth extends number>(type: type, _max?: maxDepth): unknown { return type } | ||
|
||
type evaluate<type, maxDepth extends number = 1> = evaluate.go<type, [], maxDepth> | ||
declare namespace evaluate { | ||
type go<type, currentDepth extends void[], maxDepth extends number> | ||
= maxDepth extends currentDepth["length"] ? type | ||
: type extends any.primitive | any.function ? type | ||
: { [ix in keyof type] | ||
: go<type[ix], [...currentDepth, void], maxDepth> } | ||
; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { evaluate } from "./evaluate" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export { Universal } from "./universal" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
export { | ||
Universal | ||
} | ||
|
||
import * as any from "../any" | ||
|
||
/** | ||
* TODO: | ||
* - [x]: support interpreter argument for {@link Tagless `Tagless`} | ||
* - [x]: write an {@link Ordered.object `Ordered.object`} interpreter | ||
* - [x] find implementation from TypeScript Discord | ||
* - [x]: in `any-ts`, write an enforcer with the following behavior: | ||
* - [x]: passing a key that is a numeric of any kind raises a `TypeError` | ||
* - [x]: numeric keys should be identified in the Error message | ||
* - [ ]: `show` | ||
* - [ ]: the following cases are not working: | ||
* @example | ||
* const _3 = evaluate(Tuple(["abc", Number(123)])) | ||
* const _4 = Intersection(["abc", { def: 1 }], ["ghi", { jkl: 1000 }]).type | ||
*/ | ||
|
||
type parseNumeric<type> = type extends `${infer x extends number}` ? x : never | ||
|
||
namespace Universal { export const never: never = void 0 as never } | ||
declare namespace Universal { | ||
type key<key extends any.index> = | ||
| `${Exclude<key, symbol>}` | ||
| parseNumeric<key> | ||
| key | ||
; | ||
|
||
type keyof<type> | ||
= type extends any.array | ||
? Universal.key<Extract<keyof type, `${number}`>> | ||
: Universal.key<keyof type> | ||
; | ||
} |