import { Err, Result } from "../result"; import { at } from "../issue"; import { typeMismatch } from "../issues/shared"; import { asKind } from "../as-kind"; import { TypedKind } from "../kind"; import { Projection, Type, TypeImpl } from "../type"; export class Arr extends TypeImpl> { readonly elementType: TypedKind; constructor(t: Type) { super(); this.elementType = asKind(t); } check(val: any): Result> { if(!Array.isArray(val)) return new Err(typeMismatch("array", val)); for(let index = 0; index < val.length; index++) { const result = this.elementType.check(val[index]); // Don't bother collecting all errors in an array: for long arrays this is very obnoxious if(result instanceof Err) { return new Err(at({ kind: "array-element", index }, result.issue, "array")); } } // If we got this far, there were no errors; it's an Array return val as Array; } /* * Slice each element in one pass so nested structural checkers can preserve the exact values they * validated. The default check-then-project flow would bypass a child's sliceResult override. */ sliceResult(val: any): Result> { if(!Array.isArray(val)) return new Err(typeMismatch("array", val)); const result: T[] = []; for(let index = 0; index < val.length; index++) { const sliced = this.elementType.sliceResult(val[index]); if(sliced instanceof Err) { return new Err(at({ kind: "array-element", index }, sliced.issue, "array")); } result.push(sliced); } return result; } protected merge(type: TypedKind): TypedKind & R> | undefined { if(!(type instanceof Arr)) return undefined; return asKind & R>(new Arr( this.elementType.and(type.elementType), )); } protected project(val: any): Projection> { return { kind: "structural", value: val.map((element: any) => { const projection = this.projectionOf(this.elementType, element); return projection.kind === "none" ? element : projection.value; }), }; } } export function array(t: Type): Arr { return new Arr(t); }