const x = () => {
return 1 + 2
}
const y = () => {
return 1 + 2
}
type TArg = ?
const fn = (arg: TArg) => {
console.log(arg)
}
// I want 'fn' to only accept the result of 'x' and throw an error if 'y()' is passed
fn(y()) // This should throw a type error
fn(x()) // Only this should be valid
I want to ensure that the function fn only accepts the return value of the function x and throws a TypeScript error if any other function's return value, like y(), is passed.
How can I enforce this type constraint in TypeScript?
If you really really want to do this, you could use a branded type to get something resembling nominal typing. The TypeScript Playground has an example which does just this. Some good use cases for this may be e.g. making a type for separate currencies, a validated UUID or email string, or some other specialization of a primitive type which you want to enforce at the type level.
You essentially create a type intersection of the type you actually want to use as an argument, but with a unique string (or better, a unique Symbol
) as a property. Don't try to actually assign that field, it's there just for the type system. Cast or use a type predicate function to turn a primitive to the branded type.
type BrandedX = number & {__brand: 'branded x'}
// ^^^^^^^
// Doesn't have to be `__brand`, could be anything sufficiently obvious.
const x = (): BrandedX => {
return 1 + 2 as BrandedX
}
const y = () => {
return 1 + 2
}
const fn = (arg: BrandedX) => {
console.log(arg) // 3
}
fn(y())
/* ^^^^
* Argument of type 'number' is not assignable to parameter of type 'BrandedX'.
* Type 'number' is not assignable to type '{ __brand: "branded x"; }
*/
fn(x()) // Works fine
Some TS type libraries offer a helper for this, as well as some validation libraries like Zod.
If you want to make a helper for it yourself, try something like:
type Branded<BaseType, Brand extends string> = BaseType & { __brand: Brand }
// e.g.
type BrandedX = Branded<number, 'branded x'>
Branded types also combine nicely with type guards:
type ValidatedEmail = Branded<string, 'validated email'>
const isValidEmail = (value: string): value is ValidatedEmail => {
// imagine proper validation here
return value.includes("@")
}
const sendEmail = (email: ValidatedEmail) => { }
// ...
const input: string = ""
if (!isValidEmail(input)) {
sendEmail(input) // Error: Argument of type 'string' is not assignable to parameter of type 'ValidatedEmail'.
return
}
sendEmail(input) // no error!