Skip to content

[Discuss] Consider tradeoffs between 0-overhead and string verification #201

@Eyas

Description

@Eyas

With schema-dts v1, we already know to return compile errors for:

import { Number, Integer, Float } from 'schema-dts';

const a: Number = '';  // error
const b: Number = 'z'; // error
const c: Number = '5'; // success
const d: Number = '5.323'; // success
const e: Number = '534553.' // success
const f: Number = '.534553' // success
const g: Number = '4943.4433' // error
const h: Number = '43545ax' // error
const i: Number = '0x324' // success
const j: Number = '0x234deadbeef' // success
const k: Number = '0x123z' // error

// Same with types Integer and Float

But we don't yet verify that integers really are integers, for example:

const i: Integer = '5.5';  // success, should be error

I've been trying different strategies to make sure the Integer type can complain about 5.5 natively, but I can only come up with constructs where a function argument is vetted for being an integer string.

I.e., I can write this code that lets us do this:

import {Integer, VettedIntegerString, intString, hexString} from 'schema-dts';

const a: VettedIntegerString = intString('');  // error
const b: VettedIntegerString = intString('a');  // error
const c: VettedIntegerString = intString('abc');  // error
const d: VettedIntegerString = intString('5');  // success
const e: VettedIntegerString = intString('55');  // success

const a2: VettedIntegerString = hexString('0x');  // error
const b2: VettedIntegerString = hexString('0xa');  // success
const c2: VettedIntegerString = hexString('0xabc');  // success
const d2: VettedIntegerString = hexString('0x5');  // success
const e2: VettedIntegerString = hexString('0x55');  // success
const e2: VettedIntegerString = hexString('55');  // error

// where using `Integer` instead of VettedIntegerString also works:

const xa: Integer = intString('');  // error
const xb: Integer = intString('a');  // error
const xc: Integer = intString('abc');  // error
const xd: Integer  = intString('5');  // success
const xe: Integer = intString('55');  // success
const xa2: Integer = hexString('0x');  // error
const xb2: Integer = hexString('0xa');  // success
const xc2: Integer = hexString('0xabc');  // success
const xd2: Integer = hexString('0x5');  // success
const xe2: Integer = hexString('0x55');  // success
const xe2: Integer = hexString('55');  // error

// but also:

const i: Integer = 5;  // works

The code would look like this:

const Integer_Type_Is_Constructed_With_intString_Or_hexString_Helper_Function =
  Symbol(
    'Verified Integer String (this value never used, only its type is used in compilation)',
  );
type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9';
type HexDigit =
  | Digit
  | 'a'
  | 'b'
  | 'c'
  | 'd'
  | 'e'
  | 'f'
  | 'A'
  | 'B'
  | 'C'
  | 'D'
  | 'E'
  | 'F';
type IntegerString<T extends string> = T extends `${Digit}${infer Rest}`
  ? Rest extends ''
    ? T
    : IntegerString<Rest>
  : never;
type HexSequence<T extends String> = T extends `${HexDigit}${infer Rest}`
  ? Rest extends ''
    ? T
    : HexSequence<Rest>
  : never;
type HexString<T extends string> = T extends `0x${infer Rest}`
  ? HexSequence<Rest> extends never
    ? never
    : T
  : never;
type WhenExists<Existence, T> = Existence extends never ? never : T;

export function intString<T extends string>(
  t: WhenExists<IntegerString<T>, T>,
): T & {
  [tag in typeof Integer_Type_Is_Constructed_With_intString_Or_hexString_Helper_Function]: true;
} {
  return t as unknown as T & {
    [tag in typeof Integer_Type_Is_Constructed_With_intString_Or_hexString_Helper_Function]: true;
  };
}

export function hexString<T extends string>(
  h: HexString<T>,
): T & {
  [tag in typeof Integer_Type_Is_Constructed_With_intString_Or_hexString_Helper_Function]: true;
} {
  return h as unknown as T & {
    [tag in typeof Integer_Type_Is_Constructed_With_intString_Or_hexString_Helper_Function]: true;
  };
}

export type VettedIntegerString = string & {
  [tag in typeof Integer_Type_Is_Constructed_With_intString_Or_hexString_Helper_Function]: true;
};

But this would be the first case where valid inline JSON-LD could not be verified. I.e. all string assignments to Integer would begin failing unless they were computed with intString() and hexString().

Should we wait until a better option becomes expressible in the language, or is this an improvement as-is?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions