Closed mgreystone closed 3 days ago
@mgreystone Hi,
This is a current design limitation with TemplateLiteral (mostly to support patterned numerics without having the ability to infer
inside the TemplateLiteral (as you would with TypeScript)). You can still express numeric patterns, but it requires a bit more work to construct the underlying pattern. The following achieves this.
import { Value } from '@sinclair/typebox/value'
import { Type, Static } from '@sinclair/typebox'
// Positive Pattern
const Positive = Type.Union([
Type.TemplateLiteral('${number}.${number}e${number}'),
Type.TemplateLiteral('${number}.${number}'),
Type.TemplateLiteral('${number}'),
])
// Numeric (Positive | - Positive)
const Numeric = Type.Union([
Type.TemplateLiteral([Type.Literal('-'), Positive]),
Type.TemplateLiteral([Positive]),
])
// Use Template Literal to Reduce Union anyOf into Regular Expression Pattern.
const Numberish = Type.TemplateLiteral([Numeric])
// Preview Pattern
console.log(Numberish.pattern)
// Hover Numberish for Union Variants
type Numberish = Static<typeof Numberish>
// @ts-expect-error `foobar` is not a number
const a: Numberish = "foobar";
const b: Numberish = "4";
const c: Numberish = "0.5";
const d: Numberish = "-4";
const e: Numberish = "-0.5";
const f: Numberish = "5.3e10";
// console.log(Array.from(Value.Errors(Numberish, a))); // expected error, "Expected string to match '^(0|[1-9][0-9]*)$'"
console.log(Array.from(Value.Errors(Numberish, b))); // no errors, as expected
console.log(Array.from(Value.Errors(Numberish, c))); // no errors, as expected
console.log(Array.from(Value.Errors(Numberish, d))); // no errors, as expected
console.log(Array.from(Value.Errors(Numberish, e))); // no errors, as expected
console.log(Array.from(Value.Errors(Numberish, f))); // no errors, as expected
Template Literals are currently pending a rebuild under TypeBox's new Parsing Infrastructure, so the current functionality is locked in place until then. Hopefully the above helps provide a workaround if you need number matching of this kind.
Cheers! S
@sinclairzx81 Thank you for your response. The limitations make perfect sense. Do you think the new parsing infrastructure would be able to parse something closer to the typescript-behavior of ${number}
? Or would we still need to craft something more tailored to the strings we'd expect?
@mgreystone Hiya,
The limitations make perfect sense. Do you think the new parsing infrastructure would be able to parse something closer to the typescript-behavior of ${number}? Or would we still need to craft something more tailored to the strings we'd expect?
Yes, it should be possible, but it is a huge technical challenge. TypeBox does strive for parity with TypeScript, however supporting language features such as infer
has proved elusive thus far (but not for lack of trying)
type IsNumber<T> = (
T extends `${infer _ extends number}` ? true : false
// ^ need infer to answer this question
)
type a = IsNumber<'foobar'> // false
type b = IsNumber<'4'> // true
type c = IsNumber<'0.5'> // true
type d = IsNumber<'-4'> // true
type e = IsNumber<'-0.5'> // true
type f = IsNumber<'5.3e10'> // true
Infer would be a requirement for turing complete-ness of the type system (so I am chasing it), but the API wouldn't be very friendly.
// This might be achievable with some work
const IsNumber<T extends TSchema>(T: T) => Type.Extends(
T,
Type.TemplateLiteral('${infer _ extends number}'),
// ^ need infer to answer this question
Type.Literal(true),
Type.Literal(false),
)
// This is less achievable (at least I haven't been able get this to work without hordes of additional infrastructure)
const InferNumber<T extends TSchema>(T: T) => Type.Extends(
T,
Type.TemplateLiteral('${infer R extends number}'),
(R) => R, // where R is the inferred TNumber derived from the TemplateLiteral
Type.Never(),
)
This said, TypeBox just got a new Syntax Types feature which might provide avenues to explore for expressing both TS syntax (hiding a lot of the Type Builder complexity), as well as allowing the Type Builder to explode in to more structures able to express infer
without concerns about making the API DX friendly (which has been a balancing act trying to get these advanced features in)
Anyway. Hope this brings a bit of insight into where things are at, will close up this issue for now as TemplateLiteral is working as per original design.
Keep an eye out for updates in the new year though, I am working to optimize the ParseBox backend (used for Syntax Types) and pushing it hard for various grammars. I you have experience with parsing, the ParseBox project welcomes contributions from the community, (and a TemplateLiteral built with ParseBox would be a great way to get involved with both TypeBox and ParseBox). There's a lot of research and development still to do!
https://github.com/sinclairzx81/parsebox
Cheers! S
When using
Type.Number()
inside aType.TemplateLiteral
, only non-negative integers in decimal notation do not cause errors. Decimal numbers, negative numbers, exponential notation, & probably other notations cause unexpected errors when validating.Thank you for everything.