Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: remove tree type inferrence #10

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 12 additions & 15 deletions src/engine/operator.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,23 @@
import { inspect } from 'node:util'
import type { JSONExpr } from '../json/jsonexpr.type.js'
import type { AsExpression, DefinitionType, Expression, ValueExpression } from './types.js'
import { fromLiteral } from './types.js'

import type { JSONExpr } from '../json/jsonexpr.type.js'

import { inspect } from 'node:util'
export type FactsFomExprs<T> = T extends { facts: infer U } ? U : never

// biome-ignore lint/suspicious/noExplicitAny: any is necessary for the operator function
export interface Operator<I extends any[], O, Op extends string> {
symbol: string
operator: Op
<Exprs extends { [K in keyof I]: Expression<I[K]> | I[K] }>(
...exprs: Exprs
): ValueExpression<
O,
{ [K in keyof I]: AsExpression<Exprs[K]> },
FactsFomExprs<Exprs[number]>,
// biome-ignore lint/suspicious/noExplicitAny: any is necessary for the operator function
): ValueExpression<O, { [K in keyof I]: AsExpression<Exprs[K]> }, Extract<JSONExpr, Record<Op, any[] | JSONExpr>>>
Extract<JSONExpr, Record<Op, any[] | JSONExpr>>
>
}

export function operator<I extends unknown[], O, Op extends string = string>({
Expand All @@ -25,10 +30,7 @@ export function operator<I extends unknown[], O, Op extends string = string>({
symbol: string
}): Operator<I, O, Op> {
return Object.assign(
<Exprs extends { [K in keyof I]: Expression<I[K]> | I[K] }>(
...exprs: Exprs
// biome-ignore lint/suspicious/noExplicitAny: any is necessary for the operator function
): ValueExpression<O, { [K in keyof I]: AsExpression<Exprs[K]> }, Extract<JSONExpr, Record<Op, any[] | JSONExpr>>> => {
<Exprs extends { [K in keyof I]: Expression<I[K]> | I[K] }>(...exprs: Exprs) => {
const xs = exprs.map((x) => fromLiteral(x as I[number]))
return {
fn,
Expand All @@ -39,16 +41,11 @@ export function operator<I extends unknown[], O, Op extends string = string>({
[inspect.custom]() {
return `${symbol}(${xs.map((x) => x[inspect.custom]?.() ?? '').join(', ')})`
},
} as unknown as ValueExpression<
O,
{ [K in keyof I]: AsExpression<Exprs[K]> },
// biome-ignore lint/suspicious/noExplicitAny: any is necessary for the operator function
Extract<JSONExpr, Record<Op, any[] | JSONExpr>>
>
}
},
{
symbol,
operator,
},
)
) as unknown as Operator<I, O, Op>
}
24 changes: 9 additions & 15 deletions src/engine/policy.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { collect, stack } from '@skyleague/axioms'
import type { Simplify, UnionToIntersection } from '@skyleague/axioms/types'
import type { IsEmptyObject, Simplify } from '@skyleague/axioms/types'
import { version } from '../../package.json'
import type { FactsFomExprs } from './operator.js'
import type { Expression, ExpressionReturnType } from './types.js'

export class EvaluationContext {
Expand Down Expand Up @@ -73,37 +74,31 @@ function* collapseExpression(root: Expression[], seen = new WeakSet()) {

type InferFactName<Expr, k> = Expr extends { name: string } ? Expr['name'] : k

type FilterFactExpressions<Facts extends Record<string, unknown>, k extends keyof Facts> = Facts[k] extends {
type FilterFactExpressions<Facts, K extends keyof Facts> = Facts[K] extends {
_type: 'fact'
}
? InferFactName<Facts[k], k>
? InferFactName<Facts[K], K>
: never

type CollapseTreeToArray<T> = T extends { dependsOn: (infer U)[] } ? T | CollapseTreeToArray<U> : T
type _InputFromExpressions<Facts extends Record<string, unknown>> = Simplify<{
[k in keyof Facts as FilterFactExpressions<Facts, k>]: ExpressionReturnType<Facts[k]>
type _InputFromExpressions<Facts> = Simplify<{
[K in keyof Facts as FilterFactExpressions<Facts, K>]: ExpressionReturnType<Facts[K]>
}>
type NamedUnionToRecord<T> = T extends { name: infer Name } ? (Name extends PropertyKey ? { [k in Name]: T } : never) : never

type _FactsFomExprs<Facts> = Facts extends unknown[] ? Facts[number] : Facts
export type InputFromExpressions<Facts extends Record<string, unknown>> = Simplify<
_InputFromExpressions<Facts> &
_InputFromExpressions<
UnionToIntersection<NamedUnionToRecord<Facts extends Record<string, infer E> ? CollapseTreeToArray<E> : never>>
>
_InputFromExpressions<Facts> & _InputFromExpressions<{ [K in keyof Facts]: _FactsFomExprs<FactsFomExprs<Facts[K]>> }>
>

export type OutputFromFacts<Facts extends Record<string, unknown>> = Simplify<{
[k in keyof Facts]: ExpressionReturnType<Facts[k]>
}>

export interface Policy<I, O> {
evaluate: [I] extends [never] ? () => { input: I; output: O } : (x: I) => { input: I; output: O }
evaluate: IsEmptyObject<I> extends true ? () => { input: I; output: O } : (x: I) => { input: I; output: O }
expr: () => unknown
}

export function $policy<Facts extends Record<string, Expression>>(
expressions: Facts,
// @ts-ignore
): Policy<InputFromExpressions<Facts>, OutputFromFacts<Facts>> {
const facts = Object.entries(expressions).map(([name, e]) => {
if (e.name === undefined) {
Expand All @@ -122,7 +117,6 @@ export function $policy<Facts extends Record<string, Expression>>(
const properties = Object.fromEntries(inputNodes.map((e) => [e.name, e.expr('definition')]))
const outputExpression = Object.fromEntries(outputNodes.map((f) => [f.name, f.expr('expression')]))
return {
// @ts-ignore
evaluate: ((input: Record<string, unknown>) => {
const ctx = new EvaluationContext(input)

Expand Down
4 changes: 2 additions & 2 deletions src/engine/types.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,10 @@ describe('fromLiteral', () => {
const fact = $fact(Arithmetic, 'input')
const fa = fromLiteral($from(fact, '$.a'))
const _fa_: Expression<number> = fa
expectTypeOf(fa).toEqualTypeOf<From<Arithmetic, number, [Fact<Arithmetic, 'input'>]>>()
expectTypeOf(fa).toEqualTypeOf<From<Arithmetic, number, Fact<Arithmetic, 'input'>>>()
const fb = fromLiteral($from(fact, '$.b'))
const _fb_: Expression<number> = fb
expectTypeOf(fb).toEqualTypeOf<From<Arithmetic, number, [Fact<Arithmetic, 'input'>]>>()
expectTypeOf(fb).toEqualTypeOf<From<Arithmetic, number, Fact<Arithmetic, 'input'>>>()

type val = ['1', '2']
const _test_multiple = {} as { [K in keyof val]: AsExpression<val[K]> }
Expand Down
17 changes: 11 additions & 6 deletions src/engine/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { EvaluationContext } from './policy.js'

import { $literal } from '../expressions/input.js'
import { $literal, type Fact } from '../expressions/input.js'
import type {
BooleanArrExpr,
BooleanExpr,
Expand Down Expand Up @@ -40,6 +40,7 @@ export interface Expression<O = any, I = any, Expr extends JSONExpr | ValueItemE
_type?: string
name?: PropertyKey
dependsOn: Expression[]
// facts?: Expression[]
fn: (x: I, ctx: EvaluationContext) => O
expr: (definition: DefinitionType) => Expr
[inspect.custom]?(): string
Expand All @@ -50,30 +51,34 @@ export type ExpressionReturnType<E> = E extends Pick<Expression, 'fn'> ? ReturnT
// biome-ignore lint/suspicious/noExplicitAny: this is needed for greedy matching
export interface LiteralExpression<O = any, Expr extends JSONExpr = InferExpressionType<O>> extends Expression<O, any, Expr> {
dependsOn: []
// facts: []
_type: 'literal'
}

export interface FactExpression<T, Expr extends JSONExpr = InferExpressionType<T>> extends Expression<T, unknown, Expr> {
// biome-ignore lint/suspicious/noExplicitAny: this is needed for greedy matching
export interface FactExpression<T = any, Expr extends JSONExpr = InferExpressionType<T>> extends Expression<T, unknown, Expr> {
_type: 'fact'
}

export interface InputExpression<
O,
I,
DependsOn extends Expression[],
F extends Fact<Expression, string>,
Expr extends JSONExpr | ValueItemExpr = InferExpressionType<O>,
> extends Expression<O, I, Expr> {
dependsOn: DependsOn
dependsOn: [F]
facts: [F]
_type: 'value' | 'literal'
}

export type InputFromExpressions<Expr extends Expression[]> = {
[k in keyof Expr]: ExpressionReturnType<Expr[k]>
}

export interface ValueExpression<O, DependsOn extends Expression[], Expr extends JSONExpr = InferExpressionType<O>>
export interface ValueExpression<O, DependsOn extends Expression[], Facts, Expr extends JSONExpr = InferExpressionType<O>>
extends Expression<O, InputFromExpressions<DependsOn>, Expr> {
dependsOn: DependsOn
// dependsOn: DependsOn
facts: Facts //extends FactExpression ? [Facts] : never
_type?: 'value'
}

Expand Down
57 changes: 42 additions & 15 deletions src/expressions/boolean.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,20 @@ describe('startsWith', () => {

const x1 = $startsWith('1', '2')
expectTypeOf(x1).toEqualTypeOf<
ValueExpression<boolean, [LiteralExpression<'1', StringExpr>, LiteralExpression<'2', StringExpr>], StartsWithExpr>
ValueExpression<
boolean,
[LiteralExpression<'1', StringExpr>, LiteralExpression<'2', StringExpr>],
never,
StartsWithExpr
>
>()

const x2 = $startsWith($from(fact, '$.a'), '2')
expectTypeOf(x2).toEqualTypeOf<
ValueExpression<
boolean,
[From<AbObj, string, [Fact<AbObj, 'input'>]>, LiteralExpression<'2', StringExpr>],
[str: From<AbObj, string, Fact<AbObj, 'input'>>, searchString: LiteralExpression<'2', StringExpr>],
[Fact<AbObj, 'input'>],
StartsWithExpr
>
>()
Expand All @@ -106,7 +112,8 @@ describe('startsWith', () => {
expectTypeOf(x3).toEqualTypeOf<
ValueExpression<
boolean,
[LiteralExpression<'2', StringExpr>, From<AbObj, string, [Fact<AbObj, 'input'>]>],
[str: LiteralExpression<'2', StringExpr>, searchString: From<AbObj, string, Fact<AbObj, 'input'>>],
[Fact<AbObj, 'input'>],
StartsWithExpr
>
>()
Expand Down Expand Up @@ -190,14 +197,20 @@ describe('endsWith', () => {

const x1 = $endsWith('1', '2')
expectTypeOf(x1).toEqualTypeOf<
ValueExpression<boolean, [LiteralExpression<'1', StringExpr>, LiteralExpression<'2', StringExpr>], EndsWithExpr>
ValueExpression<
boolean,
[str: LiteralExpression<'1', StringExpr>, searchString: LiteralExpression<'2', StringExpr>],
never,
EndsWithExpr
>
>()

const x2 = $endsWith($from(fact, '$.a'), '2')
expectTypeOf(x2).toEqualTypeOf<
ValueExpression<
boolean,
[From<AbObj, string, [Fact<AbObj, 'input'>]>, LiteralExpression<'2', StringExpr>],
[str: From<AbObj, string, Fact<AbObj, 'input'>>, searchString: LiteralExpression<'2', StringExpr>],
[Fact<AbObj, 'input'>],
EndsWithExpr
>
>()
Expand All @@ -206,7 +219,8 @@ describe('endsWith', () => {
expectTypeOf(x3).toEqualTypeOf<
ValueExpression<
boolean,
[LiteralExpression<'2', StringExpr>, From<AbObj, string, [Fact<AbObj, 'input'>]>],
[str: LiteralExpression<'2', StringExpr>, searchString: From<AbObj, string, Fact<AbObj, 'input'>>],
[Fact<AbObj, 'input'>],
EndsWithExpr
>
>()
Expand Down Expand Up @@ -290,14 +304,20 @@ describe('includes', () => {

const x1 = $includes('1', '2')
expectTypeOf(x1).toEqualTypeOf<
ValueExpression<boolean, [LiteralExpression<'1', StringExpr>, LiteralExpression<'2', StringExpr>], IncludesExpr>
ValueExpression<
boolean,
[LiteralExpression<'1', StringExpr>, LiteralExpression<'2', StringExpr>],
never,
IncludesExpr
>
>()

const x2 = $includes($from(fact, '$.a'), '2')
expectTypeOf(x2).toEqualTypeOf<
ValueExpression<
boolean,
[From<AbObj, string, [Fact<AbObj, 'input'>]>, LiteralExpression<'2', StringExpr>],
[str: From<AbObj, string, Fact<AbObj, 'input'>>, searchString: LiteralExpression<'2', StringExpr>],
[Fact<AbObj, 'input'>],
IncludesExpr
>
>()
Expand All @@ -306,7 +326,8 @@ describe('includes', () => {
expectTypeOf(x3).toEqualTypeOf<
ValueExpression<
boolean,
[LiteralExpression<'2', StringExpr>, From<AbObj, string, [Fact<AbObj, 'input'>]>],
[str: LiteralExpression<'2', StringExpr>, searchString: From<AbObj, string, Fact<AbObj, 'input'>>],
[Fact<AbObj, 'input'>],
IncludesExpr
>
>()
Expand Down Expand Up @@ -413,7 +434,9 @@ describe('all', () => {
const fact = $fact(LogicObj, 'input')

const x1 = $all([1, 2], (x) => $equal(x, 1))
expectTypeOf(x1).toEqualTypeOf<ValueExpression<boolean, [LiteralExpression<number[], NumberArrExpr>], BooleanExpr>>()
expectTypeOf(x1).toEqualTypeOf<
ValueExpression<boolean, [LiteralExpression<number[], NumberArrExpr>], never, BooleanExpr>
>()

const a = $from(fact, '$.d')
const x2 = $all(a, (x) => $equal(x, 1))
Expand All @@ -427,17 +450,18 @@ describe('all', () => {
a: boolean
b: boolean
}[],
[Fact<LogicObj, 'input'>]
Fact<LogicObj, 'input'>
>,
],
[Fact<LogicObj, 'input'>],
BooleanExpr
>
>()

const ba = $from(fact, '$.b..a')
const x3 = $all(ba, (x) => $equal(x, 1))
expectTypeOf(x3).toEqualTypeOf<
ValueExpression<boolean, [From<LogicObj, never[], [Fact<LogicObj, 'input'>]>], BooleanExpr>
ValueExpression<boolean, [From<LogicObj, never[], Fact<LogicObj, 'input'>>], [Fact<LogicObj, 'input'>], BooleanExpr>
>()
})
})
Expand Down Expand Up @@ -542,7 +566,9 @@ describe('any', () => {
const fact = $fact(LogicObj, 'input')

const x1 = $any([1, 2], (x) => $equal(x, 1))
expectTypeOf(x1).toEqualTypeOf<ValueExpression<boolean, [LiteralExpression<number[], NumberArrExpr>], BooleanExpr>>()
expectTypeOf(x1).toEqualTypeOf<
ValueExpression<boolean, [LiteralExpression<number[], NumberArrExpr>], never, BooleanExpr>
>()

const a = $from(fact, '$.d')
const x2 = $any(a, (x) => $equal(x, 1))
Expand All @@ -556,17 +582,18 @@ describe('any', () => {
a: boolean
b: boolean
}[],
[Fact<LogicObj, 'input'>]
Fact<LogicObj, 'input'>
>,
],
[Fact<LogicObj, 'input'>],
BooleanExpr
>
>()

const ba = $from(fact, '$.b..a')
const x3 = $any(ba, (x) => $equal(x, 1))
expectTypeOf(x3).toEqualTypeOf<
ValueExpression<boolean, [From<LogicObj, never[], [Fact<LogicObj, 'input'>]>], BooleanExpr>
ValueExpression<boolean, [From<LogicObj, never[], Fact<LogicObj, 'input'>>], [Fact<LogicObj, 'input'>], BooleanExpr>
>()
})
})
10 changes: 5 additions & 5 deletions src/expressions/boolean.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { $value, type ValueItem } from './higher-order-fn.js'

import { operator } from '../engine/operator.js'
import { type FactsFomExprs, operator } from '../engine/operator.js'
import {
type AsExpression,
type Expression,
Expand Down Expand Up @@ -44,7 +44,7 @@ export const $all = Object.assign(
<Expr extends LiteralOr<any[]>>(
xs: Expr,
predicate: (value: ValueItem<ExpressionTypeOfLiteral<Expr>>) => Expression<boolean>,
): ValueExpression<boolean, [AsExpression<Expr>]> => {
): ValueExpression<boolean, [AsExpression<Expr>], FactsFomExprs<Expr>> => {
const _xs = fromLiteral(xs)
const _value = $value<ExpressionTypeOfLiteral<Expr>>()
const _transform = predicate(_value)
Expand All @@ -59,7 +59,7 @@ export const $all = Object.assign(
[inspect.custom]() {
return `$all(${_xs[inspect.custom]?.() ?? ''}, (x) => ${_transform[inspect.custom]?.() ?? ''})`
},
} as ValueExpression<boolean, [AsExpression<Expr>]>
} as ValueExpression<boolean, [AsExpression<Expr>], FactsFomExprs<Expr>>
},
// biome-ignore lint/suspicious/noExplicitAny: this is needed for greedy matching
{ operator: 'all', symbol: '$all', parse: (xs: any, predicate: any) => $all(xs, () => predicate) } as const,
Expand All @@ -70,7 +70,7 @@ export const $any = Object.assign(
<Expr extends LiteralOr<any[]>>(
xs: Expr,
predicate: (value: ValueItem<ExpressionTypeOfLiteral<Expr>>) => Expression<boolean>,
): ValueExpression<boolean, [AsExpression<Expr>]> => {
): ValueExpression<boolean, [AsExpression<Expr>], FactsFomExprs<Expr>> => {
const _xs = fromLiteral(xs)
const _value = $value<ExpressionTypeOfLiteral<Expr>>()
const _transform = predicate(_value)
Expand All @@ -85,7 +85,7 @@ export const $any = Object.assign(
[inspect.custom]() {
return `$any(${_xs[inspect.custom]?.() ?? ''}, (x) => ${_transform[inspect.custom]?.() ?? ''})`
},
} as ValueExpression<boolean, [AsExpression<Expr>]>
} as ValueExpression<boolean, [AsExpression<Expr>], FactsFomExprs<Expr>>
},
// biome-ignore lint/suspicious/noExplicitAny: this is needed for greedy matching
{ operator: 'any', symbol: '$any', parse: (xs: any, predicate: any) => $any(xs, () => predicate) } as const,
Expand Down
Loading