Skip to content

Commit

Permalink
feat: adds Assoc, Universal, evaluate modules
Browse files Browse the repository at this point in the history
  • Loading branch information
ahrjarrett committed Jan 28, 2024
1 parent d89ddf1 commit 515cc14
Show file tree
Hide file tree
Showing 9 changed files with 266 additions and 3 deletions.
180 changes: 180 additions & 0 deletions src/associative/associative.ts
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]))),
]),
]
})
}

1 change: 1 addition & 0 deletions src/associative/exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Assoc } from "./associative"
40 changes: 40 additions & 0 deletions src/evaluate/evaluate.ts
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> }
;
}
1 change: 1 addition & 0 deletions src/evaluate/exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { evaluate } from "./evaluate"
3 changes: 3 additions & 0 deletions src/exports.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ export { never } from "./semantic-never/exports"
export { assert, describe, expect, expectToFail } from "./test/exports"
export { Err, Msg, TypeError, enforce } from "./err/exports"
export { type pathsof } from "./paths/exports"
export { evaluate } from "./evaluate/exports"
export { Universal } from "./universal/exports"
export { Assoc } from "./associative/exports"
2 changes: 1 addition & 1 deletion src/lens/focus.ts
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ type _9 = Tree.joinLeft<
interface Storage<type = {}> { [0]: type }


interface IndexedStorage<index extends any.array<any.key>, type extends any.object> {
interface IndexedStorage<index extends any.array<any.index>, type extends any.object> {
[-1]: index
[0]: type
}
Expand Down
4 changes: 2 additions & 2 deletions src/traversable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,8 @@ declare namespace impl {
type unfold<leaf, path extends any.array<any.key>> = impl.unfold<path, leaf>

/**
* {@link from `traversable.from`} is a type-constructor that takes a path and, via induction,
* constructs the tree it describes.
* {@link from `traversable.from`} is a type-constructor that takes a path describing a tree and,
* via induction, constructs the tree it describes.
*
* See also: {@link of `traversable.of`}
*
Expand Down
1 change: 1 addition & 0 deletions src/universal/exports.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { Universal } from "./universal"
37 changes: 37 additions & 0 deletions src/universal/universal.ts
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>
;
}

0 comments on commit 515cc14

Please sign in to comment.