Generic type is type script - javascript

function identity<String>(arg: String): String {
return arg;
}
let myIdentity = identity([2]);
console.log()
Hi could someone help me to understand why this doesn't throw any type error even if i am passing a array of numbers ?
Is it because the type is "String" instead of "string" , which looks for objects than primitive ?
If answer is "yes" , if i change everything to string i get error saying string is never used
function identity<string>(arg: string): string {
return arg;
}
let myIdentity = identity([2]);
console.log(myIdentity )
'string' is declared but its value is never read.

You are shadowing the builtin types String and string by using their names as your generic parameter. Just use a parameter name that is not a built-in to solve it:
TS Playground link
function identity<T extends string>(value: T): T {
return value;
}
identity([2]); // error [2] doesn't extend string
identity('2'); // ok
If you want a generic identity function without any kind of constraint, you can use an unconstrained type parameter:
TS Playground link
function identity<T>(value: T): T {
return value;
}
const numberArrayValue = identity([2]); // number[]
const objectValue = identity({b: 2}); // { b: number; }
const stringLiteralValue = identity('2'); // "2"
const numberLiteralValue = identity(2); // 2

Related

Typescript types that can be overwritten based on type arguments

I'm trying to figure out the best way to tell the typescript compiler that a function that returns T | null will definitely be T in certain circumstances.
Perhaps I'm thinking about this wrong in which case I'm also open to new ideas, here is the snippet:
type ValueOrNull<T, N> = N extends false ? T | null : T;
export function getCookie<T = any, N = false>(name: string): ValueOrNull<T, N> {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
if (match) {
const [, , value] = match;
return parseCookieValue(value) as T;
}
return null;
}
My thinking was if I could call the function as follows: getCookie<TMyCookie, true>("my_cookie") that typescript would know that I'm sure the cookie would be there, and the function would not return null. For example after a successful login.
N extends false ? feels wrong but N === false doesn't work.
the compiler error is Type 'null' is not assignable to type 'ValueOrNull<T, N>'.ts(2322)
Thanks a lot
You can do that fairly easily with function overloads and a second parameter; that also lets you build in a useful explanatory error when (inevitably) the programmer "knows" the cookie exists but it dooesn't:
export function getCookie<T>(name: string, required: true): T;
export function getCookie<T>(name: string, required?: false): T | null;
export function getCookie<T>(name: string, required?: boolean): T | null {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
if (match) {
const [, , value] = match;
// You'll want to put your `parseCookingvalue` back here,
// the `as any as T` is just because we don't have that
// function available to use in the question.
return /*parseCookieValue(value)*/ value as any as T;
} else if (required) {
throw new Error(`Required cookie "${name}" was not found`);
}
return null;
}
const a = getCookie<string>("a", true);
// ^? −−−− type is string
const b = getCookie<string>("b", false);
// ^? −−−− type is string | null
Playground link
Here's an alternative for you, though: You could leave getCookie fairly simple and have a general-use type assertion function that you can use anywhere you get back something that may be null or undefined and you "know" it's not null or undefined:
export function getCookie<T>(name: string): T | null {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
if (match) {
const [, , value] = match;
// You'll want to put your `parseCookingvalue` back here,
// the `as any as T` is just because we don't have that
// function available to use in the question.
return /*parseCookieValue(value)*/ value as any as T;
}
return null;
}
export function assertIsNotNullish<T>(value: T | null | undefined): asserts value is T {
if (value == null) {
throw new Error(`Expected non-nullish value, got ${value}`);
}
}
const a = getCookie<string>("a");
assertIsNotNullish(a);
console.log(a);
// ^? −−−− type is string
const b = getCookie<string>("b");
console.log(b);
// ^? −−−− type is string | null
Playground link

Typescript: Typing Function Return Type based on Parameters

I was wondering if there is way to type the return of a function based on the input given to it. Here is an example of what I am thinking:
function toEnum(...strings: string[]) {
const enumObject = {};
strings.forEach((str) => {
enumObject[str.toUpperCase()] = str;
});
return enumObject;
}
const myEnum = toEnum('one', 'two', 'three')
Is there a way to type this function so that we know that myEnum looks like:
{
ONE: 'one',
TWO: 'two',
THREE: 'three'
}
edit:
as #dariosicily mentioned, we could type enumObject using a Record<string, string> or index signatures, but I am wondering if there is a way to know the actual keys present in the return object based on the params passed in.
There is an intrinsic Uppercase<T> string manipulation utility type which, when given a string literal type as input, produces an uppercase version as output. So Uppercase<"abc"> and "ABC" are the same type. Using this type we can create a mapped type with remapped keys to express the output type of toEnum(), given the union of the string literal types of its arguments:
function toEnum<K extends string>(...strings: K[]): { [P in K as Uppercase<P>]: P } {
const enumObject: any = {};
strings.forEach((str) => {
enumObject[str.toUpperCase()] = str;
});
return enumObject;
}
Note that toEnum() is generic in K, the union of the element types of the strings array. Note that K is constrained to string so that strings is indeed an array of strings, and because this constraint gives the compiler a hint that we want to infer string literal types for its elements instead of just string. You definitely need to use generic here, otherwise you'd just get Record<string, string> out of the function.
The type {[P in K as Uppercase<P>]: P} iterates over every string P in the original K union and remaps it to an uppercase version as the key, and then uses just the same type P as the value. That's the type you wanted.
Also note that I gave enumObject the any type so as to opt out of strict type checking inside the implementation of toEnum(); the compiler is unable to follow the logic that enumObject[str.toUpperCase()]=str will be an appropriate operation on a value of type {[P in K as Uppercase<P>]: P}, so we won't even make it try.
Anyway you can test that it does what you want:
const myEnum = toEnum('one', 'two', 'three', "fortyFive");
/* const myEnum: {
ONE: "one";
TWO: "two";
THREE: "three";
FORTYFIVE: "fortyFive";
} */
console.log(myEnum.THREE) // "three" both at compile and runtime
In the comments you mentioned that for something like fortyFive, you'd like the key to be FORTY_FIVE instead of FORTYFIVE. That is, you don't just want the key to be an uppercase version of the input. You want the input to be interpreted as lower camel case and the output to be all-upper snake case (also known as SCREAMING_SNAKE_CASE).
This is also possible in TypeScript, using template literal types to split a string literal type into characters, and recursive conditional types to operate on these characters programmatically.
First let's do it at the type level:
type LowerPascalToUpperSnake<T extends string, A extends string = ""> =
T extends `${infer F}${infer R}` ? LowerPascalToUpperSnake<R,
`${A}${F extends Lowercase<F> ? "" : "_"}${Uppercase<F>}`
> : A;
Note that it is useful to have a function that does the same thing at the value level:
function lowerPascalToUpperSnake<T extends string>(str: T) {
return str.split("").map(
c => (c === c.toLowerCase() ? "" : "_") + c.toUpperCase()
).join("") as LowerPascalToUpperSnake<T>
}
Both the type and the function behave similarly; the idea is to iterate over each character of the string, insert an underscore if and only if the current character is not lowercase, and then insert an uppercase version of the current character.
You can verify that this works:
const test = lowerPascalToUpperSnake("abcDefGhiJklmNop");
// const test: "ABC_DEF_GHI_JKLM_NOP"
console.log(test); // "ABC_DEF_GHI_JKLM_NOP"
The value at runtime and the type computed by the compiler agree.
And now we can use the "lower-Pascal-to-upper-snake" operation in toEnum() instead of the original uppercase operation:
function toEnum<K extends string>(...strings: K[]):
{ [P in K as LowerPascalToUpperSnake<P>]: P } {
const enumObject: any = {};
strings.forEach((str) => {
enumObject[lowerPascalToUpperSnake(str)] = str;
});
return enumObject;
}
And see it in action:
const myEnum = toEnum('one', 'two', 'three', "fortyFive");
/* const myEnum: {
ONE: "one";
TWO: "two";
THREE: "three";
FORTY_FIVE: "fortyFive";
} */
console.log(myEnum.FORTY_FIVE) // "fortyFive"
Looks good!
Playground link to code
The problem is due to the fact that while in the javascript language the line enumObject[str.toUpperCase()] = str; assignment is legitimate with typescript the same line causes an error because you have not explicitely declared the index signature of the enumObject or explicitely declared it as any.
In this cases one way to solve the issue is use the builtin Record utility type applying it to your enumObject like below:
function toEnum(...strings: string[]) {
const enumObject: Record<string, string> = {};
strings.forEach((str) => {
enumObject[str.toUpperCase()] = str;
});
return enumObject;
}
const myEnum = toEnum('one', 'two', 'three')
//it will print { ONE: 'one', TWO: 'two', THREE: 'three' }
console.log(myEnum);
Edit: answering to the question
there is a way to know the actual keys present in the return object
based on the params passed in
You can use the Object.keys method and return the keys as an Array<string>:
const myEnum = toEnum('one', 'two', 'three')
//it will print [ONE, TWO, THREE]
const keys = (Object.keys(myEnum) as Array<string>);
If you want to create a new type from keys you can use typeof:
const keys = (Object.keys(myEnum) as Array<string>);
//type KeysType = 'ONE' | 'TWO' | 'THREE'
type KeysType = typeof keys[number];

How to define nodeCategoryProperty function for a Model in typescript?

nodeCategoryProperty function expects to have the signature -
(a: ObjectData, b?: string) => string, which, IMO, should be (a: ObjectData, b?: string) => string | void, as this function should not return anything if it is being used as a setter.
If the second argument is supplied, the function should modify the node data
object so that it has that new category name.
https://gojs.net/latest/api/symbols/Model.html#nodeCategoryProperty
For the function I have defined -
const categoryPropertyFunction = (partData: ObjectData, category?: string): string =>
{
if (category) partData.type = category;
else return partData.type;
};
I am getting TypeScript error:
TS2366: Function lacks ending return statement and return type does not include 'undefined'.
Here is an example of storing the category name as a property of the "d" property on the data object:
const model = . . .;
model.nodeCategoryProperty = function (obj: go.ObjectData, str?: string): string {
if (arguments.length > 1) obj.d.category = str;
return obj.d.category;
};
Note the use of function instead of using an arrow function, so that one can check the number of arguments.
The same kind of implementation can be used for the other "...Property" functional properties of the Model classes.

Is there any way to get a names of arguments of function in typescript? [duplicate]

This question already has answers here:
Get function parameter names and types in TypeScript
(1 answer)
How to get function parameter names/values dynamically?
(34 answers)
Closed 1 year ago.
I am trying to make a wrapper function which will take a function as an input and will return a newly typed function which allow both a list of parameters and an object which contain parameter names as keys.
I have written the following code for that. The code works as expected. The problem is that I have pass an additional type which contains the keys as parameter name and value as type of that parameter. I want to do this dynamically. I want to access the names of the parameters
//I want remove this Args parameters and make it dynamically using F type.
function wrapper<F extends (...args: any) => any, Args>(func: unknown) {
type ParametersList = Parameters<F>;
return func as (...args: [Args] | ParametersList) => ReturnType<F>;
}
const add = (x: number, y: number) => x + y;
const wrappedAdd = wrapper<typeof add, { x: number; y: number }>(add);
The Parameters function returns a named tuple(which is a new feature I guess). Is there any way we could get the names/labels of that tuple. You can also suggest any other way.
Thanks.
###Edit:
Ok I after some research I found out that we cannot get the names of the parameters of the function. So now my goal is to shorten my code a little bit. In the above code where I am passing an object in place of Args. I only want to pass an array of strings.
const wrappedAdd = wrapper<typeof add, ["x", "y"]>(add);
I want to generate an object dynamically using this array. Thanks
I think it is better to define any types in global scope.
type Elem = any;
type Predicate<Key extends number, Value extends Elem> = Record<Key, Value>
type Reduce<
Arr extends ReadonlyArray<Elem>,
Result extends Record<string, any> = {}
> = Arr extends []
? Result
: Arr extends [infer H]
? Result & Predicate<0, H>
: Arr extends readonly [...infer Tail, infer H]
? Tail extends ReadonlyArray<Elem>
? Reduce<Tail, Result & Predicate<Tail['length'], H>>
: never
: never;
function wrapper<F extends (...args: any) => any>(func: F):
(...args: [Reduce<Parameters<F>>] | Parameters<F>) => ReturnType<F> {
return func
}
const add = (x: number, y: string, z: number[]) => x + y;
const wrappedAdd = wrapper(add);
const add = (x: number, y: string, z: number[]) => x + y;
const wrappedAdd = wrapper(add)({ 0: 1, 1: 'hello', 2: [1] }); // ok
const wrappedAdd2 = wrapper(add)(1, 'hello', [1]); // ok
Btw, no need to use type assertion here. I mean you can get rid of as operator
If you are interested in more examples of such kind of types, you can take a look on my article
Here you have representation of Reduce type in almost pure JS:
const reduce = <T,>(arr: T[], cache = {}) => {
if (arr.length === 0) {
return cache
}
if (arr.length === 1) {
const [head] = arr;
return { ...cache, [0]: head }
}
const [head, ...rest] = arr;
return reduce(rest, { ...cache, [rest.length]: head })
}
Playground
Like #jcalz said:
Parameter names are not observable in the type system except as documentation for IntelliSense
There is no way to infer parameter name in TS and put it into another type, so I decided to use index type.
0 for first argument, 1 for second, etc ...
Regarding using argument names:
Docs
There is one place where the differences begin to become observable though: readability.
They’re purely there for documentation and tooling.
const add = (x: number) => x + x
type Args = Parameters<typeof add>
type Infer = Args extends [infer R] ? R : never // number
type Infer2 = Args extends [x: infer R] ? R : never // number
Hence, I don't think it is possible in current version of TypeScript.

How to annotate a function with multiple possible call signatures in Flow?

In JavaScript, it's common to have a function that may be called in more than one way – e.g. with handful of positional arguments or a single options object or some combination of the two.
I've been trying to work out how to annotate this.
One way I tried was to annotate rest args as a union of various possible tuples:
type Arguments =
| [string]
| [number]
| [string, number]
;
const foo = (...args: Arguments) => {
let name: string;
let age: number;
// unpack args...
if (args.length > 1) {
name = args[0];
age = args[1];
} else if (typeof args[0] === 'string') {
name = args[0];
age = 0;
} else {
name = 'someone';
age = args[1];
}
console.log(`${name} is ${age}`);
};
// any of these call signatures should be OK:
foo('fred');
foo('fred', 30);
foo(30);
The above snippet is contrived; I could probably just use (...args: Array<string | number>) in this example, but for more complex signatures (e.g. involving a typed options object that can be alone or with prior args) it would be useful to be able to define a precise, finite set of possible call signatures.
But the above doesn't type-check. You can see a bunch of confusing errors in tryflow.
I also tried typing the function itself as a union of separate entire function defs, but that didn't work either:
type FooFunction =
| (string) => void
| (number) => void
| (string, number) => void
;
const foo: FooFunction = (...args) => {
let name: string;
let age: number;
// unpack args...
if (args.length > 1) {
name = args[0];
age = args[1];
} else if (typeof args[0] === 'string') {
name = args[0];
age = 0;
} else {
name = 'someone';
age = args[1];
}
console.log(`${name} is ${age}`);
};
// any of these call signatures should be OK:
foo('fred');
foo('fred', 30);
foo(30);
How should I approach type-annotating functions with multiple possible call signatures? (Or are multi-signatures considered an anti-pattern in Flow, and I just shouldn't be doing it at all – in which case, what is the recommended approach for interacting with third party libraries that do it?)
The errors you are seeing are a combination of a bug in your code and a bug in Flow.
Bug in your code
Let's start by fixing your bug. In the third else statement, you assign the wrong value to
} else {
name = 'someone';
age = args[1]; // <-- Should be index 0
}
Changing the array access to be the correct index removes two errors. I think we can both agree this is exactly what Flow is for, finding errors in your code.
Narrowing type
In order to get to the root cause of the issue, we can be more explicit in the area where the errors are so that we can more easily see what the problem is:
if (args.length > 1) {
const args_tuple: [string, number] = args;
name = args_tuple[0];
age = args_tuple[1];
} else if (typeof args[0] === 'string') {
This is effectively the same as before but because we're very clear about what args[0] and args[1] should be at this point. This leaves us with a single error.
Bug in Flow
The remaining error is a bug in Flow: https://github.com/facebook/flow/issues/3564
bug: tuple type is not interacting with length assertions (.length >= 2 and [] | [number] | [number, number] type)
How to type overloaded functions
Flow is not great at dealing with variadics with different types, as in this case. Variadics are more for stuff like function sum(...args: Array<number>) where all the types are the same and there is no maximum arity.
Instead, you should be more explicit with your arguments, like so:
const foo = (name: string | number, age?: number) => {
let real_name: string = 'someone';
let real_age: number = 0;
// unpack args...
if (typeof name === 'number') {
real_age = name;
} else {
real_name = name;
real_age = age || 0;
}
console.log(`${real_name} is ${real_age}`);
};
// any of these call signatures should be OK:
foo('fred');
foo('fred', 30);
foo(30);
This causes no errors and I think is just easier to read for developers, too.
A better way
In another answer, Pavlo provided another solution that I like more than my own.
type Foo =
& ((string | number) => void)
& ((string, number) => void)
const foo: Foo = (name, age) => {...};
It solves the same problems in a much cleaner way, allowing you much more flexibility. By creating an intersection of multiple function types, you describe each different way of calling your function, allowing Flow to try each one based on how the function is called.
You can define multiple function signatures by joining them with &:
type Foo =
& ((string | number) => void)
& ((string, number) => void)
Try it.
Of the three possible workouts you gave, I've figured out how to make it work using an single options object, however because you require at least one object to be set, you need to define each possibility.
Like this:
type Arguments =
{|
+name?: string,
+age?: number
|} |
{|
+name: string,
+age?: number
|} |
{|
+name?: string,
+age: number
|};
const foo = (args: Arguments) => {
let name: string = args.name ? args.name : 'someone';
let age: number = typeof args.age === 'number' && !isNaN(args.age) ? args.age : 0;
console.log(`${name} is ${age}`);
}
// any of these call signatures are OK:
foo({ name: 'fred' });
foo({ name: 'fred', age: 30 });
foo({ age: 30 });
// fails
foo({});

Categories