rescript-compiler icon indicating copy to clipboard operation
rescript-compiler copied to clipboard

[PoC] Gentype emit TS assertions to let TS validate that bindings are correct

Open zth opened this issue 3 months ago • 1 comments

Papaparse.res:

@gentype.satisfies(("papaparse", "ParseError"))
type parseError = {
  /** A generalization of the error */
  @as("type") type_: [#Quotes | #Delimiter | #FieldMismatch],
  /** Standardized error code */
  code: [#MissingQuotes | #UndetectableDelimiter | #TooFewFields | #TooManyFields | #InvalidQuotes],
  /** Human-readable details */
  message: string,
  /** Row index of parsed data where error is */
  row?: int,
  /** Index within the row where error is */
  index?: int,
}

@gentype.satisfies(("papaparse", "ParseMeta"))
type parseMeta = {
  /** Delimiter used */
  delimiter: string,
  /** Line break sequence used */
  linebreak: string,
  /** Whether process was aborted */
  aborted: bool,
  /** Array of field names */
  fields?: array<string>,
  /** Whether preview consumed all input */
  truncated: bool,
  cursor: float,
}

@gentype.satisfies(("papaparse", "ParseResult"))
type parseResult<'t> = {
  /**
     * an array of rows. If header is false, rows are arrays; otherwise they are objects of data keyed by the field name.
     */
  data: array<'t>,
  /** an array of errors. */
  errors: array<parseError>,
  /**
     * contains extra information about the parse, such as delimiter used,
     * the newline sequence, whether the process was aborted, etc.
     * Properties in this object are not guaranteed to exist in all situations.
     */
  meta: parseMeta,
}

@gentype.satisfies(("papaparse", "parse")) @module("papaparse")
external parseCsvString: string => parseResult<'t> = "parse"

Emits Papaparse.assertions.ts:

export {};

type $RescriptTypeSatisfiesTypeScriptType<RescriptType extends TypeScriptType, TypeScriptType> = RescriptType;

type parseError = $RescriptTypeSatisfiesTypeScriptType<{
  /** A generalization of the error */
  readonly type: 
    "Delimiter"
  | "FieldMismatch"
  | "Quotes"; 
  /** Standardized error code */
  readonly code: 
    "TooManyFields"
  | "MissingQuotes"
  | "UndetectableDelimiter"
  | "TooFewFields"
  | "InvalidQuotes"; 
  /** Human-readable details */
  readonly message: string; 
  /** Row index of parsed data where error is */
  readonly row?: number; 
  /** Index within the row where error is */
  readonly index?: number
}, import("papaparse").ParseError>;
type parseMeta = $RescriptTypeSatisfiesTypeScriptType<{
  /** Delimiter used */
  readonly delimiter: string; 
  /** Line break sequence used */
  readonly linebreak: string; 
  /** Whether process was aborted */
  readonly aborted: boolean; 
  /** Array of field names */
  readonly fields?: string[]; 
  /** Whether preview consumed all input */
  readonly truncated: boolean; 
  readonly cursor: number
}, import("papaparse").ParseMeta>;
type parseResult<t> = $RescriptTypeSatisfiesTypeScriptType<{
  /** * an array of rows. If header is false, rows are arrays; otherwise they are objects of data keyed by the field name. */
  readonly data: t[]; 
  /** an array of errors. */
  readonly errors: parseError[]; 
  /** * contains extra information about the parse, such as delimiter used,
     * the newline sequence, whether the process was aborted, etc.
     * Properties in this object are not guaranteed to exist in all situations. */
  readonly meta: parseMeta
}, import("papaparse").ParseResult<t>>;

const parse = undefined as unknown as typeof import("papaparse").parse satisfies <t>(_1:string) => parseResult<t>;

This lets TypeScript typecheck that the bindings you've written in Papaparse.res is correct.

Very messy code and needs a bunch of cleanup. That can be done if this is an interesting direction that works out in practice.

zth avatar Sep 12 '25 06:09 zth

Open in StackBlitz

rescript

npm i https://pkg.pr.new/rescript-lang/rescript@7879
@rescript/darwin-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-arm64@7879
@rescript/darwin-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/darwin-x64@7879
@rescript/linux-arm64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-arm64@7879
@rescript/linux-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/linux-x64@7879
@rescript/runtime

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/runtime@7879
@rescript/win32-x64

npm i https://pkg.pr.new/rescript-lang/rescript/@rescript/win32-x64@7879

commit: eed7f63

pkg-pr-new[bot] avatar Sep 12 '25 06:09 pkg-pr-new[bot]