Comparing version 1.1.0-dev-2 to 2.0.0-dev.0
16
main.ts
@@ -1,5 +0,7 @@ | ||
export { Problem, Problems } from "@arktype/schema" | ||
export type { Out } from "@arktype/schema" | ||
export type { Module, Scope } from "./scope.js" | ||
export { | ||
ArkErrors as ArkErrors, | ||
type ArkTypeError as ArkTypeError | ||
} from "@arktype/schema" | ||
export type { Out, is } from "@arktype/schema" | ||
export { | ||
ark, | ||
@@ -12,10 +14,6 @@ arktypes, | ||
type, | ||
when, | ||
type Ark | ||
} from "./scopes/ark.js" | ||
export { jsObjects } from "./scopes/jsObjects.js" | ||
export { tsGenerics } from "./scopes/tsGenerics.js" | ||
export { tsKeywords } from "./scopes/tsKeywords.js" | ||
export { validation } from "./scopes/validation.js" | ||
} from "./ark.js" | ||
export type { Module, Scope } from "./scope.js" | ||
export { Type } from "./type.js" | ||
export type { inferTypeRoot, validateTypeRoot } from "./type.js" |
115
match.ts
import type { Morph } from "@arktype/schema" | ||
import type { | ||
conform, | ||
entryOf, | ||
ErrorMessage, | ||
evaluate, | ||
Fn, | ||
isDisjoint, | ||
join, | ||
paramsOf, | ||
replaceKey, | ||
@@ -19,37 +13,2 @@ returnOf, | ||
type cedille = "¸" | ||
type serializedWhentry< | ||
k extends string, | ||
v extends string | ||
> = `[${k}${cedille}${v}]` | ||
type parseWhentryKey< | ||
s extends string, | ||
$, | ||
result = {} | ||
> = s extends `${serializedWhentry<infer k, infer v>}${cedille}${infer tail}` | ||
? validateTypeRoot<v, $> extends ErrorMessage<infer message> | ||
? ErrorMessage<message> | ||
: parseWhentryKey<tail, $, result & { [_ in k]: inferTypeRoot<v, $> }> | ||
: s extends serializedWhentry<infer k, infer v> | ||
? validateTypeRoot<v, $> extends ErrorMessage<infer message> | ||
? ErrorMessage<message> | ||
: evaluate<result & { [_ in k]: inferTypeRoot<v, $> }> | ||
: validateTypeRoot<s, $> extends ErrorMessage<infer message> | ||
? ErrorMessage<message> | ||
: never | ||
export type WhenParser<$> = <const def>( | ||
def: validateTypeRoot<def, $> | ||
) => join< | ||
unionToTuple<`[${join<conform<entryOf<def>, [string, string]>, cedille>}]`>, | ||
cedille | ||
> | ||
export const createWhenParser = <$>(scope: Scope): WhenParser<$> => { | ||
const parser = (def: unknown) => new Type(def, scope).alias | ||
return parser as never | ||
} | ||
type MatchContext = { | ||
@@ -63,13 +22,43 @@ inConstraint: unknown | ||
type validateCases<cases, ctx extends MatchContext> = { | ||
// adding keyof $ explicitly provides key completions for aliases | ||
[k in keyof cases | keyof ctx["$"]]?: k extends validateTypeRoot<k, ctx["$"]> | ||
[k in keyof cases | keyof ctx["$"] | "default"]?: k extends "default" | ||
? (In: ctx["inConstraint"]) => ctx["outConstraint"] | ||
: k extends validateTypeRoot<k, ctx["$"]> | ||
? ( | ||
In: ctx["inConstraint"] & inferTypeRoot<k, ctx["$"]> | ||
) => ctx["outConstraint"] | ||
: parseWhentryKey<k & string, ctx["$"]> extends ErrorMessage<infer message> | ||
? ErrorMessage<message> | ||
: (In: parseWhentryKey<k & string, ctx["$"]>) => ctx["outConstraint"] | ||
: validateTypeRoot<k, ctx["$"]> | ||
} | ||
export type MatchParser<$> = { | ||
type errorCases<cases, ctx extends MatchContext> = { | ||
[k in keyof cases]?: k extends "default" | ||
? (In: ctx["inConstraint"]) => ctx["outConstraint"] | ||
: k extends validateTypeRoot<k, ctx["$"]> | ||
? ( | ||
In: ctx["inConstraint"] & inferTypeRoot<k, ctx["$"]> | ||
) => ctx["outConstraint"] | ||
: validateTypeRoot<k, ctx["$"]> | ||
} & { | ||
[k in Exclude<keyof ctx["$"], keyof cases>]?: ( | ||
In: ctx["inConstraint"] & ctx["$"][k] | ||
) => ctx["outConstraint"] | ||
} & { | ||
default?: (In: ctx["inConstraint"]) => ctx["outConstraint"] | ||
} | ||
export type CaseMatchParser<ctx extends MatchContext> = { | ||
<cases>( | ||
def: cases extends validateCases<cases, ctx> | ||
? cases | ||
: errorCases<cases, ctx> | ||
): ChainableMatchParser< | ||
replaceKey<ctx, "thens", [...ctx["thens"], ...unionToTuple<valueOf<cases>>]> | ||
> | ||
} | ||
export type MatchParser<$> = CaseMatchParser<{ | ||
inConstraint: unknown | ||
outConstraint: unknown | ||
thens: [] | ||
$: $ | ||
}> & { | ||
<In = unknown, Out = unknown>(): ChainableMatchParser<{ | ||
@@ -81,15 +70,4 @@ inConstraint: In | ||
}> | ||
} & CaseMatchParser<{ | ||
inConstraint: unknown | ||
outConstraint: unknown | ||
thens: [] | ||
$: $ | ||
}> | ||
} | ||
export type CaseMatchParser<ctx extends MatchContext> = <cases>( | ||
def: conform<cases, validateCases<cases, ctx>> | ||
) => ChainableMatchParser< | ||
replaceKey<ctx, "thens", [...ctx["thens"], ...unionToTuple<valueOf<cases>>]> | ||
> | ||
export type WhenMatchParser<ctx extends MatchContext> = < | ||
@@ -105,12 +83,14 @@ def, | ||
export type MatchInvokation<ctx extends MatchContext> = < | ||
export type MatchInvocation<ctx extends MatchContext> = < | ||
data extends ctx["inConstraint"] | ||
>( | ||
In: data | ||
data: data | ||
) => { | ||
[i in Extract<keyof ctx["thens"], `${number}`>]: isDisjoint< | ||
data, | ||
paramsOf<ctx["thens"][i]>[0] | ||
> extends true | ||
? never | ||
[i in Extract<keyof ctx["thens"], `${number}`>]: ctx["thens"][i] extends Fn< | ||
[infer In], | ||
infer Out | ||
> | ||
? isDisjoint<data, In> extends true | ||
? never | ||
: Out | ||
: returnOf<ctx["thens"][i]> | ||
@@ -120,3 +100,3 @@ }[Extract<keyof ctx["thens"], `${number}`>] | ||
export type ChainableMatchParser<ctx extends MatchContext> = | ||
MatchInvokation<ctx> & { | ||
MatchInvocation<ctx> & { | ||
cases: CaseMatchParser<ctx> | ||
@@ -127,3 +107,2 @@ when: WhenMatchParser<ctx> | ||
export const createMatchParser = <$>(scope: Scope): MatchParser<$> => { | ||
// TODO: move to match node, discrimination | ||
const parser = (cases: Record<string, Morph>) => { | ||
@@ -130,0 +109,0 @@ const caseArray = Object.entries(cases).map(([def, morph]) => ({ |
{ | ||
"name": "arktype", | ||
"description": "TypeScript's 1:1 validator, optimized from editor to runtime", | ||
"version": "1.1.0-dev-2", | ||
"version": "2.0.0-dev.0", | ||
"license": "MIT", | ||
@@ -16,28 +16,26 @@ "author": { | ||
"type": "module", | ||
"main": "./out/main.js", | ||
"types": "./out/main.d.ts", | ||
"exports": { | ||
".": { | ||
"arktype-repo": "./main.js", | ||
"types": "./dist/main.d.ts", | ||
"default": "./dist/main.js" | ||
"types": "./out/main.d.ts", | ||
"default": "./out/main.js" | ||
}, | ||
"./internal/*": { | ||
"arktype-repo": "./*", | ||
"types": "./dist/*", | ||
"default": "./dist/*" | ||
"default": "./out/*" | ||
} | ||
}, | ||
"files": [ | ||
"dist", | ||
"**/*.ts", | ||
"!**/*.tsBuildInfo", | ||
"!__tests__" | ||
"out", | ||
"!__tests__", | ||
"**/*.ts" | ||
], | ||
"scripts": { | ||
"build": "tsc --build ./tsconfig.build.json", | ||
"test": "ts ../repo/testPackage.ts" | ||
"build": "tsx ../repo/build.ts", | ||
"test": "tsx ../repo/testPackage.ts" | ||
}, | ||
"dependencies": { | ||
"@arktype/util": "0.0.5", | ||
"@arktype/schema": "0.0.2" | ||
"@arktype/util": "0.0.17", | ||
"@arktype/schema": "0.0.3" | ||
} | ||
} |
@@ -1,6 +0,6 @@ | ||
import { isNode, node, type Root } from "@arktype/schema" | ||
import { isNode, schema, type TypeNode } from "@arktype/schema" | ||
import { | ||
isThunk, | ||
objectKindOf, | ||
stringify, | ||
printable, | ||
throwParseError, | ||
@@ -20,4 +20,4 @@ type defined, | ||
} from "@arktype/util" | ||
import type { type } from "../ark.js" | ||
import type { ParseContext } from "../scope.js" | ||
import type { type } from "../scopes/ark.js" | ||
import { Type } from "../type.js" | ||
@@ -38,9 +38,12 @@ import { | ||
export const parseObject = (def: object, ctx: ParseContext): Root => { | ||
export const parseObject = (def: object, ctx: ParseContext): TypeNode => { | ||
const objectKind = objectKindOf(def) | ||
switch (objectKind) { | ||
case undefined: | ||
if (isNode(def)) { | ||
return def as Root | ||
if (def instanceof Type) { | ||
return def.root | ||
} | ||
if (isNode(def) && def.isType()) { | ||
return def | ||
} | ||
return parseObjectLiteral(def as Dict, ctx) | ||
@@ -50,3 +53,3 @@ case "Array": | ||
case "RegExp": | ||
return node({ basis: "string", pattern: def as RegExp }) | ||
return schema({ basis: "string", pattern: def as RegExp }) | ||
case "Function": | ||
@@ -60,3 +63,3 @@ const resolvedDef = isThunk(def) ? def() : def | ||
return throwParseError( | ||
writeBadDefinitionTypeMessage(objectKind ?? stringify(def)) | ||
writeBadDefinitionTypeMessage(objectKind ?? printable(def)) | ||
) | ||
@@ -69,12 +72,12 @@ } | ||
: def extends type.cast<infer t> | ThunkCast<infer t> | ||
? t | ||
: def extends string | ||
? inferString<def, $, args> | ||
: def extends readonly unknown[] | ||
? inferTuple<def, $, args> | ||
: def extends RegExp | ||
? string | ||
: def extends object | ||
? inferObjectLiteral<def, $, args> | ||
: never | ||
? t | ||
: def extends string | ||
? inferString<def, $, args> | ||
: def extends readonly unknown[] | ||
? inferTuple<def, $, args> | ||
: def extends RegExp | ||
? string | ||
: def extends object | ||
? inferObjectLiteral<def, $, args> | ||
: never | ||
@@ -84,18 +87,18 @@ export type validateDefinition<def, $, args> = null extends undefined | ||
: [def] extends [Terminal] | ||
? unknown extends def | ||
? // if def is any, never is the only way we can make validation fail | ||
// since any would be assignable to a standard error message | ||
never | ||
: def | ||
: def extends string | ||
? validateString<def, $, args> | ||
: def extends readonly unknown[] | ||
? validateTuple<def, $, args> | ||
: def extends BadDefinitionType | ||
? writeBadDefinitionTypeMessage<objectKindOrDomainOf<def>> | ||
: isUnknown<def> extends true | ||
? // this allows the initial list of autocompletions to be populated when a user writes "type()", | ||
// before having specified a definition | ||
BaseCompletions<$, args> | {} | ||
: validateObjectLiteral<def, $, args> | ||
? unknown extends def | ||
? // if def is any, never is the only way we can make validation fail | ||
// since any would be assignable to a standard error message | ||
never | ||
: def | ||
: def extends string | ||
? validateString<def, $, args> | ||
: def extends readonly unknown[] | ||
? validateTuple<def, $, args> | ||
: def extends BadDefinitionType | ||
? writeBadDefinitionTypeMessage<objectKindOrDomainOf<def>> | ||
: isUnknown<def> extends true | ||
? // this allows the initial list of autocompletions to be populated when a user writes "type()", | ||
// before having specified a definition | ||
BaseCompletions<$, args> | {} | ||
: validateObjectLiteral<def, $, args> | ||
@@ -114,23 +117,23 @@ export type validateDeclared<declared, def, $, args> = | ||
: def extends readonly unknown[] | ||
? declared extends readonly unknown[] | ||
? { | ||
[i in keyof declared]: i extends keyof def | ||
? validateInference<def[i], declared[i], $, args> | ||
: unknown | ||
} | ||
: evaluate<declarationMismatch<def, declared, $, args>> | ||
: def extends object | ||
? evaluate< | ||
{ | ||
[k in requiredKeyOf<declared>]: k extends keyof def | ||
? validateInference<def[k], declared[k], $, args> | ||
: unknown | ||
} & { | ||
[k in optionalKeyOf<declared> & | ||
string as `${k}?`]: `${k}?` extends keyof def | ||
? validateInference<def[`${k}?`], defined<declared[k]>, $, args> | ||
: unknown | ||
} | ||
> | ||
: validateShallowInference<def, declared, $, args> | ||
? declared extends readonly unknown[] | ||
? { | ||
[i in keyof declared]: i extends keyof def | ||
? validateInference<def[i], declared[i], $, args> | ||
: unknown | ||
} | ||
: evaluate<declarationMismatch<def, declared, $, args>> | ||
: def extends object | ||
? evaluate< | ||
{ | ||
[k in requiredKeyOf<declared>]: k extends keyof def | ||
? validateInference<def[k], declared[k], $, args> | ||
: unknown | ||
} & { | ||
[k in optionalKeyOf<declared> & | ||
string as `${k}?`]: `${k}?` extends keyof def | ||
? validateInference<def[`${k}?`], defined<declared[k]>, $, args> | ||
: unknown | ||
} | ||
> | ||
: validateShallowInference<def, declared, $, args> | ||
@@ -137,0 +140,0 @@ type validateShallowInference<def, declared, $, args> = equals< |
@@ -1,15 +0,4 @@ | ||
import type { Root } from "@arktype/schema" | ||
import { | ||
throwParseError, | ||
type ErrorMessage, | ||
type join, | ||
type nominal | ||
} from "@arktype/util" | ||
import type { ParseContext } from "../scope.js" | ||
import { DynamicState } from "./string/reduce/dynamic.js" | ||
import { writeUnclosedGroupMessage } from "./string/reduce/shared.js" | ||
import type { StaticState, state } from "./string/reduce/static.js" | ||
import { throwParseError, type nominal } from "@arktype/util" | ||
import { writeUnexpectedCharacterMessage } from "./string/shift/operator/operator.js" | ||
import { Scanner } from "./string/shift/scanner.js" | ||
import { parseUntilFinalizer } from "./string/string.js" | ||
@@ -53,4 +42,4 @@ export type GenericDeclaration< | ||
: nextNonWhitespace === "," | ||
? [param, ...parseGenericParamsRecurse(scanner)] | ||
: throwParseError(writeUnexpectedCharacterMessage(nextNonWhitespace, ",")) | ||
? [param, ...parseGenericParamsRecurse(scanner)] | ||
: throwParseError(writeUnexpectedCharacterMessage(nextNonWhitespace, ",")) | ||
} | ||
@@ -66,145 +55,16 @@ | ||
: lookahead extends Scanner.WhiteSpaceToken | ||
? param extends "" | ||
? // if the next char is whitespace and we aren't in the middle of a param, skip to the next one | ||
parseParamsRecurse<Scanner.skipWhitespace<nextUnscanned>, "", result> | ||
: Scanner.skipWhitespace<nextUnscanned> extends `${infer nextNonWhitespace}${infer rest}` | ||
? nextNonWhitespace extends "," | ||
? parseParamsRecurse<rest, "", [...result, param]> | ||
: GenericParamsParseError< | ||
writeUnexpectedCharacterMessage<nextNonWhitespace, ","> | ||
> | ||
: // params end with a single whitespace character, add the current token | ||
[...result, param] | ||
: parseParamsRecurse<nextUnscanned, `${param}${lookahead}`, result> | ||
? param extends "" | ||
? // if the next char is whitespace and we aren't in the middle of a param, skip to the next one | ||
parseParamsRecurse<Scanner.skipWhitespace<nextUnscanned>, "", result> | ||
: Scanner.skipWhitespace<nextUnscanned> extends `${infer nextNonWhitespace}${infer rest}` | ||
? nextNonWhitespace extends "," | ||
? parseParamsRecurse<rest, "", [...result, param]> | ||
: GenericParamsParseError< | ||
writeUnexpectedCharacterMessage<nextNonWhitespace, ","> | ||
> | ||
: // params end with a single whitespace character, add the current token | ||
[...result, param] | ||
: parseParamsRecurse<nextUnscanned, `${param}${lookahead}`, result> | ||
: param extends "" | ||
? result | ||
: [...result, param] | ||
export type ParsedArgs< | ||
result extends unknown[] = unknown[], | ||
unscanned extends string = string | ||
> = { | ||
result: result | ||
unscanned: unscanned | ||
} | ||
export const parseGenericArgs = ( | ||
name: string, | ||
params: string[], | ||
unscanned: string, | ||
ctx: ParseContext | ||
) => parseGenericArgsRecurse(name, params, unscanned, ctx, [], []) | ||
export type parseGenericArgs< | ||
name extends string, | ||
params extends string[], | ||
unscanned extends string, | ||
$, | ||
args | ||
> = parseGenericArgsRecurse<name, params, unscanned, $, args, [], []> | ||
const parseGenericArgsRecurse = ( | ||
name: string, | ||
params: string[], | ||
unscanned: string, | ||
ctx: ParseContext, | ||
argDefs: string[], | ||
argNodes: Root[] | ||
): ParsedArgs<Root[]> => { | ||
const s = parseUntilFinalizer(new DynamicState(unscanned, ctx)) | ||
// remove the finalizing token from the argDef | ||
argDefs.push(s.scanner.scanned.slice(0, -1)) | ||
argNodes.push(s.root) | ||
const nextUnscanned = s.scanner.unscanned | ||
if (s.finalizer === ">") { | ||
if (argNodes.length === params.length) { | ||
return { | ||
result: argNodes, | ||
unscanned: nextUnscanned | ||
} | ||
} else { | ||
return s.error(writeInvalidGenericArgsMessage(name, params, argDefs)) | ||
} | ||
} else if (s.finalizer === ",") { | ||
return parseGenericArgsRecurse( | ||
name, | ||
params, | ||
nextUnscanned, | ||
ctx, | ||
argDefs, | ||
argNodes | ||
) | ||
} | ||
return s.error(writeUnclosedGroupMessage(">")) | ||
} | ||
type parseGenericArgsRecurse< | ||
name extends string, | ||
params extends string[], | ||
unscanned extends string, | ||
$, | ||
args, | ||
argDefs extends string[], | ||
argAsts extends unknown[] | ||
> = parseUntilFinalizer< | ||
state.initialize<unscanned>, | ||
$, | ||
args | ||
> extends infer finalArgState extends StaticState | ||
? { | ||
defs: [ | ||
...argDefs, | ||
finalArgState["scanned"] extends `${infer def}${"," | ">"}` | ||
? def | ||
: finalArgState["scanned"] | ||
] | ||
asts: [...argAsts, finalArgState["root"]] | ||
unscanned: finalArgState["unscanned"] | ||
} extends { | ||
defs: infer nextDefs extends string[] | ||
asts: infer nextAsts extends unknown[] | ||
unscanned: infer nextUnscanned extends string | ||
} | ||
? finalArgState["finalizer"] extends ">" | ||
? nextAsts["length"] extends params["length"] | ||
? ParsedArgs<nextAsts, nextUnscanned> | ||
: state.error<writeInvalidGenericArgsMessage<name, params, nextDefs>> | ||
: finalArgState["finalizer"] extends "," | ||
? parseGenericArgsRecurse< | ||
name, | ||
params, | ||
nextUnscanned, | ||
$, | ||
args, | ||
nextDefs, | ||
nextAsts | ||
> | ||
: finalArgState["finalizer"] extends ErrorMessage | ||
? finalArgState | ||
: state.error<writeUnclosedGroupMessage<">">> | ||
: never | ||
: never | ||
export const writeInvalidGenericArgsMessage = < | ||
name extends string, | ||
params extends string[], | ||
argDefs extends string[] | ||
>( | ||
name: name, | ||
params: params, | ||
argDefs: argDefs | ||
) => | ||
`${name}<${params.join(", ")}> requires exactly ${params.length} args (got ${ | ||
argDefs.length | ||
}${argDefs.length === 0 ? "" : ": " + argDefs.join(", ")})` | ||
export type writeInvalidGenericArgsMessage< | ||
name extends string, | ||
params extends string[], | ||
argDefs extends string[] | ||
> = `${name}<${join< | ||
params, | ||
", " | ||
>}> requires exactly ${params["length"]} args (got ${argDefs["length"]}${argDefs["length"] extends 0 | ||
? "" | ||
: `: ${join<argDefs, ",">}`})` | ||
? result | ||
: [...result, param] |
@@ -1,10 +0,14 @@ | ||
import { node, type Inner } from "@arktype/schema" | ||
import { keywords, schema, type Inner, type TypeNode } from "@arktype/schema" | ||
import { | ||
stringify, | ||
printable, | ||
throwParseError, | ||
type Dict, | ||
type ErrorMessage, | ||
type evaluate | ||
type evaluate, | ||
type merge | ||
} from "@arktype/util" | ||
import type { ParseContext } from "../scope.js" | ||
import type { inferDefinition, validateDefinition } from "./definition.js" | ||
import type { astToString } from "./semantic/utils.js" | ||
import type { validateString } from "./semantic/validate.js" | ||
@@ -24,11 +28,57 @@ import { | ||
export const parseObjectLiteral = (def: Dict, ctx: ParseContext) => { | ||
export const parseObjectLiteral = (def: Dict, ctx: ParseContext): TypeNode => { | ||
const required: Inner<"required">[] = [] | ||
const optional: Inner<"optional">[] = [] | ||
for (const entry of stringAndSymbolicEntriesOf(def)) { | ||
// We only allow a spread operator to be used as the first key in an object | ||
// because to match JS behavior any keys before the spread are overwritten | ||
// by the values in the target object, so there'd be no useful purpose in having it | ||
// anywhere except for the beginning. | ||
// Discussion in ArkType Discord: | ||
// https://discord.com/channels/957797212103016458/1103023445035462678/1182814502471860334 | ||
let hasSeenFirstKey = false | ||
const entries = stringAndSymbolicEntriesOf(def) | ||
for (const entry of entries) { | ||
const result = parseEntry(entry) | ||
if (result.kind === "spread") { | ||
if (hasSeenFirstKey) { | ||
return throwParseError( | ||
"Spread operator may only be used as the first key in an object" | ||
) | ||
} | ||
const spreadNode = ctx.scope.parse(result.innerValue, ctx) | ||
if ( | ||
spreadNode.kind !== "intersection" || | ||
!spreadNode.extends(keywords.object) | ||
) { | ||
return throwParseError( | ||
writeInvalidSpreadTypeMessage(printable(result.innerValue)) | ||
) | ||
} | ||
// For each key on spreadNode, add it to our object. | ||
// We filter out keys from the spreadNode that will be defined later on this same object | ||
// because the currently parsed definition will overwrite them. | ||
const requiredEntriesFromSpread = (spreadNode.required ?? []).filter( | ||
(e) => !entries.some(([k]) => k === e.key) | ||
) | ||
const optionalEntriesFromSpread = (spreadNode.optional ?? []).filter( | ||
(e) => !entries.some(([k]) => k === e.key) | ||
) | ||
required.push(...requiredEntriesFromSpread) | ||
optional.push(...optionalEntriesFromSpread) | ||
continue | ||
} | ||
ctx.path.push( | ||
`${ | ||
typeof result.innerKey === "symbol" | ||
? `[${stringify(result.innerKey)}]` | ||
? `[${printable(result.innerKey)}]` | ||
: result.innerKey | ||
@@ -38,2 +88,3 @@ }` | ||
const valueNode = ctx.scope.parse(result.innerValue, ctx) | ||
if (result.kind === "optional") { | ||
@@ -51,4 +102,7 @@ optional.push({ | ||
ctx.path.pop() | ||
hasSeenFirstKey ||= true | ||
} | ||
return node({ | ||
return schema({ | ||
basis: "object", | ||
@@ -60,20 +114,31 @@ required, | ||
/** | ||
* Infers the contents of an object literal, ignoring a spread definition | ||
* You probably want to use {@link inferObjectLiteral} instead. | ||
*/ | ||
type inferObjectLiteralInner<def extends object, $, args> = { | ||
// since def is a const parameter, we remove the readonly modifier here | ||
// support for builtin readonly tracked here: | ||
// https://github.com/arktypeio/arktype/issues/808 | ||
-readonly [k in keyof def as nonOptionalKeyFrom< | ||
k, | ||
def[k], | ||
$, | ||
args | ||
>]: inferDefinition<def[k], $, args> | ||
} & { | ||
-readonly [k in keyof def as optionalKeyFrom<k, def[k]>]?: inferDefinition< | ||
def[k] extends OptionalValue<infer inner> ? inner : def[k], | ||
$, | ||
args | ||
> | ||
} | ||
export type inferObjectLiteral<def extends object, $, args> = evaluate< | ||
{ | ||
// since def is a const parameter, we remove the readonly modifier here | ||
// support for builtin readonly tracked here: | ||
// https://github.com/arktypeio/arktype/issues/808 | ||
-readonly [k in keyof def as nonOptionalKeyFrom< | ||
k, | ||
def[k], | ||
$, | ||
args | ||
>]: inferDefinition<def[k], $, args> | ||
} & { | ||
-readonly [k in keyof def as optionalKeyFrom<k, def[k]>]?: inferDefinition< | ||
def[k] extends OptionalValue<infer inner> ? inner : def[k], | ||
$, | ||
args | ||
> | ||
} | ||
"..." extends keyof def | ||
? merge< | ||
inferDefinition<def["..."], $, args>, | ||
inferObjectLiteralInner<def, $, args> | ||
> | ||
: inferObjectLiteralInner<def, $, args> | ||
> | ||
@@ -87,6 +152,10 @@ | ||
: inferDefinition<indexDef, $, args> extends PropertyKey | ||
? // if the indexDef is syntactically and semantically valid, | ||
// move on to the validating the value definition | ||
validateDefinition<def[k], $, args> | ||
: indexParseError<writeInvalidPropertyKeyMessage<indexDef>> | ||
? // if the indexDef is syntactically and semantically valid, | ||
// move on to the validating the value definition | ||
validateDefinition<def[k], $, args> | ||
: indexParseError<writeInvalidPropertyKeyMessage<indexDef>> | ||
: k extends "..." | ||
? inferDefinition<def[k], $, args> extends object | ||
? validateDefinition<def[k], $, args> | ||
: indexParseError<writeInvalidSpreadTypeMessage<astToString<def[k]>>> | ||
: validateObjectValue<def[k], $, args> | ||
@@ -102,4 +171,4 @@ } | ||
: result["kind"] extends "indexed" | ||
? inferDefinition<result["innerKey"], $, args> & PropertyKey | ||
: never | ||
? inferDefinition<result["innerKey"], $, args> & PropertyKey | ||
: never | ||
: never | ||
@@ -127,1 +196,9 @@ | ||
`Indexed key definition '${indexDef}' must be a string, number or symbol` | ||
export const writeInvalidSpreadTypeMessage = <def extends string>( | ||
def: def | ||
): writeInvalidSpreadTypeMessage<def> => | ||
`Spread operand must resolve to an object literal type (was ${def})` | ||
type writeInvalidSpreadTypeMessage<def extends string> = | ||
`Spread operand must resolve to an object literal type (was ${def})` |
@@ -1,15 +0,14 @@ | ||
import type { | ||
NumericallyBoundable, | ||
writeUnboundableMessage | ||
} from "@arktype/schema" | ||
import type { NumericallyBoundable } from "@arktype/schema" | ||
import type { ErrorMessage } from "@arktype/util" | ||
import type { DateLiteral } from "../string/shift/operand/date.js" | ||
import type { | ||
BoundKind, | ||
Comparator, | ||
InvertedComparators, | ||
LimitLiteral, | ||
writeInvalidLimitMessage | ||
LimitLiteral | ||
} from "../string/reduce/shared.js" | ||
import type { | ||
BoundExpressionKind, | ||
writeInvalidLimitMessage, | ||
writeUnboundableMessage | ||
} from "../string/shift/operator/bounds.js" | ||
import type { inferAst } from "./semantic.js" | ||
import type { inferAstBase } from "./semantic.js" | ||
import type { astToString } from "./utils.js" | ||
@@ -27,4 +26,4 @@ import type { validateAst } from "./validate.js" | ||
: l extends [infer leftAst, Comparator, unknown] | ||
? ErrorMessage<writeDoubleRightBoundMessage<astToString<leftAst>>> | ||
: validateBound<l, comparator, r & LimitLiteral, "right", $, args> | ||
? ErrorMessage<writeDoubleRightBoundMessage<astToString<leftAst>>> | ||
: validateBound<l, comparator, r & LimitLiteral, "right", $, args> | ||
@@ -35,6 +34,6 @@ export type validateBound< | ||
limit extends LimitLiteral, | ||
boundKind extends BoundKind, | ||
boundKind extends BoundExpressionKind, | ||
$, | ||
args | ||
> = inferAst<boundedAst, $, args> extends infer bounded | ||
> = inferAstBase<boundedAst, $, args> extends infer bounded | ||
? [bounded] extends [NumericallyBoundable] | ||
@@ -45,14 +44,13 @@ ? limit extends number | ||
: bounded extends Date | ||
? limit extends DateLiteral | ||
? validateAst<boundedAst, $, args> | ||
: ErrorMessage<writeInvalidLimitMessage<comparator, limit, boundKind>> | ||
: ErrorMessage< | ||
writeUnboundableMessage< | ||
astToString< | ||
boundKind extends "left" | ||
? boundedAst[0 & keyof boundedAst] | ||
: boundedAst | ||
> | ||
? // allow numeric or date literal as a Date limit | ||
validateAst<boundedAst, $, args> | ||
: ErrorMessage< | ||
writeUnboundableMessage< | ||
astToString< | ||
boundKind extends "left" | ||
? boundedAst[0 & keyof boundedAst] | ||
: boundedAst | ||
> | ||
> | ||
> | ||
> | ||
: never | ||
@@ -59,0 +57,0 @@ |
import type { writeIndivisibleMessage } from "@arktype/schema" | ||
import type { ErrorMessage } from "@arktype/util" | ||
import type { inferAst } from "./semantic.js" | ||
import type { inferAstBase } from "./semantic.js" | ||
import type { astToString } from "./utils.js" | ||
@@ -8,3 +8,3 @@ import type { validateAst } from "./validate.js" | ||
export type validateDivisor<l, $, args> = isDivisible< | ||
inferAst<l, $, args> | ||
inferAstBase<l, $, args> | ||
> extends true | ||
@@ -11,0 +11,0 @@ ? validateAst<l, $, args> |
@@ -1,2 +0,2 @@ | ||
import type { Out } from "@arktype/schema" | ||
import type { MorphAst, Out } from "@arktype/schema" | ||
import { | ||
@@ -10,3 +10,2 @@ Hkt, | ||
} from "@arktype/util" | ||
import type { MorphAst } from "../tuple.js" | ||
@@ -16,18 +15,18 @@ export type inferIntersection<l, r> = [l] extends [never] | ||
: [r] extends [never] | ||
? never | ||
: [l & r] extends [never] | ||
? never | ||
: isAny<l | r> extends true | ||
? any | ||
: l extends MorphAst<infer lIn, infer lOut> | ||
? r extends MorphAst | ||
? never | ||
: (In: evaluate<lIn & r>) => Out<lOut> | ||
: r extends MorphAst<infer rIn, infer rOut> | ||
? (In: evaluate<rIn & l>) => Out<rOut> | ||
: [l, r] extends [object, object] | ||
? intersectObjects<l, r> extends infer result | ||
? result | ||
: never | ||
: l & r | ||
? never | ||
: [l & r] extends [never] | ||
? never | ||
: isAny<l | r> extends true | ||
? any | ||
: l extends MorphAst<infer lIn, infer lOut> | ||
? r extends MorphAst | ||
? never | ||
: (In: evaluate<lIn & r>) => Out<lOut> | ||
: r extends MorphAst<infer rIn, infer rOut> | ||
? (In: evaluate<rIn & l>) => Out<rOut> | ||
: [l, r] extends [object, object] | ||
? intersectObjects<l, r> extends infer result | ||
? result | ||
: never | ||
: l & r | ||
@@ -34,0 +33,0 @@ declare class MorphableIntersection extends Hkt.Kind { |
@@ -1,3 +0,16 @@ | ||
import type { BigintLiteral, List, NumberLiteral } from "@arktype/util" | ||
import type { | ||
DateLiteral, | ||
Refinements, | ||
RegexLiteral, | ||
distill, | ||
is | ||
} from "@arktype/schema" | ||
import type { | ||
BigintLiteral, | ||
List, | ||
NumberLiteral, | ||
evaluate, | ||
extend | ||
} from "@arktype/util" | ||
import type { | ||
UnparsedScope, | ||
@@ -9,14 +22,23 @@ resolve, | ||
import type { inferDefinition } from "../definition.js" | ||
import type { DateLiteral } from "../string/shift/operand/date.js" | ||
import type { StringLiteral } from "../string/shift/operand/enclosed.js" | ||
import type { | ||
Comparator, | ||
InvertedComparators, | ||
LimitLiteral | ||
} from "../string/shift/operator/bounds.js" | ||
} from "../string/reduce/shared.js" | ||
import type { StringLiteral } from "../string/shift/operand/enclosed.js" | ||
import type { inferIntersection } from "./intersections.js" | ||
export type inferAst<ast, $, args> = ast extends List | ||
? inferExpression<ast, $, args> | ||
: inferTerminal<ast, $, args> | ||
export type inferAstRoot<ast, $, args> = inferAst<ast, $, args, {}> | ||
export type inferAstBase<ast, $, args> = distill<inferAstRoot<ast, $, args>> | ||
export type inferAst< | ||
ast, | ||
$, | ||
args, | ||
refinements extends Refinements | ||
> = ast extends List | ||
? inferExpression<ast, $, args, refinements> | ||
: inferTerminal<ast, $, args, refinements> | ||
export type GenericInstantiationAst< | ||
@@ -30,3 +52,4 @@ g extends GenericProps = GenericProps, | ||
$, | ||
args | ||
args, | ||
refinements extends Refinements | ||
> = ast extends GenericInstantiationAst | ||
@@ -48,3 +71,4 @@ ? inferDefinition< | ||
$, | ||
args | ||
args, | ||
refinements | ||
> | ||
@@ -54,19 +78,26 @@ } | ||
: ast[1] extends "[]" | ||
? inferAst<ast[0], $, args>[] | ||
: ast[1] extends "|" | ||
? inferAst<ast[0], $, args> | inferAst<ast[2], $, args> | ||
: ast[1] extends "&" | ||
? inferIntersection< | ||
inferAst<ast[0], $, args>, | ||
inferAst<ast[2], $, args> | ||
> | ||
: ast[1] extends Comparator | ||
? ast[0] extends LimitLiteral | ||
? inferAst<ast[2], $, args> | ||
: inferAst<ast[0], $, args> | ||
: ast[1] extends "%" | ||
? inferAst<ast[0], $, args> | ||
: ast[0] extends "keyof" | ||
? keyof inferAst<ast[1], $, args> | ||
: never | ||
? inferAst<ast[0], $, args, refinements>[] | ||
: ast[1] extends "|" | ||
? | ||
| inferAst<ast[0], $, args, refinements> | ||
| inferAst<ast[2], $, args, refinements> | ||
: ast[1] extends "&" | ||
? inferIntersection< | ||
inferAst<ast[0], $, args, refinements>, | ||
inferAst<ast[2], $, args, refinements> | ||
> | ||
: ast[1] extends Comparator | ||
? ast[0] extends LimitLiteral | ||
? inferAst< | ||
ast[2], | ||
$, | ||
args, | ||
refinements & { [_ in InvertedComparators[ast[1]]]: ast[0] } | ||
> | ||
: inferAst<ast[0], $, args, refinements & { [_ in ast[1]]: ast[2] }> | ||
: ast[1] extends "%" | ||
? inferAst<ast[0], $, args, refinements & { [k in `%${ast[2] & string}`]: 0 }> | ||
: ast[0] extends "keyof" | ||
? keyof inferAst<ast[1], $, args, refinements> | ||
: never | ||
@@ -95,18 +126,24 @@ export type PrefixOperator = "keyof" | "instanceof" | "===" | "node" | ||
export type RegexLiteral<source extends string = string> = `/${source}/` | ||
export type inferTerminal<token, $, args> = token extends keyof args | keyof $ | ||
? resolve<token, $, args> | ||
: token extends StringLiteral<infer Text> | ||
? Text | ||
: token extends RegexLiteral | ||
? string | ||
: token extends DateLiteral | ||
? Date | ||
: token extends NumberLiteral<infer value> | ||
? value | ||
: token extends BigintLiteral<infer value> | ||
? value | ||
: // doing this last allows us to infer never if it isn't valid rather than check | ||
// if it's a valid submodule reference ahead of time | ||
tryInferSubmoduleReference<$, token> | ||
export type inferTerminal< | ||
token, | ||
$, | ||
args, | ||
refinements extends Refinements | ||
> = token extends keyof args | keyof $ | ||
? {} extends refinements | ||
? resolve<token, $, args> | ||
: is<resolve<token, $, args>, evaluate<refinements>> | ||
: token extends StringLiteral<infer text> | ||
? text | ||
: token extends RegexLiteral | ||
? is<string, extend<refinements, { [_ in token]: true }>> | ||
: token extends DateLiteral | ||
? is<Date, extend<refinements, { [_ in token]: true }>> | ||
: token extends NumberLiteral<infer value> | ||
? value | ||
: token extends BigintLiteral<infer value> | ||
? value | ||
: // TODO: refinements | ||
// doing this last allows us to infer never if it isn't valid rather than check | ||
// if it's a valid submodule reference ahead of time | ||
tryInferSubmoduleReference<$, token> |
import type { List, Stringifiable } from "@arktype/util" | ||
import type { Comparator } from "../string/shift/operator/bounds.js" | ||
import type { Comparator } from "../string/reduce/shared.js" | ||
import type { InfixExpression, PostfixExpression } from "./semantic.js" | ||
@@ -15,8 +15,8 @@ | ||
: ast extends InfixExpression<infer operator, infer l, infer r> | ||
? operator extends "&" | "|" | "%" | Comparator | ||
? `${groupAst<l>}${operator}${groupAst<r>}` | ||
: never | ||
: ast extends Stringifiable | ||
? `${ast extends bigint ? `${ast}n` : ast}` | ||
: "..." | ||
? operator extends "&" | "|" | "%" | Comparator | ||
? `${groupAst<l>}${operator}${groupAst<r>}` | ||
: never | ||
: ast extends Stringifiable | ||
? `${ast extends bigint ? `${ast}n` : ast}` | ||
: "..." | ||
@@ -23,0 +23,0 @@ type groupAst<ast> = ast extends List |
@@ -0,1 +1,2 @@ | ||
import type { Refinements } from "@arktype/schema" | ||
import type { | ||
@@ -10,5 +11,5 @@ BigintLiteral, | ||
import type { GenericProps } from "../../type.js" | ||
import type { writeInvalidGenericArgsMessage } from "../generic.js" | ||
import type { Comparator } from "../string/reduce/shared.js" | ||
import type { writeInvalidGenericArgsMessage } from "../string/shift/operand/genericArgs.js" | ||
import type { writeMissingSubmoduleAccessMessage } from "../string/shift/operand/unenclosed.js" | ||
import type { Comparator } from "../string/shift/operator/bounds.js" | ||
import type { parseString } from "../string/string.js" | ||
@@ -27,18 +28,18 @@ import type { validateRange } from "./bounds.js" | ||
: ast extends PostfixExpression<infer operator, infer operand> | ||
? operator extends "[]" | ||
? validateAst<operand, $, args> | ||
: never | ||
: ast extends InfixExpression<infer operator, infer l, infer r> | ||
? operator extends "&" | "|" | ||
? validateInfix<ast, $, args> | ||
: operator extends Comparator | ||
? validateRange<l, operator, r, $, args> | ||
: operator extends "%" | ||
? validateDivisor<l, $, args> | ||
: undefined | ||
: ast extends readonly ["keyof", infer operand] | ||
? validateAst<operand, $, args> | ||
: ast extends GenericInstantiationAst | ||
? validateGenericArgs<ast["2"], $, args> | ||
: ErrorMessage<writeUnexpectedExpressionMessage<astToString<ast>>> | ||
? operator extends "[]" | ||
? validateAst<operand, $, args> | ||
: never | ||
: ast extends InfixExpression<infer operator, infer l, infer r> | ||
? operator extends "&" | "|" | ||
? validateInfix<ast, $, args> | ||
: operator extends Comparator | ||
? validateRange<l, operator, r, $, args> | ||
: operator extends "%" | ||
? validateDivisor<l, $, args> | ||
: undefined | ||
: ast extends readonly ["keyof", infer operand] | ||
? validateAst<operand, $, args> | ||
: ast extends GenericInstantiationAst | ||
? validateGenericArgs<ast["2"], $, args> | ||
: ErrorMessage<writeUnexpectedExpressionMessage<astToString<ast>>> | ||
@@ -72,18 +73,21 @@ type writeUnexpectedExpressionMessage<expression extends string> = | ||
: def extends BigintLiteral<infer value> | ||
? bigint extends value | ||
? ErrorMessage<writeMalformedNumericLiteralMessage<def, "bigint">> | ||
: undefined | ||
: def extends keyof $ | ||
? // these problems would've been caught during a fullStringParse, but it's most | ||
// efficient to check for them here in case the string was naively parsed | ||
$[def] extends GenericProps | ||
? ErrorMessage< | ||
writeInvalidGenericArgsMessage<def, $[def]["parameters"], []> | ||
> | ||
: $[def] extends Module | ||
? ErrorMessage<writeMissingSubmoduleAccessMessage<def>> | ||
: undefined | ||
: def extends ErrorMessage | ||
? def | ||
: undefined | ||
? bigint extends value | ||
? ErrorMessage<writeMalformedNumericLiteralMessage<def, "bigint">> | ||
: undefined | ||
: def extends keyof $ | ||
? $[def] extends null | ||
? // handle any/never | ||
def | ||
: // these problems would've been caught during a fullStringParse, but it's most | ||
// efficient to check for them here in case the string was naively parsed | ||
$[def] extends GenericProps | ||
? ErrorMessage< | ||
writeInvalidGenericArgsMessage<def, $[def]["parameters"], []> | ||
> | ||
: $[def] extends Module | ||
? ErrorMessage<writeMissingSubmoduleAccessMessage<def>> | ||
: undefined | ||
: def extends ErrorMessage | ||
? def | ||
: undefined | ||
@@ -107,3 +111,3 @@ export type validateString<def extends string, $, args> = validateAst< | ||
: validateAst<ast[2], $, args> extends ErrorMessage<infer message> | ||
? ErrorMessage<message> | ||
: undefined | ||
? ErrorMessage<message> | ||
: undefined |
@@ -29,3 +29,3 @@ import type { extend } from "@arktype/util" | ||
type ParsedKeyKind = "required" | "optional" | "indexed" | ||
type ParsedKeyKind = "required" | "optional" | "indexed" | "spread" | ||
@@ -77,2 +77,5 @@ type parsedEntry<result extends EntryParseResult> = result | ||
// these methods are also used in tuple parsing, however the keys of a tuple will always be 0, 1, | ||
// 2, etc and never be `...`, meaning `"spread"` as a kind will never occur in tuples | ||
export const parseEntry = ([key, value]: DefinitionEntry): EntryParseResult => { | ||
@@ -84,2 +87,6 @@ const keyParseResult: KeyParseResult = | ||
: { innerKey: key.slice(0, -1), kind: "optional" } | ||
: key === "..." | ||
? { innerKey: "...", kind: "spread" } | ||
: key === "\\..." | ||
? { innerKey: "...", kind: "required" } | ||
: { innerKey: key, kind: "required" } | ||
@@ -94,4 +101,4 @@ const valueParseResult = getInnerValue(value) | ||
: valueParseResult.kind === "optional" | ||
? "optional" | ||
: keyParseResult.kind | ||
? "optional" | ||
: keyParseResult.kind | ||
} | ||
@@ -131,16 +138,20 @@ } | ||
}> | ||
: k extends "..." | ||
? parsedKey<{ kind: "spread"; innerKey: "..." }> | ||
: k extends "\\..." | ||
? parsedKey<{ kind: "required"; innerKey: "..." }> | ||
: k extends IndexedKey<infer def> | ||
? parsedKey<{ | ||
kind: "indexed" | ||
innerKey: def | ||
}> | ||
: k extends `${Scanner.EscapeToken}${infer escapedIndexKey extends | ||
IndexedKey}` | ||
? parsedKey<{ | ||
kind: "required" | ||
innerKey: escapedIndexKey | ||
}> | ||
: parsedKey<{ | ||
kind: "required" | ||
innerKey: k & (string | symbol) | ||
}> | ||
? parsedKey<{ | ||
kind: "indexed" | ||
innerKey: def | ||
}> | ||
: k extends `${Scanner.EscapeToken}${infer escapedIndexKey extends | ||
IndexedKey}` | ||
? parsedKey<{ | ||
kind: "required" | ||
innerKey: escapedIndexKey | ||
}> | ||
: parsedKey<{ | ||
kind: "required" | ||
innerKey: k & (string | symbol) | ||
}> |
@@ -1,2 +0,2 @@ | ||
import type { Root } from "@arktype/schema" | ||
import type { TypeNode } from "@arktype/schema" | ||
import { | ||
@@ -9,10 +9,9 @@ isKeyOf, | ||
import type { ParseContext } from "../../../scope.js" | ||
import { parseOperand } from "../shift/operand/operand.js" | ||
import { parseOperator } from "../shift/operator/operator.js" | ||
import { Scanner } from "../shift/scanner.js" | ||
import { parseUntilFinalizer } from "../string.js" | ||
import { | ||
invertedComparators, | ||
minComparators, | ||
type Comparator, | ||
type LimitLiteral | ||
} from "../shift/operator/bounds.js" | ||
import { Scanner } from "../shift/scanner.js" | ||
import { | ||
writeMultipleLeftBoundsMessage, | ||
@@ -23,2 +22,4 @@ writeOpenRangeMessage, | ||
writeUnpairableComparatorMessage, | ||
type Comparator, | ||
type LimitLiteral, | ||
type OpenLeftBound, | ||
@@ -31,4 +32,4 @@ type StringifiablePrefixOperator | ||
leftBound?: OpenLeftBound | ||
"&"?: Root | ||
"|"?: Root | ||
"&"?: TypeNode | ||
"|"?: TypeNode | ||
} | ||
@@ -40,3 +41,3 @@ | ||
readonly scanner: Scanner | ||
root: Root | undefined | ||
root: TypeNode | undefined | ||
branches: BranchState = { | ||
@@ -69,7 +70,7 @@ prefixes: [] | ||
constrainRoot(...args: Parameters<Root["constrain"]>) { | ||
constrainRoot(...args: Parameters<TypeNode["constrain"]>) { | ||
this.root = this.root!.constrain(...args) | ||
} | ||
setRoot(root: Root) { | ||
setRoot(root: TypeNode) { | ||
this.root = root | ||
@@ -156,2 +157,16 @@ } | ||
parseUntilFinalizer() { | ||
return parseUntilFinalizer( | ||
new DynamicState(this.scanner.unscanned, this.ctx) | ||
) | ||
} | ||
parseOperator(this: DynamicStateWithRoot) { | ||
return parseOperator(this) | ||
} | ||
parseOperand() { | ||
return parseOperand(this) | ||
} | ||
private assertRangeUnset() { | ||
@@ -158,0 +173,0 @@ if (this.branches.leftBound) { |
@@ -1,11 +0,39 @@ | ||
import { | ||
invertedComparators, | ||
type Comparator, | ||
type InvertedComparators, | ||
type LimitLiteral, | ||
type MinComparator | ||
} from "../shift/operator/bounds.js" | ||
import type { DateLiteral } from "@arktype/schema" | ||
export type StringifiablePrefixOperator = "keyof" | ||
export const minComparators = { | ||
">": true, | ||
">=": true | ||
} as const | ||
export type MinComparator = keyof typeof minComparators | ||
export const maxComparators = { | ||
"<": true, | ||
"<=": true | ||
} as const | ||
export type MaxComparator = keyof typeof maxComparators | ||
export const comparators = { | ||
...minComparators, | ||
...maxComparators, | ||
"==": true | ||
} | ||
export type Comparator = keyof typeof comparators | ||
export const invertedComparators = { | ||
"<": ">", | ||
">": "<", | ||
"<=": ">=", | ||
">=": "<=", | ||
"==": "==" | ||
} as const satisfies Record<Comparator, Comparator> | ||
export type InvertedComparators = typeof invertedComparators | ||
export type LimitLiteral = number | DateLiteral | ||
export type OpenLeftBound = { limit: LimitLiteral; comparator: MinComparator } | ||
@@ -12,0 +40,0 @@ |
import type { Completion, defined, ErrorMessage } from "@arktype/util" | ||
import type { Scanner } from "../shift/scanner.js" | ||
import type { | ||
@@ -7,6 +8,3 @@ Comparator, | ||
MaxComparator, | ||
MinComparator | ||
} from "../shift/operator/bounds.js" | ||
import type { Scanner } from "../shift/scanner.js" | ||
import type { | ||
MinComparator, | ||
OpenLeftBound, | ||
@@ -234,11 +232,11 @@ StringifiablePrefixOperator, | ||
: s["groups"] extends popGroup<infer stack, infer top> | ||
? from<{ | ||
groups: stack | ||
branches: top | ||
root: mergeToUnion<s> | ||
finalizer: s["finalizer"] | ||
scanned: updateScanned<s["scanned"], s["unscanned"], unscanned> | ||
unscanned: unscanned | ||
}> | ||
: state.error<writeUnmatchedGroupCloseMessage<unscanned>> | ||
? from<{ | ||
groups: stack | ||
branches: top | ||
root: mergeToUnion<s> | ||
finalizer: s["finalizer"] | ||
scanned: updateScanned<s["scanned"], s["unscanned"], unscanned> | ||
unscanned: unscanned | ||
}> | ||
: state.error<writeUnmatchedGroupCloseMessage<unscanned>> | ||
@@ -280,11 +278,11 @@ export type reduceGroupOpen< | ||
: s["branches"]["prefixes"] extends [ | ||
...unknown[], | ||
infer tail extends string | ||
] | ||
? tail | ||
: s["branches"]["&"] extends {} | ||
? "&" | ||
: s["branches"]["|"] extends {} | ||
? "|" | ||
: undefined | ||
...unknown[], | ||
infer tail extends string | ||
] | ||
? tail | ||
: s["branches"]["&"] extends {} | ||
? "&" | ||
: s["branches"]["|"] extends {} | ||
? "|" | ||
: undefined | ||
@@ -291,0 +289,0 @@ export type scanTo<s extends StaticState, unscanned extends string> = from<{ |
@@ -0,7 +1,4 @@ | ||
import type { DateLiteral } from "@arktype/schema" | ||
import { throwParseError, tryParseNumber } from "@arktype/util" | ||
export type DateLiteral<source extends string = string> = | ||
| `d"${source}"` | ||
| `d'${source}'` | ||
export const isDateLiteral = (value: unknown): value is DateLiteral => | ||
@@ -8,0 +5,0 @@ typeof value === "string" && |
@@ -1,6 +0,5 @@ | ||
import { node } from "@arktype/schema" | ||
import { schema, type RegexLiteral } from "@arktype/schema" | ||
import { isKeyOf } from "@arktype/util" | ||
import type { RegexLiteral } from "../../../semantic/semantic.js" | ||
import type { DynamicState } from "../../reduce/dynamic.js" | ||
import type { state, StaticState } from "../../reduce/static.js" | ||
import type { StaticState, state } from "../../reduce/static.js" | ||
import type { Scanner } from "../scanner.js" | ||
@@ -34,8 +33,8 @@ import { tryParseDate, writeInvalidDateMessage } from "./date.js" | ||
new RegExp(enclosed) | ||
s.root = node({ basis: "string", pattern: token as RegexLiteral }) | ||
s.root = schema({ basis: "string", pattern: token as RegexLiteral }) | ||
} else if (isKeyOf(enclosing, enclosingQuote)) { | ||
s.root = node({ is: enclosed }) | ||
s.root = schema({ unit: enclosed }) | ||
} else { | ||
const date = tryParseDate(enclosed, writeInvalidDateMessage(enclosed)) | ||
s.root = node({ is: date, description: token }) | ||
s.root = schema({ unit: date, description: token }) | ||
} | ||
@@ -42,0 +41,0 @@ } |
@@ -18,12 +18,12 @@ import type { DynamicState } from "../../reduce/dynamic.js" | ||
: s.scanner.lookahead === "(" | ||
? s.shiftedByOne().reduceGroupOpen() | ||
: s.scanner.lookaheadIsIn(enclosingChar) | ||
? parseEnclosed(s, s.scanner.shift()) | ||
: s.scanner.lookaheadIsIn(Scanner.whiteSpaceTokens) | ||
? parseOperand(s.shiftedByOne()) | ||
: s.scanner.lookahead === "d" | ||
? s.shiftedByOne().scanner.lookaheadIsIn(enclosingChar) | ||
? parseEnclosed(s, `d${s.scanner.shift()}` as EnclosingStartToken) | ||
: parseUnenclosed(s) | ||
: parseUnenclosed(s) | ||
? s.shiftedByOne().reduceGroupOpen() | ||
: s.scanner.lookaheadIsIn(enclosingChar) | ||
? parseEnclosed(s, s.scanner.shift()) | ||
: s.scanner.lookaheadIsIn(Scanner.whiteSpaceTokens) | ||
? parseOperand(s.shiftedByOne()) | ||
: s.scanner.lookahead === "d" | ||
? s.shiftedByOne().scanner.lookaheadIsIn(enclosingChar) | ||
? parseEnclosed(s, `d${s.scanner.shift()}` as EnclosingStartToken) | ||
: parseUnenclosed(s) | ||
: parseUnenclosed(s) | ||
@@ -38,13 +38,13 @@ export type parseOperand< | ||
: lookahead extends EnclosingEndToken | ||
? parseEnclosed<s, lookahead, unscanned> | ||
: lookahead extends Scanner.WhiteSpaceToken | ||
? parseOperand<state.scanTo<s, unscanned>, $, args> | ||
: lookahead extends "d" | ||
? unscanned extends Scanner.shift< | ||
infer enclosing extends EnclosingQuote, | ||
infer nextUnscanned | ||
> | ||
? parseEnclosed<s, `d${enclosing}`, nextUnscanned> | ||
: parseUnenclosed<s, $, args> | ||
: parseUnenclosed<s, $, args> | ||
? parseEnclosed<s, lookahead, unscanned> | ||
: lookahead extends Scanner.WhiteSpaceToken | ||
? parseOperand<state.scanTo<s, unscanned>, $, args> | ||
: lookahead extends "d" | ||
? unscanned extends Scanner.shift< | ||
infer enclosing extends EnclosingQuote, | ||
infer nextUnscanned | ||
> | ||
? parseEnclosed<s, `d${enclosing}`, nextUnscanned> | ||
: parseUnenclosed<s, $, args> | ||
: parseUnenclosed<s, $, args> | ||
: state.completion<`${s["scanned"]}${BaseCompletions<$, args>}`> |
@@ -1,4 +0,4 @@ | ||
import { isNode, node, type Root } from "@arktype/schema" | ||
import { BaseType, schema, type TypeNode } from "@arktype/schema" | ||
import { | ||
stringify, | ||
printable, | ||
throwParseError, | ||
@@ -14,12 +14,4 @@ tryParseNumber, | ||
import type { Module } from "../../../../scope.js" | ||
import { | ||
hasArkKind, | ||
type Generic, | ||
type GenericProps | ||
} from "../../../../type.js" | ||
import { | ||
parseGenericArgs, | ||
writeInvalidGenericArgsMessage, | ||
type ParsedArgs | ||
} from "../../../generic.js" | ||
import type { Generic, GenericProps } from "../../../../type.js" | ||
import { hasArkKind } from "../../../../util.js" | ||
import type { GenericInstantiationAst } from "../../../semantic/semantic.js" | ||
@@ -30,2 +22,7 @@ import type { DynamicState } from "../../reduce/dynamic.js" | ||
import type { Scanner } from "../scanner.js" | ||
import { | ||
parseGenericArgs, | ||
writeInvalidGenericArgsMessage, | ||
type ParsedArgs | ||
} from "./genericArgs.js" | ||
@@ -51,16 +48,16 @@ export const parseUnenclosed = (s: DynamicState) => { | ||
: tryResolve<s, token, $, args> extends infer result | ||
? result extends ErrorMessage<infer message> | ||
? state.error<message> | ||
: result extends keyof $ | ||
? $[result] extends GenericProps | ||
? parseGenericInstantiation< | ||
token, | ||
$[result], | ||
state.scanTo<s, unscanned>, | ||
$, | ||
args | ||
> | ||
: state.setRoot<s, result, unscanned> | ||
: state.setRoot<s, result, unscanned> | ||
: never | ||
? result extends ErrorMessage<infer message> | ||
? state.error<message> | ||
: result extends keyof $ | ||
? $[result] extends GenericProps | ||
? parseGenericInstantiation< | ||
token, | ||
$[result], | ||
state.scanTo<s, unscanned>, | ||
$, | ||
args | ||
> | ||
: state.setRoot<s, result, unscanned> | ||
: state.setRoot<s, result, unscanned> | ||
: never | ||
: never | ||
@@ -72,3 +69,3 @@ | ||
s: DynamicState | ||
) => { | ||
): TypeNode => { | ||
s.scanner.shiftUntilNonWhitespace() | ||
@@ -79,8 +76,3 @@ const lookahead = s.scanner.shift() | ||
} | ||
const parsedArgs = parseGenericArgs( | ||
name, | ||
g.parameters, | ||
s.scanner.unscanned, | ||
s.ctx | ||
) | ||
const parsedArgs = parseGenericArgs(name, g.parameters, s) | ||
const remainingChars = parsedArgs.unscanned.length | ||
@@ -91,3 +83,3 @@ // set the scanner position to where the args scanner left off | ||
) | ||
return g(...parsedArgs.result).root | ||
return g(...parsedArgs.result).root as never | ||
} | ||
@@ -117,3 +109,3 @@ | ||
const unenclosedToNode = (s: DynamicState, token: string): Root => | ||
const unenclosedToNode = (s: DynamicState, token: string): TypeNode => | ||
maybeParseReference(s, token) ?? | ||
@@ -130,3 +122,3 @@ maybeParseUnenclosedLiteral(s, token) ?? | ||
token: string | ||
): Root | undefined => { | ||
): TypeNode | undefined => { | ||
if (s.ctx.args?.[token]) { | ||
@@ -136,3 +128,3 @@ return s.ctx.args[token] | ||
const resolution = s.ctx.scope.maybeResolve(token) | ||
if (isNode(resolution)) { | ||
if (resolution instanceof BaseType) { | ||
return resolution | ||
@@ -146,3 +138,3 @@ } | ||
} | ||
return throwParseError(`Unexpected resolution ${stringify(resolution)}`) | ||
return throwParseError(`Unexpected resolution ${printable(resolution)}`) | ||
} | ||
@@ -153,10 +145,10 @@ | ||
token: string | ||
): Root | undefined => { | ||
): TypeNode | undefined => { | ||
const maybeNumber = tryParseNumber(token, { strict: true }) | ||
if (maybeNumber !== undefined) { | ||
return node({ is: maybeNumber }) | ||
return schema({ unit: maybeNumber }) | ||
} | ||
const maybeBigint = tryParseWellFormedBigint(token) | ||
if (maybeBigint !== undefined) { | ||
return node({ is: maybeBigint }) | ||
return schema({ unit: maybeBigint }) | ||
} | ||
@@ -173,27 +165,21 @@ } | ||
: token extends keyof args | ||
? token | ||
: token extends NumberLiteral | ||
? token | ||
: token extends BigintLiteral | ||
? token | ||
: token extends `${infer submodule extends keyof $ & | ||
string}.${infer reference}` | ||
? $[submodule] extends Module<infer r> | ||
? reference extends keyof r["exports"] | ||
? token | ||
: unknown extends r["exports"] | ||
? // not sure why I need the additional check here, but for now TS seems to | ||
// hit this branch for a non-scope dot access rather than failing | ||
// initially when we try to infer r. if this can be removed without breaking | ||
// any submodule test cases, do it! | ||
ErrorMessage<writeNonSubmoduleDotMessage<submodule>> | ||
: unresolvableError< | ||
s, | ||
reference, | ||
$[submodule], | ||
args, | ||
[submodule] | ||
> | ||
: ErrorMessage<writeNonSubmoduleDotMessage<submodule>> | ||
: unresolvableError<s, token, $, args, []> | ||
? token | ||
: token extends NumberLiteral | ||
? token | ||
: token extends BigintLiteral | ||
? token | ||
: token extends `${infer submodule extends keyof $ & | ||
string}.${infer reference}` | ||
? $[submodule] extends Module<infer r> | ||
? reference extends keyof r["exports"] | ||
? token | ||
: unknown extends r["exports"] | ||
? // not sure why I need the additional check here, but for now TS seems to | ||
// hit this branch for a non-scope dot access rather than failing | ||
// initially when we try to infer r. if this can be removed without breaking | ||
// any submodule test cases, do it! | ||
ErrorMessage<writeNonSubmoduleDotMessage<submodule>> | ||
: unresolvableError<s, reference, $[submodule], args, [submodule]> | ||
: ErrorMessage<writeNonSubmoduleDotMessage<submodule>> | ||
: unresolvableError<s, token, $, args, []> | ||
@@ -203,6 +189,6 @@ export const writeNonSubmoduleDotMessage = <name extends string>( | ||
): writeNonSubmoduleDotMessage<name> => | ||
`'${name}' must reference a scope to be accessed using dot syntax` | ||
`'${name}' must reference a module to be accessed using dot syntax` | ||
type writeNonSubmoduleDotMessage<name extends string> = | ||
`'${name}' must reference a scope to be accessed using dot syntax` | ||
`'${name}' must reference a module to be accessed using dot syntax` | ||
@@ -209,0 +195,0 @@ export const writeMissingSubmoduleAccessMessage = <name extends string>( |
@@ -1,3 +0,14 @@ | ||
import type { MinSchema } from "@arktype/schema" | ||
import { isKeyOf, tryParseNumber, type keySet } from "@arktype/util" | ||
import { | ||
keywords, | ||
type BoundKind, | ||
type LimitSchemaValue, | ||
type Schema, | ||
type TypeNode | ||
} from "@arktype/schema" | ||
import { | ||
isKeyOf, | ||
throwParseError, | ||
tryParseNumber, | ||
type keySet | ||
} from "@arktype/util" | ||
import type { astToString } from "../../../semantic/utils.js" | ||
@@ -9,12 +20,12 @@ import type { | ||
import { | ||
maxComparators, | ||
writeUnpairableComparatorMessage, | ||
type Comparator, | ||
type LimitLiteral, | ||
type MaxComparator, | ||
type OpenLeftBound | ||
} from "../../reduce/shared.js" | ||
import type { StaticState, state } from "../../reduce/static.js" | ||
import { | ||
extractDateLiteralSource, | ||
isDateLiteral, | ||
type DateLiteral | ||
} from "../operand/date.js" | ||
import { parseOperand } from "../operand/operand.js" | ||
import { extractDateLiteralSource, isDateLiteral } from "../operand/date.js" | ||
import type { parseOperand } from "../operand/operand.js" | ||
import type { Scanner } from "../scanner.js" | ||
@@ -28,10 +39,10 @@ | ||
if (s.root.kind === "unit") { | ||
if (typeof s.root.is === "number") { | ||
if (typeof s.root.unit === "number") { | ||
s.unsetRoot() | ||
return s.reduceLeftBound(s.root.is, comparator) | ||
return s.reduceLeftBound(s.root.unit, comparator) | ||
} | ||
if (s.root.is instanceof Date) { | ||
if (s.root.unit instanceof Date) { | ||
s.unsetRoot() | ||
const literal = `d'${ | ||
s.root.description ?? s.root.is.toISOString() | ||
s.root.description ?? s.root.unit.toISOString() | ||
}'` as const | ||
@@ -61,26 +72,2 @@ return s.reduceLeftBound(literal, comparator) | ||
export const minComparators = { | ||
">": true, | ||
">=": true | ||
} as const | ||
export type MinComparator = keyof typeof minComparators | ||
export const maxComparators = { | ||
"<": true, | ||
"<=": true | ||
} as const | ||
export type MaxComparator = keyof typeof maxComparators | ||
export const comparators = { | ||
...minComparators, | ||
...maxComparators, | ||
"==": true | ||
} | ||
export type Comparator = keyof typeof comparators | ||
export type LimitLiteral = number | DateLiteral | ||
const oneCharComparators = { | ||
@@ -110,4 +97,4 @@ "<": true, | ||
: isKeyOf(start, oneCharComparators) | ||
? start | ||
: s.error(singleEqualsMessage) | ||
? start | ||
: s.error(singleEqualsMessage) | ||
@@ -120,10 +107,64 @@ type shiftComparator< | ||
: start extends OneCharComparator | ||
? [start, unscanned] | ||
: state.error<singleEqualsMessage> | ||
? [start, unscanned] | ||
: state.error<singleEqualsMessage> | ||
export const writeUnboundableMessage = <root extends string>( | ||
root: root | ||
): writeUnboundableMessage<root> => | ||
`Bounded expression ${root} must be a number, string, Array, or Date` | ||
export type writeUnboundableMessage<root extends string> = | ||
`Bounded expression ${root} must be a number, string, Array, or Date` | ||
export const writeIncompatibleRangeMessage = (l: BoundKind, r: BoundKind) => | ||
`Bound kinds ${l} and ${r} are incompatible` | ||
export const writeLimitMismatchMessage = ( | ||
root: string, | ||
limitValue: LimitSchemaValue | ||
) => `Limit '${limitValue}' cannot bound ${root}` | ||
export const getBoundKinds = ( | ||
comparator: Comparator, | ||
limit: LimitSchemaValue, | ||
root: TypeNode | ||
): BoundKind[] => { | ||
if (root.extends(keywords.number)) { | ||
if (typeof limit !== "number") { | ||
return throwParseError(writeLimitMismatchMessage(root.toString(), limit)) | ||
} | ||
return comparator === "==" | ||
? ["min", "max"] | ||
: comparator[0] === ">" | ||
? ["min"] | ||
: ["max"] | ||
} | ||
if (root.extends(keywords.string) || root.extends(keywords.Array)) { | ||
if (typeof limit !== "number") { | ||
return throwParseError(writeLimitMismatchMessage(root.toString(), limit)) | ||
} | ||
return comparator === "==" | ||
? ["minLength", "maxLength"] | ||
: comparator[0] === ">" | ||
? ["minLength"] | ||
: ["maxLength"] | ||
} | ||
if (root.extends(keywords.Date)) { | ||
// allow either numeric or date limits | ||
return comparator === "==" | ||
? ["after", "before"] | ||
: comparator[0] === ">" | ||
? ["after"] | ||
: ["before"] | ||
} | ||
return throwParseError(writeUnboundableMessage(root.toString())) | ||
} | ||
export const singleEqualsMessage = `= is not a valid comparator. Use == to check for equality` | ||
type singleEqualsMessage = typeof singleEqualsMessage | ||
const openLeftBoundToSchema = (leftBound: OpenLeftBound): MinSchema => ({ | ||
min: isDateLiteral(leftBound.limit) | ||
const openLeftBoundToSchema = ( | ||
leftBound: OpenLeftBound | ||
): Schema<BoundKind> => ({ | ||
limit: isDateLiteral(leftBound.limit) | ||
? extractDateLiteralSource(leftBound.limit) | ||
@@ -134,3 +175,2 @@ : leftBound.limit, | ||
// TODO: allow numeric limits for Dates? | ||
export const parseRightBound = ( | ||
@@ -143,3 +183,3 @@ s: DynamicStateWithRoot, | ||
const previousScannerIndex = s.scanner.location | ||
parseOperand(s) | ||
s.parseOperand() | ||
// after parsing the next operand, use the locations to get the | ||
@@ -159,9 +199,6 @@ // token from which it was parsed | ||
const exclusive = comparator.length === 1 | ||
// if the comparator is ==, both max and min will be applied | ||
if (comparator[0] !== ">") { | ||
s.constrainRoot("max", { max: limit, exclusive }) | ||
// if the comparator is ==, both the min and max of that pair will be applied | ||
for (const kind of getBoundKinds(comparator, limit, previousRoot)) { | ||
s.constrainRoot(kind, { limit, exclusive }) | ||
} | ||
if (comparator[0] !== "<") { | ||
s.constrainRoot("min", { min: limit, exclusive }) | ||
} | ||
if (!s.branches.leftBound) { | ||
@@ -174,3 +211,11 @@ return | ||
} | ||
s.constrainRoot("min", openLeftBoundToSchema(s.branches.leftBound)) | ||
const lowerBoundKind = getBoundKinds( | ||
s.branches.leftBound.comparator, | ||
s.branches.leftBound.limit, | ||
previousRoot | ||
) | ||
s.constrainRoot( | ||
lowerBoundKind[0], | ||
openLeftBoundToSchema(s.branches.leftBound) | ||
) | ||
delete s.branches.leftBound | ||
@@ -210,3 +255,3 @@ } | ||
limit extends string | number, | ||
boundKind extends BoundKind | ||
boundKind extends BoundExpressionKind | ||
>( | ||
@@ -224,3 +269,3 @@ comparator: comparator, | ||
limit extends string | number, | ||
boundKind extends BoundKind | ||
boundKind extends BoundExpressionKind | ||
> = `Comparator ${comparator} must be ${boundKind extends "left" | ||
@@ -230,12 +275,2 @@ ? "preceded" | ||
export type BoundKind = "left" | "right" | ||
export const invertedComparators = { | ||
"<": ">", | ||
">": "<", | ||
"<=": ">=", | ||
">=": "<=", | ||
"==": "==" | ||
} as const satisfies Record<Comparator, Comparator> | ||
export type InvertedComparators = typeof invertedComparators | ||
export type BoundExpressionKind = "left" | "right" |
@@ -17,18 +17,18 @@ import { isKeyOf } from "@arktype/util" | ||
: lookahead === "[" | ||
? s.scanner.shift() === "]" | ||
? s.setRoot(s.root.array()) | ||
: s.error(incompleteArrayTokenMessage) | ||
: lookahead === "|" || lookahead === "&" | ||
? s.pushRootToBranch(lookahead) | ||
: lookahead === ")" | ||
? s.finalizeGroup() | ||
: Scanner.lookaheadIsFinalizing(lookahead, s.scanner.unscanned) | ||
? s.finalize(lookahead) | ||
: isKeyOf(lookahead, comparatorStartChars) | ||
? parseBound(s, lookahead) | ||
: lookahead === "%" | ||
? parseDivisor(s) | ||
: lookahead === " " | ||
? parseOperator(s) | ||
: s.error(writeUnexpectedCharacterMessage(lookahead)) | ||
? s.scanner.shift() === "]" | ||
? s.setRoot(s.root.array()) | ||
: s.error(incompleteArrayTokenMessage) | ||
: lookahead === "|" || lookahead === "&" | ||
? s.pushRootToBranch(lookahead) | ||
: lookahead === ")" | ||
? s.finalizeGroup() | ||
: Scanner.lookaheadIsFinalizing(lookahead, s.scanner.unscanned) | ||
? s.finalize(lookahead) | ||
: isKeyOf(lookahead, comparatorStartChars) | ||
? parseBound(s, lookahead) | ||
: lookahead === "%" | ||
? parseDivisor(s) | ||
: lookahead === " " | ||
? parseOperator(s) | ||
: s.error(writeUnexpectedCharacterMessage(lookahead)) | ||
} | ||
@@ -46,17 +46,17 @@ | ||
: lookahead extends "|" | "&" | ||
? state.reduceBranch<s, lookahead, unscanned> | ||
: lookahead extends ")" | ||
? state.finalizeGroup<s, unscanned> | ||
: Scanner.lookaheadIsFinalizing<lookahead, unscanned> extends true | ||
? state.finalize< | ||
state.scanTo<s, unscanned>, | ||
lookahead & Scanner.FinalizingLookahead | ||
> | ||
: lookahead extends ComparatorStartChar | ||
? parseBound<s, lookahead, unscanned, $, args> | ||
: lookahead extends "%" | ||
? parseDivisor<s, unscanned> | ||
: lookahead extends Scanner.WhiteSpaceToken | ||
? parseOperator<state.scanTo<s, unscanned>, $, args> | ||
: state.error<writeUnexpectedCharacterMessage<lookahead>> | ||
? state.reduceBranch<s, lookahead, unscanned> | ||
: lookahead extends ")" | ||
? state.finalizeGroup<s, unscanned> | ||
: Scanner.lookaheadIsFinalizing<lookahead, unscanned> extends true | ||
? state.finalize< | ||
state.scanTo<s, unscanned>, | ||
lookahead & Scanner.FinalizingLookahead | ||
> | ||
: lookahead extends ComparatorStartChar | ||
? parseBound<s, lookahead, unscanned, $, args> | ||
: lookahead extends "%" | ||
? parseDivisor<s, unscanned> | ||
: lookahead extends Scanner.WhiteSpaceToken | ||
? parseOperator<state.scanTo<s, unscanned>, $, args> | ||
: state.error<writeUnexpectedCharacterMessage<lookahead>> | ||
: state.finalize<s, ""> | ||
@@ -63,0 +63,0 @@ |
import { isKeyOf, type Dict } from "@arktype/util" | ||
import type { Comparator } from "./operator/bounds.js" | ||
import type { Comparator } from "../reduce/shared.js" | ||
@@ -160,9 +160,9 @@ export class Scanner<Lookahead extends string = string> { | ||
: Scanner.skipWhitespace<unscanned> extends | ||
| "" | ||
| `${TerminatingChar}${string}` | ||
? true | ||
: false | ||
| "" | ||
| `${TerminatingChar}${string}` | ||
? true | ||
: false | ||
: lookahead extends "," | ||
? true | ||
: false | ||
? true | ||
: false | ||
@@ -169,0 +169,0 @@ export type shift< |
@@ -1,21 +0,17 @@ | ||
import type { Root } from "@arktype/schema" | ||
import { throwParseError, type ErrorMessage } from "@arktype/util" | ||
import type { ParseContext } from "../../scope.js" | ||
import type { inferAst } from "../semantic/semantic.js" | ||
import { writeUnsatisfiableExpressionError } from "../semantic/validate.js" | ||
import { DynamicState, type DynamicStateWithRoot } from "./reduce/dynamic.js" | ||
import type { TypeNode } from "@arktype/schema" | ||
import { | ||
throwInternalError, | ||
throwParseError, | ||
type ErrorMessage | ||
} from "@arktype/util" | ||
import type { inferAstRoot } from "../semantic/semantic.js" | ||
import type { DynamicState, DynamicStateWithRoot } from "./reduce/dynamic.js" | ||
import type { StringifiablePrefixOperator } from "./reduce/shared.js" | ||
import type { StaticState, state } from "./reduce/static.js" | ||
import { parseOperand } from "./shift/operand/operand.js" | ||
import type { parseOperand } from "./shift/operand/operand.js" | ||
import { | ||
parseOperator, | ||
writeUnexpectedCharacterMessage | ||
writeUnexpectedCharacterMessage, | ||
type parseOperator | ||
} from "./shift/operator/operator.js" | ||
export const parseString = (def: string, ctx: ParseContext): Root => | ||
ctx.scope.maybeResolveNode(def) ?? | ||
((def.endsWith("[]") && | ||
ctx.scope.maybeResolveNode(def.slice(0, -2))?.array()) || | ||
fullStringParse(def, ctx)) | ||
/** | ||
@@ -30,8 +26,8 @@ * Try to parse the definition from right to left using the most common syntax. | ||
: def extends `${infer child}[]` | ||
? child extends keyof $ | ||
? [child, "[]"] | ||
: fullStringParse<def, $, args> | ||
: fullStringParse<def, $, args> | ||
? child extends keyof $ | ||
? [child, "[]"] | ||
: fullStringParse<state.initialize<def>, $, args> | ||
: fullStringParse<state.initialize<def>, $, args> | ||
export type inferString<def extends string, $, args> = inferAst< | ||
export type inferString<def extends string, $, args> = inferAstRoot< | ||
parseString<def, $, args>, | ||
@@ -48,6 +44,10 @@ $, | ||
export const fullStringParse = (def: string, ctx: ParseContext) => { | ||
const s = new DynamicState(def, ctx) | ||
parseOperand(s) | ||
export const fullStringParse = (s: DynamicState): TypeNode => { | ||
s.parseOperand() | ||
const result = parseUntilFinalizer(s).root | ||
if (!result) { | ||
return throwInternalError( | ||
`Root was unexpectedly unset after parsing string '${s.scanner.scanned}'` | ||
) | ||
} | ||
s.scanner.shiftUntilNonWhitespace() | ||
@@ -58,10 +58,7 @@ if (s.scanner.lookahead) { | ||
} | ||
// TODO: would this ever happen? | ||
return result.isNever() | ||
? throwParseError(writeUnsatisfiableExpressionError(def)) | ||
: result | ||
return result | ||
} | ||
type fullStringParse<def extends string, $, args> = extractFinalizedResult< | ||
parseUntilFinalizer<state.initialize<def>, $, args> | ||
type fullStringParse<s extends StaticState, $, args> = extractFinalizedResult< | ||
parseUntilFinalizer<s, $, args> | ||
> | ||
@@ -85,3 +82,3 @@ | ||
const next = (s: DynamicState) => | ||
s.hasRoot() ? parseOperator(s) : parseOperand(s) | ||
s.hasRoot() ? s.parseOperator() : s.parseOperand() | ||
@@ -96,3 +93,3 @@ type next<s extends StaticState, $, args> = s["root"] extends undefined | ||
: s["finalizer"] extends "" | ||
? s["root"] | ||
: state.error<writeUnexpectedCharacterMessage<`${s["finalizer"]}`>> | ||
? s["root"] | ||
: state.error<writeUnexpectedCharacterMessage<`${s["finalizer"]}`>> |
import { | ||
builtins, | ||
node, | ||
type BaseAttributes, | ||
keywords, | ||
schema, | ||
type BaseMeta, | ||
type Morph, | ||
type Out, | ||
type Predicate, | ||
type Root, | ||
type Schema, | ||
type TypeNode, | ||
type ValidatorKind, | ||
type extractIn, | ||
type extractOut, | ||
type inferMorphOut, | ||
@@ -15,3 +19,3 @@ type inferNarrow | ||
objectKindOrDomainOf, | ||
stringify, | ||
printable, | ||
throwParseError, | ||
@@ -27,3 +31,2 @@ type BuiltinObjectKind, | ||
import type { ParseContext } from "../scope.js" | ||
import type { extractIn, extractOut } from "../type.js" | ||
import type { inferDefinition, validateDefinition } from "./definition.js" | ||
@@ -44,3 +47,3 @@ import type { inferIntersection } from "./semantic/intersections.js" | ||
export const parseTupleLiteral = (def: List, ctx: ParseContext): Root => { | ||
export const parseTupleLiteral = (def: List, ctx: ParseContext): TypeNode => { | ||
const props: unknown[] = [] | ||
@@ -65,3 +68,3 @@ let isVariadic = false | ||
if (isVariadic) { | ||
if (!value.extends(builtins.array)) { | ||
if (!value.extends(keywords.Array)) { | ||
return throwParseError(writeNonArrayRestMessage(elementDef)) | ||
@@ -75,3 +78,3 @@ } | ||
// TODO: first variadic i | ||
props.push({ key: builtins.number, value: elementType }) | ||
props.push({ key: keywords.number, value: elementType }) | ||
} else { | ||
@@ -97,7 +100,7 @@ props.push({ | ||
// , ctx | ||
value: node({ is: def.length }) | ||
value: schema({ unit: def.length }) | ||
}) | ||
} | ||
// props , ctx | ||
return node(Array) | ||
return schema(Array) | ||
} | ||
@@ -108,8 +111,8 @@ | ||
ctx: ParseContext | ||
): Root | undefined => { | ||
): TypeNode | undefined => { | ||
const tupleExpressionResult = isIndexOneExpression(def) | ||
? indexOneParsers[def[1]](def as never, ctx) | ||
: isIndexZeroExpression(def) | ||
? prefixParsers[def[0]](def as never, ctx) | ||
: undefined | ||
? prefixParsers[def[0]](def as never, ctx) | ||
: undefined | ||
if (tupleExpressionResult) { | ||
@@ -120,3 +123,3 @@ return tupleExpressionResult.isNever() | ||
def | ||
.map((def) => (typeof def === "string" ? def : stringify(def))) | ||
.map((def) => (typeof def === "string" ? def : printable(def))) | ||
.join(" ") | ||
@@ -141,17 +144,13 @@ ) | ||
: def extends PostfixExpression | ||
? validatePostfixExpression<def, $, args> | ||
: def extends InfixExpression | ||
? validateInfixExpression<def, $, args> | ||
: def extends | ||
| readonly ["", ...unknown[]] | ||
| readonly [unknown, "", ...unknown[]] | ||
? readonly [ | ||
def[0] extends "" | ||
? BaseCompletions<$, args, IndexZeroOperator> | ||
: def[0], | ||
def[1] extends "" | ||
? BaseCompletions<$, args, IndexOneOperator> | ||
: def[1] | ||
] | ||
: validateTupleLiteral<def, $, args> | ||
? validatePostfixExpression<def, $, args> | ||
: def extends InfixExpression | ||
? validateInfixExpression<def, $, args> | ||
: def extends | ||
| readonly ["", ...unknown[]] | ||
| readonly [unknown, "", ...unknown[]] | ||
? readonly [ | ||
def[0] extends "" ? BaseCompletions<$, args, IndexZeroOperator> : def[0], | ||
def[1] extends "" ? BaseCompletions<$, args, IndexOneOperator> : def[1] | ||
] | ||
: validateTupleLiteral<def, $, args> | ||
@@ -199,6 +198,6 @@ export type validateTupleLiteral< | ||
: isAny<result> extends true | ||
? writeNonArrayRestMessage<operand> | ||
: result extends readonly unknown[] | ||
? operand | ||
: writeNonArrayRestMessage<operand> | ||
? writeNonArrayRestMessage<operand> | ||
: result extends readonly unknown[] | ||
? operand | ||
: writeNonArrayRestMessage<operand> | ||
: never | ||
@@ -273,24 +272,24 @@ | ||
: def[1] extends "&" | ||
? inferIntersection< | ||
inferDefinition<def[0], $, args>, | ||
inferDefinition<def[2], $, args> | ||
> | ||
: def[1] extends "|" | ||
? inferDefinition<def[0], $, args> | inferDefinition<def[2], $, args> | ||
: def[1] extends ":" | ||
? inferNarrow<inferDefinition<def[0], $, args>, def[2]> | ||
: def[1] extends "=>" | ||
? parseMorph<def[0], def[2], $, args> | ||
: def[1] extends "@" | ||
? inferDefinition<def[0], $, args> | ||
: def extends readonly ["===", ...infer values] | ||
? values[number] | ||
: def extends readonly [ | ||
"instanceof", | ||
...infer constructors extends Constructor[] | ||
] | ||
? InstanceType<constructors[number]> | ||
: def[0] extends "keyof" | ||
? inferKeyOfExpression<def[1], $, args> | ||
: never | ||
? inferIntersection< | ||
inferDefinition<def[0], $, args>, | ||
inferDefinition<def[2], $, args> | ||
> | ||
: def[1] extends "|" | ||
? inferDefinition<def[0], $, args> | inferDefinition<def[2], $, args> | ||
: def[1] extends ":" | ||
? inferNarrow<inferDefinition<def[0], $, args>, def[2]> | ||
: def[1] extends "=>" | ||
? parseMorph<def[0], def[2], $, args> | ||
: def[1] extends "@" | ||
? inferDefinition<def[0], $, args> | ||
: def extends readonly ["===", ...infer values] | ||
? values[number] | ||
: def extends readonly [ | ||
"instanceof", | ||
...infer constructors extends Constructor[] | ||
] | ||
? InstanceType<constructors[number]> | ||
: def[0] extends "keyof" | ||
? inferKeyOfExpression<def[1], $, args> | ||
: never | ||
@@ -304,8 +303,8 @@ export type validatePrefixExpression< | ||
: def[0] extends "keyof" | ||
? readonly [def[0], validateDefinition<def[1], $, args>] | ||
: def[0] extends "===" | ||
? readonly [def[0], ...unknown[]] | ||
: def[0] extends "instanceof" | ||
? readonly [def[0], ...Constructor[]] | ||
: never | ||
? readonly [def[0], validateDefinition<def[1], $, args>] | ||
: def[0] extends "===" | ||
? readonly [def[0], ...unknown[]] | ||
: def[0] extends "instanceof" | ||
? readonly [def[0], ...Constructor[]] | ||
: never | ||
@@ -332,10 +331,10 @@ export type validatePostfixExpression< | ||
: def[1] extends "&" | ||
? validateDefinition<def[2], $, args> | ||
: def[1] extends ":" | ||
? Predicate<extractIn<inferDefinition<def[0], $, args>>> | ||
: def[1] extends "=>" | ||
? Morph<extractOut<inferDefinition<def[0], $, args>>, unknown> | ||
: def[1] extends "@" | ||
? BaseAttributes | string | ||
: validateDefinition<def[2], $, args> | ||
? validateDefinition<def[2], $, args> | ||
: def[1] extends ":" | ||
? Predicate<extractIn<inferDefinition<def[0], $, args>>> | ||
: def[1] extends "=>" | ||
? Morph<extractOut<inferDefinition<def[0], $, args>>, unknown> | ||
: def[1] extends "@" | ||
? BaseMeta | string | ||
: validateDefinition<def[2], $, args> | ||
] | ||
@@ -372,3 +371,3 @@ | ||
ctx: ParseContext | ||
) => Root | ||
) => TypeNode | ||
@@ -378,3 +377,3 @@ export type PrefixParser<token extends IndexZeroOperator> = ( | ||
ctx: ParseContext | ||
) => Root | ||
) => TypeNode | ||
@@ -404,15 +403,9 @@ export type TupleExpression = IndexZeroExpression | IndexOneExpression | ||
} | ||
// TODO: fix | ||
return ctx.scope.parse(def[0], ctx) //.constrain("morph", def[2] as Morph) | ||
// TODO: nested morphs? | ||
return schema({ | ||
in: ctx.scope.parse(def[0], ctx) as Schema<ValidatorKind>, | ||
morph: def[2] as Morph | ||
}) | ||
} | ||
export type parseMorph<inDef, morph, $, args> = morph extends Morph | ||
? ( | ||
// TODO: should this be extractOut | ||
In: extractIn<inferDefinition<inDef, $, args>> | ||
) => Out<inferMorphOut<ReturnType<morph>>> | ||
: never | ||
export type MorphAst<i = any, o = any> = (In: i) => Out<o> | ||
export const writeMalformedFunctionalExpressionMessage = ( | ||
@@ -426,2 +419,8 @@ operator: FunctionalTupleOperator, | ||
export type parseMorph<inDef, morph, $, args> = morph extends Morph | ||
? ( | ||
In: extractIn<inferDefinition<inDef, $, args>> | ||
) => Out<inferMorphOut<ReturnType<morph>>> | ||
: never | ||
export const parseNarrowTuple: PostfixParser<":"> = (def, ctx) => { | ||
@@ -465,3 +464,3 @@ if (typeof def[2] !== "function") { | ||
keyof: parseKeyOfTuple, | ||
instanceof: (def, ctx) => { | ||
instanceof: (def) => { | ||
if (typeof def[1] !== "function") { | ||
@@ -476,3 +475,3 @@ return throwParseError( | ||
typeof ctor === "function" | ||
? { basis: ctor as Constructor } | ||
? { proto: ctor as Constructor } | ||
: throwParseError( | ||
@@ -482,5 +481,5 @@ writeInvalidConstructorMessage(objectKindOrDomainOf(ctor)) | ||
) | ||
return node(...branches) | ||
return schema(...branches) | ||
}, | ||
"===": (def) => node({ is: def.slice(1) }) | ||
"===": (def) => schema.units(...def.slice(1)) | ||
} | ||
@@ -495,2 +494,2 @@ | ||
actual: actual | ||
) => `Expected a constructor following 'instanceof' operator (was ${actual}).` | ||
) => `Expected a constructor following 'instanceof' operator (was ${actual})` |
161
scope.ts
@@ -1,8 +0,16 @@ | ||
import { builtins, type ProblemCode, type Root } from "@arktype/schema" | ||
import { | ||
BaseType, | ||
keywords, | ||
type ArkErrorCode, | ||
type KeyCheckKind, | ||
type TypeNode, | ||
type extractIn, | ||
type extractOut | ||
} from "@arktype/schema" | ||
import { | ||
domainOf, | ||
hasDomain, | ||
isThunk, | ||
map, | ||
throwParseError, | ||
transform, | ||
type Dict, | ||
@@ -13,9 +21,5 @@ type evaluate, | ||
} from "@arktype/util" | ||
import type { type } from "./ark.js" | ||
import { createMatchParser, type MatchParser } from "./match.js" | ||
import { | ||
createMatchParser, | ||
createWhenParser, | ||
type MatchParser, | ||
type WhenParser | ||
} from "./match.js" | ||
import { | ||
parseObject, | ||
@@ -31,2 +35,3 @@ writeBadDefinitionTypeMessage, | ||
} from "./parser/generic.js" | ||
import { DynamicState } from "./parser/string/reduce/dynamic.js" | ||
import { | ||
@@ -37,22 +42,16 @@ writeMissingSubmoduleAccessMessage, | ||
} from "./parser/string/shift/operand/unenclosed.js" | ||
import { parseString } from "./parser/string/string.js" | ||
import type { type } from "./scopes/ark.js" | ||
import { fullStringParse } from "./parser/string/string.js" | ||
import { | ||
addArkKind, | ||
Type, | ||
createTypeParser, | ||
generic, | ||
hasArkKind, | ||
Type, | ||
validateUninstantiatedGeneric, | ||
type arkKind, | ||
type DeclarationParser, | ||
type DefinitionParser, | ||
type extractIn, | ||
type extractOut, | ||
type Generic, | ||
type GenericProps, | ||
type KeyCheckKind, | ||
type TypeConfig, | ||
type TypeParser | ||
} from "./type.js" | ||
import { addArkKind, hasArkKind, type arkKind } from "./util.js" | ||
@@ -77,3 +76,3 @@ export type ScopeParser<parent, ambient> = { | ||
ambient?: Scope | null | ||
codes?: Record<ProblemCode, { mustBe?: string }> | ||
codes?: Record<ArkErrorCode, { mustBe?: string }> | ||
keys?: KeyCheckKind | ||
@@ -89,14 +88,14 @@ } | ||
: parseScopeKey<k>["params"] extends GenericParamsParseError | ||
? // use the full nominal type here to avoid an overlap between the | ||
// error message and a possible value for the property | ||
parseScopeKey<k>["params"][0] | ||
: validateDefinition< | ||
def[k], | ||
$ & bootstrap<def>, | ||
{ | ||
// once we support constraints on generic parameters, we'd use | ||
// the base type here: https://github.com/arktypeio/arktype/issues/796 | ||
[param in parseScopeKey<k>["params"][number]]: unknown | ||
} | ||
> | ||
? // use the full nominal type here to avoid an overlap between the | ||
// error message and a possible value for the property | ||
parseScopeKey<k>["params"][0] | ||
: validateDefinition< | ||
def[k], | ||
$ & bootstrap<def>, | ||
{ | ||
// once we support constraints on generic parameters, we'd use | ||
// the base type here: https://github.com/arktypeio/arktype/issues/796 | ||
[param in parseScopeKey<k>["params"][number]]: unknown | ||
} | ||
> | ||
} | ||
@@ -106,7 +105,2 @@ | ||
export const bindThis = () => ({ | ||
// TODO: fix | ||
this: builtins.unknown | ||
}) | ||
/** nominal type for an unparsed definition used during scope bootstrapping */ | ||
@@ -141,4 +135,4 @@ type Def<def = {}> = nominal<def, "unparsed"> | ||
: def[k] extends (() => infer thunkReturn extends PreparsedResolution) | ||
? thunkReturn | ||
: Def<def[k]> | ||
? thunkReturn | ||
: Def<def[k]> | ||
} & { | ||
@@ -156,6 +150,6 @@ [k in keyof def & GenericDeclaration as extractGenericName<k>]: Generic< | ||
: r["exports"][name] extends GenericProps<infer params, infer def> | ||
? // add the scope in which the generic was defined here | ||
Generic<params, def, $<r>> | ||
: // otherwise should be a submodule | ||
r["exports"][name] | ||
? // add the scope in which the generic was defined here | ||
Generic<params, def, $<r>> | ||
: // otherwise should be a submodule | ||
r["exports"][name] | ||
}> | ||
@@ -186,6 +180,6 @@ | ||
: isAny<resolution> extends true | ||
? any | ||
: resolution extends Def<infer def> | ||
? inferDefinition<def, $, args> | ||
: resolution | ||
? any | ||
: resolution extends Def<infer def> | ||
? inferDefinition<def, $, args> | ||
: resolution | ||
: never | ||
@@ -216,6 +210,6 @@ | ||
: isAny<r["exports"][k]> extends true | ||
? Type<any, $<r>> | ||
: r["exports"][k] extends PreparsedResolution | ||
? r["exports"][k] | ||
: Type<r["exports"][k], $<r>> | ||
? Type<any, $<r>> | ||
: r["exports"][k] extends PreparsedResolution | ||
? r["exports"][k] | ||
: Type<r["exports"][k], $<r>> | ||
: // set the nominal symbol's value to something validation won't care about | ||
@@ -236,6 +230,6 @@ // since the inferred type will be omitted anyways | ||
scope: Scope | ||
args: Record<string, Root> | undefined | ||
args: Record<string, TypeNode> | undefined | ||
} | ||
type MergedResolutions = Record<string, Root | Generic> | ||
type MergedResolutions = Record<string, TypeNode | Generic> | ||
@@ -250,3 +244,3 @@ type ParseContextInput = Pick<ParseContext, "baseName" | "args"> | ||
private parseCache: Record<string, Root> = {} | ||
private parseCache: Record<string, TypeNode> = {} | ||
private resolutions: MergedResolutions | ||
@@ -259,3 +253,2 @@ | ||
private ambient: Scope | null | ||
private references: Root[] = [] | ||
@@ -291,4 +284,2 @@ constructor(def: Dict, config: ScopeConfig) { | ||
when: WhenParser<$<r>> = createWhenParser(this as never) as never | ||
// TODO: decide if this API will be used for non-validated types | ||
@@ -341,3 +332,3 @@ declare: DeclarationParser<$<r>> = () => ({ type: this.type }) as never | ||
parse(def: unknown, ctx: ParseContext): Root { | ||
parse(def: unknown, ctx: ParseContext): TypeNode { | ||
if (typeof def === "string") { | ||
@@ -347,6 +338,6 @@ if (ctx.args !== undefined) { | ||
// resolutions like "this" or generic args | ||
return parseString(def, ctx) | ||
return this.parseString(def, ctx) | ||
} | ||
if (!this.parseCache[def]) { | ||
this.parseCache[def] = parseString(def, ctx) | ||
this.parseCache[def] = this.parseString(def, ctx) | ||
} | ||
@@ -360,3 +351,12 @@ return this.parseCache[def] | ||
maybeResolve(name: string): Root | Generic | undefined { | ||
parseString(def: string, ctx: ParseContext): TypeNode { | ||
return ( | ||
this.maybeResolveNode(def) ?? | ||
((def.endsWith("[]") && | ||
this.maybeResolveNode(def.slice(0, -2))?.array()) || | ||
fullStringParse(new DynamicState(def, ctx))) | ||
) | ||
} | ||
maybeResolve(name: string): TypeNode | Generic | undefined { | ||
const cached = this.resolutions[name] | ||
@@ -377,7 +377,7 @@ if (cached) { | ||
: hasArkKind(def, "module") | ||
? throwParseError(writeMissingSubmoduleAccessMessage(name)) | ||
: this.parseDefinition( | ||
def, | ||
this.createRootContext({ baseName: name, args: {} }) | ||
) | ||
? throwParseError(writeMissingSubmoduleAccessMessage(name)) | ||
: this.parseDefinition( | ||
def, | ||
this.createRootContext({ baseName: name, args: {} }) | ||
) | ||
this.resolutions[name] = resolution | ||
@@ -412,5 +412,5 @@ return resolution | ||
maybeResolveNode(name: string): Root | undefined { | ||
maybeResolveNode(name: string): TypeNode | undefined { | ||
const result = this.maybeResolve(name) | ||
return hasArkKind(result, "node") ? (result as never) : undefined | ||
return result instanceof BaseType ? (result as never) : undefined | ||
} | ||
@@ -422,7 +422,7 @@ | ||
r, | ||
names extends [] ? keyof r["exports"] : names[number] | ||
names extends [] ? keyof r["exports"] & string : names[number] | ||
> { | ||
return addArkKind( | ||
transform(this.export(...names), ([alias, value]) => [ | ||
`#${alias as string}`, | ||
map(this.export(...names) as Dict, (alias, value) => [ | ||
`#${alias}`, | ||
value | ||
@@ -437,3 +437,3 @@ ]) as never, | ||
// this.export() | ||
// const references: Set<TypeNode> = new Set() | ||
// const references: Set<Root> = new Set() | ||
// for (const k in this.exportedResolutions!) { | ||
@@ -457,2 +457,7 @@ // const resolution = this.exportedResolutions[k] | ||
bindThis() { | ||
// TODO: fix | ||
return { this: keywords.unknown } | ||
} | ||
private exportedResolutions: MergedResolutions | undefined | ||
@@ -490,6 +495,3 @@ private exportCache: ExportCache | undefined | ||
return addArkKind( | ||
transform(namesToExport, ([, name]) => [ | ||
name, | ||
this.exportCache![name] | ||
]) as never, | ||
map(namesToExport, (_, name) => [name, this.exportCache![name]]) as never, | ||
"module" | ||
@@ -508,6 +510,6 @@ ) as never | ||
const innerResolutions = resolutionsOfModule(v as never) | ||
const prefixedResolutions = transform( | ||
innerResolutions, | ||
([innerK, innerV]) => [`${k}.${innerK}`, innerV] | ||
) | ||
const prefixedResolutions = map(innerResolutions, (innerK, innerV) => [ | ||
`${k}.${innerK}`, | ||
innerV | ||
]) | ||
Object.assign(result, prefixedResolutions) | ||
@@ -517,4 +519,3 @@ } else if (hasArkKind(v, "generic")) { | ||
} else { | ||
// TODO: needed? | ||
result[k] = v.root as never | ||
result[k] = (v as Type).root | ||
} | ||
@@ -521,0 +522,0 @@ } |
208
type.ts
import { | ||
In, | ||
TraversalState, | ||
arkKind, | ||
builtins, | ||
inferred, | ||
registry, | ||
type BaseAttributes, | ||
type CheckResult, | ||
keywords, | ||
type ArkResult, | ||
type BaseMeta, | ||
type KeyCheckKind, | ||
type Morph, | ||
type Out, | ||
type Predicate, | ||
type Root, | ||
type UnknownNode, | ||
type TypeNode, | ||
type distill, | ||
type extractIn, | ||
type extractOut, | ||
type includesMorphs, | ||
type inferMorphOut, | ||
@@ -19,9 +19,7 @@ type inferNarrow | ||
import { | ||
CompiledFunction, | ||
transform, | ||
type BuiltinObjectKind, | ||
type BuiltinObjects, | ||
Callable, | ||
CastableBase, | ||
map, | ||
type Constructor, | ||
type Json, | ||
type Primitive, | ||
type conform | ||
@@ -42,6 +40,6 @@ } from "@arktype/util" | ||
IndexZeroOperator, | ||
MorphAst, | ||
TupleInfixOperator | ||
} from "./parser/tuple.js" | ||
import { bindThis, type Module, type Scope } from "./scope.js" | ||
import type { Scope, bindThis } from "./scope.js" | ||
import { arkKind } from "./util.js" | ||
@@ -59,20 +57,20 @@ export type TypeParser<$> = { | ||
: zero extends "instanceof" | ||
? conform<one, Constructor> | ||
: zero extends "===" | ||
? conform<one, unknown> | ||
: conform<one, IndexOneOperator>, | ||
? conform<one, Constructor> | ||
: zero extends "===" | ||
? conform<one, unknown> | ||
: conform<one, IndexOneOperator>, | ||
..._2: zero extends "===" | ||
? rest | ||
: zero extends "instanceof" | ||
? conform<rest, readonly Constructor[]> | ||
: one extends TupleInfixOperator | ||
? one extends ":" | ||
? [Predicate<extractIn<inferTypeRoot<zero, $>>>] | ||
: one extends "=>" | ||
? // TODO: centralize | ||
[Morph<extractOut<inferTypeRoot<zero, $>>, unknown>] | ||
: one extends "@" | ||
? [string | BaseAttributes] | ||
: [validateTypeRoot<rest[0], $>] | ||
: [] | ||
? conform<rest, readonly Constructor[]> | ||
: one extends TupleInfixOperator | ||
? one extends ":" | ||
? [Predicate<extractIn<inferTypeRoot<zero, $>>>] | ||
: one extends "=>" | ||
? // TODO: centralize | ||
[Morph<extractOut<inferTypeRoot<zero, $>>, unknown>] | ||
: one extends "@" | ||
? [string | BaseMeta] | ||
: [validateTypeRoot<rest[0], $>] | ||
: [] | ||
): Type<inferTypeRoot<[zero, one, ...rest], $>, $> | ||
@@ -125,26 +123,2 @@ | ||
export type ArkKinds = { | ||
node: UnknownNode | ||
generic: Generic | ||
module: Module | ||
} | ||
export const addArkKind = <kind extends ArkKind>( | ||
value: Omit<ArkKinds[kind], arkKind> & { [arkKind]?: kind }, | ||
kind: kind | ||
): ArkKinds[kind] => | ||
Object.defineProperty(value, arkKind, { | ||
value: kind, | ||
enumerable: false | ||
}) as never | ||
export type arkKind = typeof arkKind | ||
export type ArkKind = keyof ArkKinds | ||
export const hasArkKind = <kind extends ArkKind>( | ||
value: unknown, | ||
kind: kind | ||
): value is ArkKinds[kind] => (value as any)?.[arkKind] === kind | ||
export type DefinitionParser<$> = <def>( | ||
@@ -154,4 +128,2 @@ def: validateDefinition<def, $, bindThis<def>> | ||
export type KeyCheckKind = "distilled" | "strict" | "loose" | ||
export type TypeConfig = { | ||
@@ -162,17 +134,13 @@ keys?: KeyCheckKind | ||
registry().register(TraversalState, "state") | ||
export class Type<t = unknown, $ = any> extends CompiledFunction< | ||
(data: unknown) => CheckResult<extractOut<t>> | ||
export class Type<t = unknown, $ = any> extends Callable< | ||
(data: unknown) => ArkResult<distill<extractOut<t>>> | ||
> { | ||
declare [inferred]: t | ||
declare inferMorph: t | ||
declare infer: extractOut<t> | ||
declare inferIn: extractIn<t> | ||
// TODO: in/out? | ||
declare infer: distill<extractOut<t>> | ||
config: TypeConfig | ||
root: Root<t> | ||
condition = "" | ||
alias: string | ||
root: TypeNode<t> | ||
allows: this["root"]["allows"] | ||
expected: string | ||
json: Json | ||
@@ -184,15 +152,9 @@ | ||
) { | ||
const root = parseTypeRoot(definition, scope) as Root<t> | ||
super(In, `return true ? { data: ${In} } : { problems: [] } `) | ||
// const state = new ${registry().reference("state")}(); | ||
// const morphs = []; | ||
// for(let i = 0; i < morphs.length; i++) { | ||
// morphs[i]() | ||
// } | ||
// return state.finalize(${In}); | ||
const root = parseTypeRoot(definition, scope) as TypeNode<t> | ||
super(root.apply, root) | ||
this.root = root | ||
this.allows = root.allows | ||
this.config = scope.config | ||
this.json = this.root.json | ||
this.alias = this.root.alias | ||
this.json = root.json | ||
this.expected = this.root.description | ||
} | ||
@@ -206,10 +168,6 @@ | ||
// TODO: should return out | ||
from(literal: this["infer"]) { | ||
from(literal: this["in"]["infer"]) { | ||
return literal | ||
} | ||
fromIn(literal: this["inferIn"]) { | ||
return literal | ||
} | ||
// TODO: Morph intersections, ordering | ||
@@ -232,10 +190,11 @@ and<def>( | ||
morph<morph extends Morph<extractOut<t>>>( | ||
// TODO: standardize these | ||
morph<morph extends Morph<this["infer"]>>( | ||
morph: morph | ||
): Type<(In: this["inferIn"]) => Out<inferMorphOut<ReturnType<morph>>>, $> | ||
morph<morph extends Morph<extractOut<t>>, def>( | ||
): Type<(In: this["in"]["infer"]) => Out<inferMorphOut<ReturnType<morph>>>, $> | ||
morph<morph extends Morph<this["infer"]>, def>( | ||
morph: morph, | ||
outValidator: validateTypeRoot<def, $> | ||
): Type< | ||
(In: this["inferIn"]) => Out< | ||
(In: this["in"]["infer"]) => Out< | ||
// TODO: validate overlapping | ||
@@ -262,3 +221,3 @@ // inferMorphOut<ReturnType<morph>> & | ||
includesMorphs<t> extends true | ||
? (In: this["inferIn"]) => Out<inferNarrow<this["infer"], def>> | ||
? (In: this["in"]["infer"]) => Out<inferNarrow<this["infer"], def>> | ||
: inferNarrow<this["infer"], def>, | ||
@@ -274,23 +233,41 @@ $ | ||
keyof(): Type<keyof this["inferIn"], $> { | ||
keyof(): Type<keyof this["in"]["infer"], $> { | ||
return new Type(this.root.keyof(), this.scope) as never | ||
} | ||
assert(data: unknown): extractOut<t> { | ||
assert(data: unknown): this["infer"] { | ||
const result = this.call(null, data) | ||
return result.problems ? result.problems.throw() : result.data | ||
return result.errors ? result.errors.throw() : result.out | ||
} | ||
// TODO: parse these | ||
equals<other>(other: Type<other>): this is Type<other, $> { | ||
return this.root === (other.root as unknown) | ||
equals<def>( | ||
other: validateTypeRoot<def, $> | ||
): this is Type<inferTypeRoot<def, $>, $> { | ||
return this.root.equals(parseTypeRoot(other, this.scope)) | ||
} | ||
extends<other>(other: Type<other>): this is Type<other, $> { | ||
return this.root.extends(other.root) | ||
extends<def>( | ||
other: validateTypeRoot<def, $> | ||
): this is Type<inferTypeRoot<def, $>, $> { | ||
return this.root.extends(parseTypeRoot(other, this.scope)) | ||
} | ||
private inCache?: Type<extractIn<t>, $>; | ||
get in() { | ||
this.inCache ??= new Type(this.root.in, this.scope) as never | ||
return this.inCache | ||
} | ||
outCache?: Type<extractOut<t>, $> | ||
get out() { | ||
this.outCache ??= new Type(this.root.out, this.scope) as never | ||
return this.outCache | ||
} | ||
} | ||
const parseTypeRoot = (def: unknown, scope: Scope, args?: BoundArgs) => | ||
scope.parseDefinition(def, { args: args ?? bindThis(), baseName: "type" }) | ||
scope.parseDefinition(def, { | ||
args: args ?? scope.bindThis(), | ||
baseName: "type" | ||
}) | ||
@@ -315,3 +292,3 @@ export type validateTypeRoot<def, $> = validateDefinition<def, $, bindThis<def>> | ||
baseName: "generic", | ||
args: transform(g.parameters, ([, name]) => [name, builtins.unknown]) | ||
args: map(g.parameters, (_, name) => [name, keywords.unknown]) | ||
} | ||
@@ -329,3 +306,3 @@ ) | ||
(...args: unknown[]) => { | ||
const argNodes = transform(parameters, ([i, param]) => [ | ||
const argNodes = map(parameters, (i, param) => [ | ||
param, | ||
@@ -360,3 +337,3 @@ parseTypeRoot(args[i], scope) | ||
export type BoundArgs = Record<string, Root> | ||
export type BoundArgs = Record<string, TypeNode> | ||
@@ -385,34 +362,1 @@ // TODO: Fix external reference (i.e. if this is attached to a scope, then args are defined using it) | ||
} | ||
export type extractIn<t> = includesMorphs<t> extends true | ||
? extractMorphs<t, "in"> | ||
: t | ||
export type extractOut<t> = includesMorphs<t> extends true | ||
? extractMorphs<t, "out"> | ||
: t | ||
type includesMorphs<t> = [ | ||
t, | ||
extractMorphs<t, "in">, | ||
t, | ||
extractMorphs<t, "out"> | ||
] extends [extractMorphs<t, "in">, t, extractMorphs<t, "out">, t] | ||
? false | ||
: true | ||
type extractMorphs<t, io extends "in" | "out"> = t extends MorphAst< | ||
infer i, | ||
infer o | ||
> | ||
? io extends "in" | ||
? i | ||
: o | ||
: t extends TerminallyInferredObjectKind | Primitive | ||
? t | ||
: { [k in keyof t]: extractMorphs<t[k], io> } | ||
/** Objects we don't want to expand during inference like Date or Promise */ | ||
type TerminallyInferredObjectKind = | ||
| ReturnType<ArkConfig["preserve"]> | ||
| BuiltinObjects[Exclude<BuiltinObjectKind, "Object" | "Array">] |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
678941
211
14874
+ Added@arktype/schema@0.0.3(transitive)
+ Added@arktype/util@0.0.17(transitive)
- Removed@arktype/schema@0.0.2(transitive)
- Removed@arktype/util@0.0.5(transitive)
Updated@arktype/schema@0.0.3
Updated@arktype/util@0.0.17