import type { AbstractableConstructor, BuiltinObjectKind, Domain, evaluate, isAny, List } from "../../dev/utils/src/main.js" import { domainOf, isArray, objectKindOf, stringify, throwParseError } from "../../dev/utils/src/main.js" import type { CheckResult, TraversalState } from "../compile/traverse.js" import type { Problem } from "../main.js" import { arrayIndexTypeNode } from "../nodes/composite/indexed.js" import { predicateNode } from "../nodes/composite/predicate.js" import type { NodeEntry } from "../nodes/composite/props.js" import { propsNode } from "../nodes/composite/props.js" import type { TypeNode } from "../nodes/composite/type.js" import { builtins, typeNode } from "../nodes/composite/type.js" import { arrayClassNode } from "../nodes/primitive/basis/class.js" import type { ParseContext } from "../scope.js" import type { extractIn, extractOut } from "../type.js" import { type InfixOperator, type PostfixExpression, writeUnsatisfiableExpressionError } from "./ast/ast.js" import type { inferIntersection } from "./ast/intersections.js" import type { astToString } from "./ast/utils.js" import type { inferDefinition, validateDefinition } from "./definition.js" import { writeMissingRightOperandMessage } from "./string/shift/operand/unenclosed.js" import type { BaseCompletions } from "./string/string.js" export const parseTuple = (def: List, ctx: ParseContext): TypeNode => { const tupleExpressionResult = isIndexOneExpression(def) ? indexOneParsers[def[1]](def as never, ctx) : isIndexZeroExpression(def) ? prefixParsers[def[0]](def as never, ctx) : undefined if (tupleExpressionResult) { return tupleExpressionResult.isNever() ? throwParseError( writeUnsatisfiableExpressionError( def .map((def) => typeof def === "string" ? def : stringify(def) ) .join(" ") ) ) : tupleExpressionResult } const props: NodeEntry[] = [] let isVariadic = false for (let i = 0; i < def.length; i++) { let elementDef = def[i] ctx.path.push(`${i}`) if (typeof elementDef === "string" && elementDef.startsWith("...")) { elementDef = elementDef.slice(3) isVariadic = true } else if ( isArray(elementDef) && elementDef.length === 2 && elementDef[0] === "..." ) { elementDef = elementDef[1] isVariadic = true } const value = ctx.scope.parse(elementDef, ctx) if (isVariadic) { if (!value.extends(builtins.array())) { return throwParseError(writeNonArrayRestMessage(elementDef)) } if (i !== def.length - 1) { return throwParseError(prematureRestMessage) } const elementType = value.getPath(arrayIndexTypeNode()) props.push({ key: arrayIndexTypeNode(i), value: elementType }) } else { props.push({ key: { name: `${i}`, prerequisite: false, optional: false }, value }) } ctx.path.pop() } if (!isVariadic) { props.push({ key: { name: "length", prerequisite: true, optional: false }, value: typeNode({ basis: ["===", def.length] }) }) } const predicate = predicateNode([arrayClassNode(), propsNode(props)]) return typeNode([predicate]) } type InfixExpression = [unknown, InfixOperator, ...unknown[]] export type validateTuple< def extends List, $, args > = def extends IndexZeroExpression ? validatePrefixExpression : def extends PostfixExpression ? validatePostfixExpression : def extends InfixExpression ? validateInfixExpression : def extends | readonly ["", ...unknown[]] | readonly [unknown, "", ...unknown[]] ? [ def[0] extends "" ? BaseCompletions<$, args, IndexZeroOperator> : def[0], def[1] extends "" ? BaseCompletions<$, args, IndexOneOperator> : def[1] ] : validateTupleLiteral export type validateTupleLiteral< def extends List, $, args, result extends unknown[] = [] > = def extends [infer head, ...infer tail] ? validateTupleLiteral< tail, $, args, [ ...result, head extends variadicExpression ? validateDefinition< operand, $, args > extends infer syntacticResult ? syntacticResult extends operand ? semanticallyValidateRestElement< operand, $, args > extends infer semanticResult ? semanticResult extends operand ? tail extends [] ? head : prematureRestMessage : semanticResult : never : syntacticResult : never : validateDefinition ] > : result type semanticallyValidateRestElement = inferDefinition< operand, $, args > extends infer result ? result extends never ? writeNonArrayRestMessage : isAny extends true ? writeNonArrayRestMessage : result extends readonly unknown[] ? operand : writeNonArrayRestMessage : never export const writeNonArrayRestMessage = (operand: operand) => `Rest element ${ typeof operand === "string" ? `'${operand}'` : "" } must be an array` as writeNonArrayRestMessage type writeNonArrayRestMessage = `Rest element ${operand extends string ? `'${operand}'` : ""} must be an array` export const prematureRestMessage = `Rest elements are only allowed at the end of a tuple` type prematureRestMessage = typeof prematureRestMessage type inferTupleLiteral< def extends List, $, args, result extends unknown[] = [] > = def extends [infer head, ...infer tail] ? inferDefinition< head extends variadicExpression ? operand : head, $, args > extends infer element ? head extends variadicExpression ? element extends readonly unknown[] ? inferTupleLiteral : never : inferTupleLiteral : never : result type variadicExpression = | variadicStringExpression | variadicTupleExpression type variadicStringExpression = `...${operandDef}` type variadicTupleExpression = ["...", operandDef] export type inferTuple = def extends TupleExpression ? inferTupleExpression : inferTupleLiteral export type inferTupleExpression< def extends TupleExpression, $, args > = def[1] extends "[]" ? inferDefinition[] : def[1] extends "&" ? inferIntersection< inferDefinition, inferDefinition > : def[1] extends "|" ? inferDefinition | inferDefinition : def[1] extends ":" ? inferNarrow, def[2]> : def[1] extends "=>" ? parseMorph : def[0] extends "===" ? def[1] : def[0] extends "instanceof" ? def[1] extends AbstractableConstructor ? t : never : def[0] extends "keyof" ? inferKeyOfExpression : never export type validatePrefixExpression< def extends IndexZeroExpression, $, args > = def["length"] extends 1 ? [writeMissingRightOperandMessage] : [ def[0] extends "keyof" ? inferDefinition extends never ? writeUnsatisfiableExpressionError> : def[0] : def[0], def[0] extends "===" ? def[1] : def[0] extends "instanceof" ? AbstractableConstructor : def[0] extends "keyof" ? validateDefinition : never ] export type validatePostfixExpression< def extends PostfixExpression, $, args > = [validateDefinition, "[]"] export type validateInfixExpression< def extends InfixExpression, $, args > = def["length"] extends 2 ? [def[0], writeMissingRightOperandMessage] : [ validateDefinition, def[1] extends "&" ? inferDefinition extends never ? writeUnsatisfiableExpressionError<"intersection"> : def["1"] : def[1], def[1] extends "|" ? validateDefinition : def[1] extends "&" ? validateDefinition : def[1] extends ":" ? Narrow>> : def[1] extends "=>" ? Morph>, unknown> : validateDefinition ] export type UnparsedTupleExpressionInput = { instanceof: AbstractableConstructor "===": unknown } export type UnparsedTupleOperator = evaluate export const parseKeyOfTuple: PrefixParser<"keyof"> = (def, ctx) => ctx.scope.parse(def[1], ctx).keyof() export type inferKeyOfExpression = evaluate< keyof inferDefinition > const parseBranchTuple: PostfixParser<"|" | "&"> = (def, ctx) => { if (def[2] === undefined) { return throwParseError(writeMissingRightOperandMessage(def[1], "")) } const l = ctx.scope.parse(def[0], ctx) const r = ctx.scope.parse(def[2], ctx) return def[1] === "&" ? l.and(r) : l.or(r) } const parseArrayTuple: PostfixParser<"[]"> = (def, ctx) => ctx.scope.parse(def[0], ctx).array() export type PostfixParser = ( def: IndexOneExpression, ctx: ParseContext ) => TypeNode export type PrefixParser = ( def: IndexZeroExpression, ctx: ParseContext ) => TypeNode export type TupleExpression = IndexZeroExpression | IndexOneExpression export type TupleExpressionOperator = IndexZeroOperator | IndexOneOperator export type IndexOneOperator = TuplePostfixOperator | TupleInfixOperator export type TuplePostfixOperator = "[]" export type TupleInfixOperator = "&" | "|" | "=>" | ":" export type IndexOneExpression< token extends IndexOneOperator = IndexOneOperator > = readonly [unknown, token, ...unknown[]] const isIndexOneExpression = (def: List): def is IndexOneExpression => indexOneParsers[def[1] as IndexOneOperator] !== undefined export const parseMorphTuple: PostfixParser<"=>"> = (def, ctx) => { if (typeof def[2] !== "function") { return throwParseError( writeMalformedFunctionalExpressionMessage("=>", def[2]) ) } return ctx.scope.parse(def[0], ctx).constrain("morph", def[2] as Morph) } export type Morph = (In: i, state: TraversalState) => o export type parseMorph = morph extends Morph ? ( // TODO: should this be extractOut In: extractIn> ) => Out>> : never export type MorphAst = (In: i) => Out export type Out = ["=>", o] export type inferMorphOut = out extends CheckResult ? out extends null ? // avoid treating any/never as CheckResult out : t : Exclude export const writeMalformedFunctionalExpressionMessage = ( operator: FunctionalTupleOperator, value: unknown ) => `${ operator === ":" ? "Narrow" : "Morph" } expression requires a function following '${operator}' (was ${typeof value})` export const parseNarrowTuple: PostfixParser<":"> = (def, ctx) => { if (typeof def[2] !== "function") { return throwParseError( writeMalformedFunctionalExpressionMessage(":", def[2]) ) } return ctx.scope.parse(def[0], ctx).constrain("narrow", def[2] as Narrow) } export type Narrow = (data: data, state: TraversalState) => boolean export type NarrowCast = ( data: data, state: TraversalState ) => data is narrowed export type inferNarrow = predicate extends ( data: any, ...args: any[] ) => data is infer narrowed ? narrowed : In const indexOneParsers: { [token in IndexOneOperator]: PostfixParser } = { "|": parseBranchTuple, "&": parseBranchTuple, "[]": parseArrayTuple, ":": parseNarrowTuple, "=>": parseMorphTuple } export type FunctionalTupleOperator = ":" | "=>" export type IndexZeroOperator = "keyof" | "instanceof" | "===" export type IndexZeroExpression< token extends IndexZeroOperator = IndexZeroOperator > = readonly [token, ...unknown[]] const prefixParsers: { [token in IndexZeroOperator]: PrefixParser } = { keyof: parseKeyOfTuple, instanceof: (def) => { const objectKind = objectKindOf(def[1]) if (objectKind !== "Function") { return throwParseError( writeInvalidConstructorMessage( objectKind ? objectKind : domainOf(def[1]) ) ) } return typeNode({ basis: def[1] as AbstractableConstructor }) }, "===": (def) => typeNode({ basis: ["===", def[1]] }) } const isIndexZeroExpression = (def: List): def is IndexZeroExpression => prefixParsers[def[0] as IndexZeroOperator] !== undefined export const writeInvalidConstructorMessage = < actual extends Domain | BuiltinObjectKind >( actual: actual ) => `Expected a constructor following 'instanceof' operator (was ${actual}).`