type-fest icon indicating copy to clipboard operation
type-fest copied to clipboard

PositiveIntegerString type

Open mohsen1 opened this issue 4 months ago • 4 comments

This is more of a question for advanced type experts. Would it be possible to make a type that only allows positive integers in a string? E.g. "123" and "82739283293237"

In my attempt to use template string types with many unions will hit a wall with

Expression produces a union type that is too complex to represent.(2590)

Also posted this question here https://github.com/ts-essentials/ts-essentials/issues/388

See my attempt here

My attempt for PositiveIntegerString

type Digits = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'; // Problem: This pattern can not be repeated for too long... type PositiveIntegerString = ${Digits}${Digits | ''}${Digits | ''}${Digits | ''};

function processPositiveInteger(input: PositiveIntegerString) { console.log("Valid positive integer string:", input); }

// Example Usage: processPositiveInteger("3"); // OK processPositiveInteger("323"); // OK processPositiveInteger("1323"); // OK processPositiveInteger("1322323233"); // Should be OK but is not processPositiveInteger("-1"); // Error processPositiveInteger("12.34"); // Error

mohsen1 avatar Apr 23 '24 09:04 mohsen1

Are you asking for "positive" or "non-negative"? Because 0 is not a positive number.

sindresorhus avatar Apr 23 '24 10:04 sindresorhus

We have some internal types here that may be useful as inspiration:

  • https://github.com/sindresorhus/type-fest/blob/cb6cdaba3a391dcf6f00f02fafe401d341021884/source/internal.d.ts#L113-L147
  • https://github.com/sindresorhus/type-fest/blob/cb6cdaba3a391dcf6f00f02fafe401d341021884/source/internal.d.ts#L623-L641

sindresorhus avatar Apr 23 '24 10:04 sindresorhus

Are you asking for "positive" or "non-negative"?

Concretely non-negative. Thank you for clarification and the hints in the source code.

I believe recursively inferring the first digit and ensuring it is on of 0..9 digits should work. Let me try this!

mohsen1 avatar Apr 23 '24 11:04 mohsen1

@mohsen1 Yes it is 100% possible:

const a0: PositiveIntegerStringType<'0'> = 0; // works
const a1: PositiveIntegerStringType<'82739283293237'> = 82739283293237; // works
const a2: PositiveIntegerStringType<'82739.283293237'> = 82739.28329323; // never
const a3: PositiveIntegerStringType<'-82739.283293237'> = 82739.28329323; // never
const a4: PositiveIntegerStringType<'-1'> = -1; // never

When it works, no squiggly lines, when it's never you get the squiggly lines.

Here's the type definition:

type PositiveIntegerStringType<S extends string> = IfEquals<
  IsPositiveInteger<Integer<NumerifyString<S>>>,
  true,
  Integer<NumerifyString<S>>,
  never
>;

with

type Numeric = number | bigint; 

/**
 * Turn a given string literal to a numeric 
 * @example 
 * 
 * NumerifyString<'54'>; // 54
 * NumerifyString<'699620.000000001'>; // 699620.000000001
  * IsNegativeFloat<NumerifyString<'-699620.000000001'>>; // true 
 */
export type NumerifyString<S extends string> = S extends `${infer N extends
  Numeric}`
  ? N
  : never;

/**
 * Type representing an integer
 */
export type Integer<N extends Numeric> = IfExtends<
  IsInteger<N>,
  true,
  N,
  never
>;


/**
 * Check if a given numeric value is an integer
 * @returns
 * true if it is, else false
 */
export type IsInteger<N extends Numeric> = number extends N
  ? false | true
  : N extends N
    ? `${N}` extends `${string}.${string}`
      ? false
      : true
    : never;

/**
 * Is it a positive integer ?
 * @return
 * `true` if it is, else `false`
 */
export type IsPositiveInteger<F extends Numeric> = IsPositive<Integer<F>>;

/**
 * Checks if a given numeric value is in [0,+∞[
 * @returns
 * true if it is, otherwise false
 */
export type IsPositive<N extends Numeric> = N extends N
  ? Numeric extends N
    ? boolean
    : `${N}` extends `-${Numeric}`
      ? false
      : true
  : never;


/**
 * Conditional type that checks if type `T` is equal to type `P`.
 * If `T` is equal to `P`, the type resolves to `Do`, otherwise `Else`.
 * @example
 *  type Result1 = IfEquals<string, string, true, false>; // is true
 *  type Result2 = IfEquals<number, string, true, false>; // is false
 *  type Result3 = IfEquals<boolean, boolean, true, false>; // is true
 *
 *  type IsExactlyString<T> = IfEquals<T, string, true, false>;
 *  type IsExactlyNumber<T> = IfEquals<T, number, true, false>;
 *
 *  type TestString = IsExactlyString<string>; // is true
 *  type TestNumber = IsExactlyNumber<number>; // is false
 *  type TestBoolean = IsExactlyString<boolean>; // is false
 */
export type IfEquals<T, P, Do, Else> = Equals<T, P> extends true ? Do : Else;


/**
 * Conditional  type that checks if two types `X` and `Y` are exactly equal.
 * If `X` is equal to `Y`, the  type resolves to `true`; otherwise `false`.
 * @example
 *  type Result1 = Equals<string, string>; // is true
 *  type Result2 = Equals<number, string>; // is false
 *  type Result3 = Equals<boolean | string, string | boolean>; // is true
 */
export type Equals<X, Y> = (<T>() => T extends X ? true : false) extends <
  T,
>() => T extends Y ? true : false
  ? true
  : false;

I hope that helps.

AshGw avatar Apr 27 '24 01:04 AshGw